diff --git a/SConstruct b/SConstruct index 5f25eaa82..af0c28554 100644 --- a/SConstruct +++ b/SConstruct @@ -326,6 +326,7 @@ opts.AddVariables( BoolVariable('DEMO', 'Compile demo c++ application', 'False'), BoolVariable('PGSQL2SQLITE', 'Compile and install a utility to convert postgres tables to sqlite', 'False'), BoolVariable('COLOR_PRINT', 'Print build status information in color', 'True'), + BoolVariable('SAMPLE_INPUT_PLUGINS', 'Compile and install sample plugins', 'False'), ) # variables to pickle after successful configure step @@ -1318,3 +1319,8 @@ if not HELP_REQUESTED: # write the viewer.ini file SConscript('demo/viewer/SConscript') + + # if requested, build the sample input plugins + if env['SAMPLE_INPUT_PLUGINS']: + SConscript('plugins/input/templates/helloworld/build.py') + diff --git a/include/mapnik/datasource.hpp b/include/mapnik/datasource.hpp index ea2235329..ea768984c 100644 --- a/include/mapnik/datasource.hpp +++ b/include/mapnik/datasource.hpp @@ -38,7 +38,8 @@ #include #include -namespace mapnik { +namespace mapnik { + typedef MAPNIK_DECL boost::shared_ptr feature_ptr; struct MAPNIK_DECL Featureset diff --git a/plugins/input/templates/README b/plugins/input/templates/README new file mode 100644 index 000000000..2b80be843 --- /dev/null +++ b/plugins/input/templates/README @@ -0,0 +1,14 @@ +template plugins +---------------- + +Directory to hold sample plugin templates. + +These are NOT intended to be used except for testing by developers. + +Build these plugins with: + +$ scons SAMPLE_INPUT_PLUGINS=True + +Only an ultra-simple hello world is available currently, +but planned are example plugins templates for file-based +and sql-based datasources. diff --git a/plugins/input/templates/helloworld/README b/plugins/input/templates/helloworld/README new file mode 100644 index 000000000..b38d3b29e --- /dev/null +++ b/plugins/input/templates/helloworld/README @@ -0,0 +1,51 @@ +hello world plugin +------------------ + +This is a very simple sample plugin. It is designed to help developers +see the skeletal basics needed to achieve a functional datasource plugin. + +Code comments attempt to highlight which code is mandatory, which is +simply recommended, and which is purely fluff used to get the plugin to +actually show some data. + +When added to a map it provides a single point geometry representing +the center of any query. This means that it should place a point in +the middle of any map tile and display a "hello world!" label if used like: + + + + + + style + + hello + + + + + +Or used in python like: + +from mapnik2 import * +m = Map(600,400) +m.background = Color('white') +s = Style() +r = Rule() +r.symbols.append(PointSymbolizer()) +t = TextSymbolizer(Expression("[key]"),"DejaVu Sans Book",10,Color('black')) +t.displacement(15,15) +r.symbols.append(t) +s.rules.append(r) +m.append_style('style',s) +ds = Datasource(type="hello") +l = Layer('test') +l.styles.append('style') +l.datasource = ds +m.layers.append(l) +m.zoom_all() +render_to_file(m,'test.png') diff --git a/plugins/input/templates/helloworld/build.py b/plugins/input/templates/helloworld/build.py new file mode 100644 index 000000000..15f60543f --- /dev/null +++ b/plugins/input/templates/helloworld/build.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python + +# Mapnik uses the build tool SCons. + +# This python file is run to compile a plugin +# It must be called from the main 'SConstruct' file like: + +# SConscript('path/to/this/file.py') + +# see docs at: http://www.scons.org/wiki/SConscript() + +import os + +# Give this plugin a name +# here this happens to be the same as the directory +PLUGIN_NAME = 'hello' + +# Here we pull from the SCons environment exported from the main instance +Import ('env') + +# the below install details are also pulled from the +# main SConstruct file where configuration happens + +# DESTDIR is default None, PREFIX is default '/usr/local/' +install_prefix = os.path.join(env['DESTDIR'],env['PREFIX']) + +# LIBDIR_SCHEMA is likely either 'lib' or 'lib64', LIB_DIR_NAME is default None +lib_name = '%s%s' % (env['LIBDIR_SCHEMA'],env['LIB_DIR_NAME']) + +# plugins can go anywhere, and be registered in custom locations by Mapnik +# but the standard location is '/usr/local/lib/mapnik2/input' +install_dest = os.path.join(install_prefix,lib_name,'input') + +# clone the environment here +# so that if we modify the env it in this file +# those changes to not pollute other builds later on... +plugin_env = env.Clone() + +# Add the cpp files that need to be compiled +plugin_sources = Split( + """ + %(PLUGIN_NAME)s_datasource.cpp + %(PLUGIN_NAME)s_featureset.cpp + """ % locals() + ) + +# Add any external libraries this plugin should +# directly link to +libraries = [ '' ] # eg 'libfoo' + +# on mac os x we need to directly link to mapnik and libicu* +if env['PLATFORM'] == 'Darwin': + libraries.append('mapnik2') + # link libicuuc, but ICU_LIB_NAME is used custom builds of icu can + # have different library names like osx which offers /usr/lib/libicucore.dylib + libraries.append(env['ICU_LIB_NAME']) + +TARGET = plugin_env.SharedLibrary( + # the name of the target to build, eg 'sqlite.input' + '../%s' % PLUGIN_NAME, + # prefix - normally none used + SHLIBPREFIX='', + # extension, mapnik expects '.input' + SHLIBSUFFIX='.input', + # list of source files to compile + source=plugin_sources, + # libraries to link to + LIBS=libraries, + # any custom linkflags, eg. LDFLAGS + # in this case CUSTOM_LDFLAGS comes + # from Mapnik's main SConstruct file + # and can be removed here if you do + # not need it + LINKFLAGS=env.get('CUSTOM_LDFLAGS') + ) + +# if 'uninstall' is not passed on the command line +# then we actually create the install targets that +# scons will install if 'install' is passed as an arg +if 'uninstall' not in COMMAND_LINE_TARGETS: + env.Install(install_dest, TARGET) + env.Alias('install', install_dest) diff --git a/plugins/input/templates/helloworld/hello_datasource.cpp b/plugins/input/templates/helloworld/hello_datasource.cpp new file mode 100644 index 000000000..1bb3188b2 --- /dev/null +++ b/plugins/input/templates/helloworld/hello_datasource.cpp @@ -0,0 +1,86 @@ +// file plugin +#include "hello_datasource.hpp" +#include "hello_featureset.hpp" + +using mapnik::datasource; +using mapnik::parameters; + +DATASOURCE_PLUGIN(hello_datasource) + +hello_datasource::hello_datasource(parameters const& params, bool bind) + : datasource(params), + type_(datasource::Vector), + desc_(*params_.get("type"), *params_.get("encoding","utf-8")), + extent_() +{ + if (bind) + { + this->bind(); + } +} + +void hello_datasource::bind() const +{ + if (is_bound_) return; + + // every datasource must have some way of reporting its extent + // in this case we are not actually reading from any data so for fun + // let's just create a world extent in Mapnik's default srs: + // '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs' (equivalent to +init=epsg:4326) + // see http://spatialreference.org/ref/epsg/4326/ for more details + extent_.init(-180,-90,180,90); + + is_bound_ = true; +} + +hello_datasource::~hello_datasource() { } + +// This name must match the plugin filename, eg 'hello.input' +std::string const hello_datasource::name_="hello"; + +std::string hello_datasource::name() +{ + return name_; +} + +int hello_datasource::type() const +{ + return type_; +} + +mapnik::box2d hello_datasource::envelope() const +{ + if (!is_bound_) bind(); + + return extent_; +} + +mapnik::layer_descriptor hello_datasource::get_descriptor() const +{ + if (!is_bound_) bind(); + + return desc_; +} + +mapnik::featureset_ptr hello_datasource::features(mapnik::query const& q) const +{ + if (!is_bound_) bind(); + + // if the query box intersects our world extent then query for features + if (extent_.intersects(q.get_bbox())) + { + return mapnik::featureset_ptr(new hello_featureset(q.get_bbox(),desc_.get_encoding())); + } + + // otherwise return an empty featureset pointer + return mapnik::featureset_ptr(); +} + +mapnik::featureset_ptr hello_datasource::features_at_point(mapnik::coord2d const& pt) const +{ + if (!is_bound_) bind(); + + // features_at_point is rarely used - only by custom applications, + // so for this sample plugin let's do nothing... + return mapnik::featureset_ptr(); +} diff --git a/plugins/input/templates/helloworld/hello_datasource.hpp b/plugins/input/templates/helloworld/hello_datasource.hpp new file mode 100644 index 000000000..0e4b4e690 --- /dev/null +++ b/plugins/input/templates/helloworld/hello_datasource.hpp @@ -0,0 +1,51 @@ +#ifndef FILE_DATASOURCE_HPP +#define FILE_DATASOURCE_HPP + +// mapnik +#include + +class hello_datasource : public mapnik::datasource +{ + public: + // constructor + // arguments must not change + hello_datasource(mapnik::parameters const& params, bool bind=true); + + // destructor + virtual ~hello_datasource (); + + // mandatory: type of the plugin, used to match at runtime + int type() const; + + // mandatory: name of the plugin + static std::string name(); + + // mandatory: function to query features by box2d + // this is called when rendering, specifically in feature_style_processor.hpp + mapnik::featureset_ptr features(mapnik::query const& q) const; + + // mandatory: function to query features by point (coord2d) + // not used by rendering, but available to calling applications + mapnik::featureset_ptr features_at_point(mapnik::coord2d const& pt) const; + + // mandatory: return the box2d of the datasource + // called during rendering to determine if the layer should be processed + mapnik::box2d envelope() const; + + // mandatory: return the layer descriptor + mapnik::layer_descriptor get_descriptor() const; + + // mandatory: whether to bind the datasource or delay + void bind() const; + + private: + // recommended naming convention of datasource members: + // name_, type_, extent_, and desc_ + static const std::string name_; + int type_; + mutable mapnik::layer_descriptor desc_; + mutable mapnik::box2d extent_; +}; + + +#endif // FILE_DATASOURCE_HPP diff --git a/plugins/input/templates/helloworld/hello_featureset.cpp b/plugins/input/templates/helloworld/hello_featureset.cpp new file mode 100644 index 000000000..f3e53173a --- /dev/null +++ b/plugins/input/templates/helloworld/hello_featureset.cpp @@ -0,0 +1,46 @@ +#include "hello_featureset.hpp" + +hello_featureset::hello_featureset(mapnik::box2d const& box, std::string const& encoding) + : box_(box), + count_(0), + tr_(new mapnik::transcoder(encoding)) { } + +hello_featureset::~hello_featureset() { } + +mapnik::feature_ptr hello_featureset::next() +{ + if (!count_) + { + // create a new feature + mapnik::feature_ptr feature(new mapnik::Feature(count_)); + + // create an attribute pair of key:value + UnicodeString ustr = tr_->transcode("hello world!"); + boost::put(*feature,"key",ustr); + + // we need a geometry to display so just for fun here + // we take the center of the bbox that was used to query + // since we don't actually have any data to pull from... + mapnik::coord2d center = box_.center(); + + // create a new geometry + mapnik::geometry2d * point = new mapnik::point_impl; + + // we use path type geometries in Mapnik to fit nicely with AGG and Cairo + // here we stick an x,y pair into the geometry + point->move_to(center.x,center.y); + + // add the geometry to the feature + feature->add_geometry(point); + + // increment to count so that we only return on feature + ++count_; + + // return the feature! + return feature; + } + + // otherwise return an empty feature_ptr + return mapnik::feature_ptr(); +} + diff --git a/plugins/input/templates/helloworld/hello_featureset.hpp b/plugins/input/templates/helloworld/hello_featureset.hpp new file mode 100644 index 000000000..a8af6a208 --- /dev/null +++ b/plugins/input/templates/helloworld/hello_featureset.hpp @@ -0,0 +1,30 @@ +#ifndef HELLO_FEATURESET_HPP +#define HELLO_FEATURESET_HPP + +// mapnik +#include + +// boost +#include // needed for wrapping the transcoder + +// extend the mapnik::Featureset defined in include/mapnik/datasource.hpp +class hello_featureset : public mapnik::Featureset +{ + public: + // this constructor can have any arguments you need + hello_featureset(mapnik::box2d const& box, std::string const& encoding); + + // desctructor + virtual ~hello_featureset(); + + // mandatory: you must expose a next() method, called when rendering + mapnik::feature_ptr next(); + + private: + // members are up to you, but these are recommended + mapnik::box2d const& box_; + mutable int count_; + boost::scoped_ptr tr_; +}; + +#endif // HELLO_FEATURESET_HPP diff --git a/plugins/input/templates/helloworld/test.xml b/plugins/input/templates/helloworld/test.xml new file mode 100644 index 000000000..20f5bca14 --- /dev/null +++ b/plugins/input/templates/helloworld/test.xml @@ -0,0 +1,19 @@ + + + + + + style + + + hello + + + \ No newline at end of file