add a directory for plugin templates - and add a first "hello world" with basic docs"

This commit is contained in:
Dane Springmeyer 2010-10-24 08:04:16 +00:00
parent 9281e6dcd8
commit ea39e6f69e
10 changed files with 387 additions and 1 deletions

View file

@ -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')

View file

@ -38,7 +38,8 @@
#include <map>
#include <string>
namespace mapnik {
namespace mapnik {
typedef MAPNIK_DECL boost::shared_ptr<Feature> feature_ptr;
struct MAPNIK_DECL Featureset

View 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.

View 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')

View 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)

View 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();
}

View 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

View 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();
}

View 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

View 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>