add a directory for plugin templates - and add a first "hello world" with basic docs"
This commit is contained in:
parent
9281e6dcd8
commit
ea39e6f69e
10 changed files with 387 additions and 1 deletions
|
@ -326,6 +326,7 @@ opts.AddVariables(
|
||||||
BoolVariable('DEMO', 'Compile demo c++ application', 'False'),
|
BoolVariable('DEMO', 'Compile demo c++ application', 'False'),
|
||||||
BoolVariable('PGSQL2SQLITE', 'Compile and install a utility to convert postgres tables to sqlite', '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('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
|
# variables to pickle after successful configure step
|
||||||
|
@ -1318,3 +1319,8 @@ if not HELP_REQUESTED:
|
||||||
|
|
||||||
# write the viewer.ini file
|
# write the viewer.ini file
|
||||||
SConscript('demo/viewer/SConscript')
|
SConscript('demo/viewer/SConscript')
|
||||||
|
|
||||||
|
# if requested, build the sample input plugins
|
||||||
|
if env['SAMPLE_INPUT_PLUGINS']:
|
||||||
|
SConscript('plugins/input/templates/helloworld/build.py')
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,8 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace mapnik {
|
namespace mapnik {
|
||||||
|
|
||||||
typedef MAPNIK_DECL boost::shared_ptr<Feature> feature_ptr;
|
typedef MAPNIK_DECL boost::shared_ptr<Feature> feature_ptr;
|
||||||
|
|
||||||
struct MAPNIK_DECL Featureset
|
struct MAPNIK_DECL Featureset
|
||||||
|
|
14
plugins/input/templates/README
Normal file
14
plugins/input/templates/README
Normal file
|
@ -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.
|
51
plugins/input/templates/helloworld/README
Normal file
51
plugins/input/templates/helloworld/README
Normal file
|
@ -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:
|
||||||
|
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Map srs="+init=epsg:4326" background-color="white">
|
||||||
|
<Style name="style">
|
||||||
|
<Rule>
|
||||||
|
<PointSymbolizer />
|
||||||
|
<TextSymbolizer name="[key]" face_name="DejaVu Sans Book" size="10" dx="5" dy="5"/>
|
||||||
|
</Rule>
|
||||||
|
</Style>
|
||||||
|
<Layer name="test" srs="+init=epsg:4326">
|
||||||
|
<StyleName>style</StyleName>
|
||||||
|
<Datasource>
|
||||||
|
<Parameter name="type">hello</Parameter>
|
||||||
|
</Datasource>
|
||||||
|
</Layer>
|
||||||
|
</Map>
|
||||||
|
|
||||||
|
|
||||||
|
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')
|
82
plugins/input/templates/helloworld/build.py
Normal file
82
plugins/input/templates/helloworld/build.py
Normal file
|
@ -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)
|
86
plugins/input/templates/helloworld/hello_datasource.cpp
Normal file
86
plugins/input/templates/helloworld/hello_datasource.cpp
Normal file
|
@ -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<std::string>("type"), *params_.get<std::string>("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<double> 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();
|
||||||
|
}
|
51
plugins/input/templates/helloworld/hello_datasource.hpp
Normal file
51
plugins/input/templates/helloworld/hello_datasource.hpp
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
#ifndef FILE_DATASOURCE_HPP
|
||||||
|
#define FILE_DATASOURCE_HPP
|
||||||
|
|
||||||
|
// mapnik
|
||||||
|
#include <mapnik/datasource.hpp>
|
||||||
|
|
||||||
|
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<double> 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<double> extent_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // FILE_DATASOURCE_HPP
|
46
plugins/input/templates/helloworld/hello_featureset.cpp
Normal file
46
plugins/input/templates/helloworld/hello_featureset.cpp
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
#include "hello_featureset.hpp"
|
||||||
|
|
||||||
|
hello_featureset::hello_featureset(mapnik::box2d<double> 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();
|
||||||
|
}
|
||||||
|
|
30
plugins/input/templates/helloworld/hello_featureset.hpp
Normal file
30
plugins/input/templates/helloworld/hello_featureset.hpp
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#ifndef HELLO_FEATURESET_HPP
|
||||||
|
#define HELLO_FEATURESET_HPP
|
||||||
|
|
||||||
|
// mapnik
|
||||||
|
#include <mapnik/datasource.hpp>
|
||||||
|
|
||||||
|
// boost
|
||||||
|
#include <boost/scoped_ptr.hpp> // 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<double> 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<double> const& box_;
|
||||||
|
mutable int count_;
|
||||||
|
boost::scoped_ptr<mapnik::transcoder> tr_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // HELLO_FEATURESET_HPP
|
19
plugins/input/templates/helloworld/test.xml
Normal file
19
plugins/input/templates/helloworld/test.xml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Map srs="+init=epsg:4326" background-color="white">
|
||||||
|
<Style name="style">
|
||||||
|
<Rule>
|
||||||
|
<PointSymbolizer />
|
||||||
|
<!-- the hello world sample hardcodes 'key' as the hypothetical field name -->
|
||||||
|
<TextSymbolizer name="[key]" face_name="DejaVu Sans Book" size="10" dx="5" dy="5"/>
|
||||||
|
</Rule>
|
||||||
|
</Style>
|
||||||
|
<!-- this example only works in EPSG:4326 -->
|
||||||
|
<Layer name="test" srs="+init=epsg:4326">
|
||||||
|
<StyleName>style</StyleName>
|
||||||
|
<Datasource>
|
||||||
|
<!-- here we create a 'hello' type datasource which simply
|
||||||
|
displays a point in the middle of the world's boundin box -->
|
||||||
|
<Parameter name="type">hello</Parameter>
|
||||||
|
</Datasource>
|
||||||
|
</Layer>
|
||||||
|
</Map>
|
Loading…
Add table
Reference in a new issue