remove python plugin from core

- now at https://github.com/mapnik/non-core-plugins
 - refs #2773, #1875, #1337,
This commit is contained in:
Dane Springmeyer 2015-04-24 08:51:56 +02:00
parent d310806485
commit 686731cade
14 changed files with 3 additions and 1053 deletions

View file

@ -126,8 +126,7 @@ PLUGINS = { # plugins with external dependencies
'csv': {'default':True,'path':None,'inc':None,'lib':None,'lang':'C++'}, 'csv': {'default':True,'path':None,'inc':None,'lib':None,'lang':'C++'},
'raster': {'default':True,'path':None,'inc':None,'lib':None,'lang':'C++'}, 'raster': {'default':True,'path':None,'inc':None,'lib':None,'lang':'C++'},
'geojson': {'default':True,'path':None,'inc':None,'lib':None,'lang':'C++'}, 'geojson': {'default':True,'path':None,'inc':None,'lib':None,'lang':'C++'},
'topojson':{'default':True,'path':None,'inc':None,'lib':None,'lang':'C++'}, 'topojson':{'default':True,'path':None,'inc':None,'lib':None,'lang':'C++'}
'python': {'default':False,'path':None,'inc':None,'lib':None,'lang':'C++'},
} }
@ -1647,7 +1646,7 @@ if not preconfigured:
env['SKIPPED_DEPS'].append('cairo') env['SKIPPED_DEPS'].append('cairo')
env['HAS_CAIRO'] = False env['HAS_CAIRO'] = False
if 'python' in env['BINDINGS'] or 'python' in env['REQUESTED_PLUGINS']: if 'python' in env['BINDINGS']:
if not os.access(env['PYTHON'], os.X_OK): if not os.access(env['PYTHON'], os.X_OK):
color_print(1,"Cannot run python interpreter at '%s', make sure that you have the permissions to execute it." % env['PYTHON']) color_print(1,"Cannot run python interpreter at '%s', make sure that you have the permissions to execute it." % env['PYTHON'])
Exit(1) Exit(1)
@ -1865,7 +1864,7 @@ if not preconfigured:
if env['DEBUG_UNDEFINED']: if env['DEBUG_UNDEFINED']:
env.Append(CXXFLAGS = '-fsanitize=undefined-trap -fsanitize-undefined-trap-on-error -ftrapv -fwrapv') env.Append(CXXFLAGS = '-fsanitize=undefined-trap -fsanitize-undefined-trap-on-error -ftrapv -fwrapv')
if 'python' in env['BINDINGS'] or 'python' in env['REQUESTED_PLUGINS']: if 'python' in env['BINDINGS']:
majver, minver = env['PYTHON_VERSION'].split('.') majver, minver = env['PYTHON_VERSION'].split('.')
# we don't want the includes it in the main environment... # we don't want the includes it in the main environment...
# as they are later set in the python build.py # as they are later set in the python build.py

View file

@ -1,241 +0,0 @@
# Python plugin
This plugin allows you to write data sources in the Python programming language.
This is useful if you want to rapidly prototype a plugin, perform some custom
manipulation on data or if you want to bind mapnik to a datasource which is most
conveniently accessed through Python.
The plugin may be used from the existing mapnik Python bindings or it can embed
the Python interpreter directly allowing it to be used from C++, XML or even
JavaScript.
## Rationale
Mapnik already has excellent Python bindings but they only directly support
calling *into* mapnik *from* Python. This forces mapnik and its input plugins to
be the lowest layer of the stack. The role of this plugin is to allow mapnik to
call *into* Python itself. This allows mapnik to sit as rendering middleware
between a custom Python frontend and a custom Python datasource. This increases
the utility of mapnik as a component in a larger system.
There already exists MemoryDatasource which can be used to dynamically create
geometry in Python. It suffers from the problem that it does not allow
generating only the geometry which is seen by a particular query. Similarly the
entire geometry must exist in memory before rendering can progress. By using a
custom iterator object or by using generator expressions this plugin allows
geometry to be created on demand and to be destroyed after use. This can have a
great impact on memory efficiency. Since geometry is generated on-demand as
rendering progresses there can be arbitrarily complex 'cleverness' optimising
the geometry generated for a particular query. Obvious examples of this would
be generating only geometry within the query bounding box and generating
geometry with an appropriate level of detail for the output resolution.
## Initialization
Only the `factory` parameter is required. This is of the form
`[module:]callable`. If `module` is present then `module` will be imported and
its attribute named `callable` will be used as a factory callable. If `module`
is omitted, then `__main__` is used. Any other parameter aside from `factory` or
`type` will be passed directly to the callable as keyword arguments. Note that
these will always be passed as strings even if the parameter can be parsed as an
integer of floating point value.
The callable should return an object with the following required attributes:
* `envelope` - a 4-tuple giving the (minx, miny, maxx, maxy) extent of the
datasource;
* `data_type` - a `mapnik.DataType` instance giving the type of data stored in
this datasource. This will usually be one of `mapnik.DataType.Vector` or
`mapnik.DataType.Raster`.
The following attributes are optional:
* `geometry_type` - if the dataset is a vector dataset, this is an instance of
`mapnik.DataGeometryType` giving the type of geometry returned by the
datasource.
The following methods must be present:
* `features(query)` - takes a single argument which is an instance of
`mapnik.Query` and returns an iterable of `mapnik.Feature` instances for that
query.
* `features_at_point(point)` - almost never used. Takes a single argument which
is an instance of `mapnik.Point` (I think) and returns an iterable of
features associated with that point.
## Convenience classes
The standard `mapnik` module provides a convenience class called
`mapnik.PythonDatasource` which has default implementations for the required
methods and accepts the geometry type, data type and envelope as constructor
arguments. It also provides some convenience class methods which take care of
constructing features for you:
* `mapnik.PythonDatasource.wkb_features` - constructs features from
well-known-binary (WKB) format geometry. Takes two keyword arguments: `keys`
which is a sequence of keys associated with each feature and `features` which
is a sequence of pairs. The first element in each pair is the WKB
representation of the feature and the second element is a dictionary mapping
keys to values.
# Caveats
* If used directly from C++, `Py_Initialize()` must have been called before the
plugin is loaded to initialise the interpreter correctly.
* When inside the interpreter the global interpreter lock is held each time a
feature is fetched and so multi-threaded rendering performance may suffer. You
can mitigate this by making sure that the feature iterator yields its value as
quickly as possible, potentially from an in-memory buffer filled fom another
process over IPC.
# Examples
In XML:
```xml
<?xml version="1.0" encoding="utf-8"?>
<Map srs="+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs" background-color="white">
<Style name="style">
<Rule>
<PointSymbolizer />
<TextSymbolizer name="[label]" face_name="DejaVu Sans Book" size="10" dx="5" dy="5"/>
</Rule>
</Style>
<Layer name="test" srs="+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs">
<StyleName>style</StyleName>
<Datasource>
<Parameter name="type">python</Parameter>
<Parameter name="factory">test:TestDatasource</Parameter>
</Datasource>
</Layer>
</Map>
```
In Python using the shapely geometry library:
```python
import mapnik
from shapely.geometry import *
class TestDatasource(mapnik.PythonDatasource):
def __init__(self):
super(TestDatasource, self).__init__()
def features(self, query):
return mapnik.PythonDatasource.wkb_features(
keys = ('label',),
features = (
( Point(5,6).wkb, { 'label': 'foo-bar'} ),
( Point(100,60).wkb, { 'label': 'buzz-quux'} ),
)
)
if __name__ == '__main__':
m = mapnik.Map(1280,1024)
m.background = mapnik.Color('white')
s = mapnik.Style()
r = mapnik.Rule()
r.symbols.append(mapnik.PointSymbolizer())
t = mapnik.TextSymbolizer(mapnik.Expression("[label]"),"DejaVu Sans Book",10,mapnik.Color('black'))
t.displacement = (5,5)
r.symbols.append(t)
s.rules.append(r)
m.append_style('point_style',s)
ds = mapnik.Python(factory='TestDatasource')
layer = mapnik.Layer('python')
layer.datasource = ds
layer.styles.append('point_style')
m.layers.append(layer)
m.zoom_all()
mapnik.render_to_file(m,'map.png', 'png')
```
A more complex Python example which makes use of iterators to generate geometry
dynamically:
```python
"""A more complex example which renders an infinite series of concentric
circles centred on a point.
The circles are represented by a Python iterator which will yield only the
circles which intersect the query's bounding box. The advantage of this
approach over a MemoryDatasource is that a) only those circles which intersect
the viewport are actually generated and b) only the memory for the largest
circle need be available since each circle is created on demand and destroyed
when finished with.
"""
import math
import mapnik
from shapely.geometry import *
def box2d_to_shapely(box):
import shapely.geometry
return shapely.geometry.box(box.minx, box.miny, box.maxx, box.maxy)
class ConcentricCircles(object):
def __init__(self, centre, bounds, step=1):
self.centre = centre
self.bounds = bounds
self.step = step
class Iterator(object):
def __init__(self, container):
self.container = container
centre = self.container.centre
bounds = self.container.bounds
step = self.container.step
if centre.within(bounds):
self.radius = 0
else:
self.radius = math.ceil(centre.distance(bounds) / float(step)) * step
def next(self):
circle = self.container.centre.buffer(self.radius)
self.radius += self.container.step
# has the circle grown so large that the boundary is entirely within it?
if circle.contains(self.container.bounds):
raise StopIteration()
return ( circle.wkb, { } )
def __iter__(self):
return ConcentricCircles.Iterator(self)
class TestDatasource(mapnik.PythonDatasource):
def __init__(self):
super(TestDatasource, self).__init__(geometry_type=mapnik.DataGeometryType.Polygon)
def features(self, query):
# Get the query bounding-box as a shapely bounding box
bounding_box = box2d_to_shapely(query.bbox)
centre = Point(-20, 0)
return mapnik.PythonDatasource.wkb_features(
keys = (),
features = ConcentricCircles(centre, bounding_box, 0.5)
)
if __name__ == '__main__':
m = mapnik.Map(640, 320)
m.background = mapnik.Color('white')
s = mapnik.Style()
r = mapnik.Rule()
r.symbols.append(mapnik.LineSymbolizer())
s.rules.append(r)
m.append_style('point_style',s)
ds = mapnik.Python(factory='TestDatasource')
layer = mapnik.Layer('python')
layer.datasource = ds
layer.styles.append('point_style')
m.layers.append(layer)
box = mapnik.Box2d(-60, -60, 0, -30)
m.zoom_to_box(box)
mapnik.render_to_file(m,'map.png', 'png')
```

View file

@ -1,97 +0,0 @@
#
# This file is part of Mapnik (c++ mapping toolkit)
#
# Copyright (C) 2013 Artem Pavlenko
#
# Mapnik is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
#
import os
import copy
Import ('plugin_base')
Import ('env')
PLUGIN_NAME = 'python'
plugin_env = plugin_base.Clone()
plugin_sources = Split(
"""
%(PLUGIN_NAME)s_datasource.cpp
%(PLUGIN_NAME)s_featureset.cpp
%(PLUGIN_NAME)s_utils.cpp
""" % locals()
)
# Link Library to Dependencies
libraries = []
libraries.append('boost_system%s' % env['BOOST_APPEND'])
libraries.append(env['BOOST_PYTHON_LIB'])
libraries.append(env['ICU_LIB_NAME'])
python_cpppath = env['PYTHON_INCLUDES']
allcpp_paths = copy.copy(env['CPPPATH'])
allcpp_paths.extend(python_cpppath)
# NOTE: explicit linking to libpython is uneeded on most linux version if the
# python plugin is used by a app in python using mapnik's python bindings
# we explicitly link to libpython here so that this plugin
# can be used from a pure C++ calling application or a different binding language
if env['PLATFORM'] == 'Darwin' and env['FRAMEWORK_PYTHON']:
if env['FRAMEWORK_SEARCH_PATH']:
python_link_flag = '-F%s -framework Python -Z' % env['FRAMEWORK_SEARCH_PATH']
else:
link_prefix = env['PYTHON_SYS_PREFIX']
if '.framework' in link_prefix:
python_link_flag = '-F%s -framework Python -Z' % os.path.dirname(link_prefix.split('.')[0])
elif '/System' in link_prefix:
python_link_flag = '-F/System/Library/Frameworks/ -framework Python -Z'
else:
python_link_flag = '-F/ -framework Python'
else:
# on linux the linkflags end up to early in the compile flags to work correctly
python_link_flag = '-L%s' % env['PYTHON_SYS_PREFIX'] + os.path.sep + env['LIBDIR_SCHEMA']
# so instead add to libraries
libraries.append('python%s' % env['PYTHON_VERSION'])
plugin_env.Append(LINKFLAGS=python_link_flag)
if env['PLUGIN_LINKING'] == 'shared':
libraries.append(env['MAPNIK_NAME'])
TARGET = plugin_env.SharedLibrary('../%s' % PLUGIN_NAME,
SHLIBPREFIX='',
SHLIBSUFFIX='.input',
source=plugin_sources,
CPPPATH=allcpp_paths,
LIBS=libraries)
# if the plugin links to libmapnik ensure it is built first
Depends(TARGET, env.subst('../../../src/%s' % env['MAPNIK_LIB_NAME']))
# 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(env['MAPNIK_INPUT_PLUGINS_DEST'], TARGET)
env.Alias('install', env['MAPNIK_INPUT_PLUGINS_DEST'])
plugin_obj = {
'LIBS': libraries,
'SOURCES': plugin_sources,
'CPPPATH': python_cpppath,
'LINKFLAGS': python_link_flag.replace('-Z','').split(' '),
}
Return('plugin_obj')

View file

@ -1,83 +0,0 @@
"""A more complex example which renders an infinite series of concentric
circles centred on a point.
The circles are represented by a Python iterator which will yield only the
circles which intersect the query's bounding box. The advantage of this
approach over a MemoryDatasource is that a) only those circles which intersect
the viewport are actually generated and b) only the memory for the largest
circle need be available since each circle is created on demand and destroyed
when finished with.
"""
import math
import mapnik
from shapely.geometry import *
def box2d_to_shapely(box):
import shapely.geometry
return shapely.geometry.box(box.minx, box.miny, box.maxx, box.maxy)
class ConcentricCircles(object):
def __init__(self, centre, bounds, step=1):
self.centre = centre
self.bounds = bounds
self.step = step
class Iterator(object):
def __init__(self, container):
self.container = container
centre = self.container.centre
bounds = self.container.bounds
step = self.container.step
if centre.within(bounds):
self.radius = 0
else:
self.radius = math.ceil(centre.distance(bounds) / float(step)) * step
def next(self):
circle = self.container.centre.buffer(self.radius)
self.radius += self.container.step
# has the circle grown so large that the boundary is entirely within it?
if circle.contains(self.container.bounds):
raise StopIteration()
return ( circle.wkb, { } )
def __iter__(self):
return ConcentricCircles.Iterator(self)
class TestDatasource(mapnik.PythonDatasource):
def __init__(self):
super(TestDatasource, self).__init__(
geometry_type=mapnik.DataGeometryType.Polygon
)
def features(self, query):
# Get the query bounding-box as a shapely bounding box
bounding_box = box2d_to_shapely(query.bbox)
centre = Point(-20, 0)
return mapnik.PythonDatasource.wkb_features(
keys = (),
features = ConcentricCircles(centre, bounding_box, 0.5)
)
if __name__ == '__main__':
m = mapnik.Map(640, 320)
m.background = mapnik.Color('white')
s = mapnik.Style()
r = mapnik.Rule()
r.symbols.append(mapnik.LineSymbolizer())
s.rules.append(r)
m.append_style('point_style',s)
ds = mapnik.Python(factory='TestDatasource')
layer = mapnik.Layer('python')
layer.datasource = ds
layer.styles.append('point_style')
m.layers.append(layer)
box = mapnik.Box2d(-60, -60, 0, -30)
m.zoom_to_box(box)
mapnik.render_to_file(m,'map.png', 'png')

View file

@ -1,34 +0,0 @@
import mapnik
from shapely.geometry import *
class TestDatasource(mapnik.PythonDatasource):
def __init__(self):
super(TestDatasource, self).__init__()
def features(self, query):
return mapnik.PythonDatasource.wkb_features(
keys = ('label',),
features = (
( Point(5,6).wkb, { 'label': 'foo-bar'} ),
( Point(100,60).wkb, { 'label': 'buzz-quux'} ),
)
)
if __name__ == '__main__':
m = mapnik.Map(1280,1024)
m.background = mapnik.Color('white')
s = mapnik.Style()
r = mapnik.Rule()
r.symbols.append(mapnik.PointSymbolizer())
t = mapnik.TextSymbolizer(mapnik.Expression("[label]"),"DejaVu Sans Book",10,mapnik.Color('black'))
t.displacement = (5,5)
r.symbols.append(t)
s.rules.append(r)
m.append_style('point_style',s)
ds = mapnik.Python(factory='TestDatasource')
layer = mapnik.Layer('python')
layer.datasource = ds
layer.styles.append('point_style')
m.layers.append(layer)
m.zoom_all()
mapnik.render_to_file(m,'map.png', 'png')

View file

@ -1,8 +0,0 @@
import mapnik
stylesheet = 'simple_xml.xml'
image = 'simple_xml.png'
m = mapnik.Map(600, 300)
mapnik.load_map(m, stylesheet)
m.zoom_all()
mapnik.render_to_file(m, image)
print "rendered image to '%s'" % image

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Map srs="+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs" background-color="white">
<Style name="style">
<Rule>
<PointSymbolizer />
<TextSymbolizer name="[label]" face-name="DejaVu Sans Book" size="10" dx="5" dy="5"/>
</Rule>
</Style>
<Layer name="test" srs="+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs">
<StyleName>style</StyleName>
<Datasource>
<Parameter name="type">python</Parameter>
<Parameter name="factory">test:TestDatasource</Parameter>
</Datasource>
</Layer>
</Map>

View file

@ -1,238 +0,0 @@
// file plugin
#include "python_datasource.hpp"
#include "python_featureset.hpp"
// stl
#include <string>
#include <vector>
// boost
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-local-typedef"
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#include <boost/python.hpp>
#include <boost/python/stl_iterator.hpp>
#include <boost/algorithm/string.hpp>
#pragma GCC diagnostic pop
#include "python_utils.hpp"
using mapnik::datasource;
using mapnik::parameters;
DATASOURCE_PLUGIN(python_datasource)
python_datasource::python_datasource(parameters const& params)
: datasource(params),
desc_(python_datasource::name(), *params.get<std::string>("encoding","utf-8")),
factory_(*params.get<std::string>("factory", ""))
{
// extract any remaining parameters as keyword args for the factory
for (const mapnik::parameters::value_type& kv : params)
{
if((kv.first != "type") && (kv.first != "factory"))
{
kwargs_.emplace(kv.first, *params.get<std::string>(kv.first));
}
}
// The following methods call into the Python interpreter and hence require, unfortunately, that the GIL be held.
using namespace boost;
if (factory_.empty())
{
throw mapnik::datasource_exception("Python: 'factory' option must be defined");
}
try
{
// split factory at ':' to parse out module and callable
std::vector<std::string> factory_split;
split(factory_split, factory_, is_any_of(":"));
if ((factory_split.size() < 1) || (factory_split.size() > 2))
{
throw mapnik::datasource_exception(
std::string("python: factory string must be of the form '[module:]callable' when parsing \"")
+ factory_ + '"');
}
// extract the module and the callable
boost::python::str module_name("__main__"), callable_name;
if (factory_split.size() == 1)
{
callable_name = boost::python::str(factory_split[0]);
}
else
{
module_name = boost::python::str(factory_split[0]);
callable_name = boost::python::str(factory_split[1]);
}
ensure_gil lock;
// import the main module from Python (in case we're embedding the
// interpreter directly) and also import the callable.
boost::python::object main_module = boost::python::import("__main__");
boost::python::object callable_module = boost::python::import(module_name);
boost::python::object callable = callable_module.attr(callable_name);
// prepare the arguments
boost::python::dict kwargs;
using kv_type = std::map<std::string, std::string>::value_type;
for (kv_type const& kv : kwargs_)
{
kwargs[boost::python::str(kv.first)] = boost::python::str(kv.second);
}
// get our wrapped data source
datasource_ = callable(*boost::python::make_tuple(), **kwargs);
}
catch ( boost::python::error_already_set )
{
throw mapnik::datasource_exception(extractException());
}
}
python_datasource::~python_datasource() { }
// This name must match the plugin filename, eg 'python.input'
const char* python_datasource::name_="python";
const char* python_datasource::name()
{
return name_;
}
mapnik::layer_descriptor python_datasource::get_descriptor() const
{
return desc_;
}
mapnik::datasource::datasource_t python_datasource::type() const
{
try
{
ensure_gil lock;
boost::python::object data_type = datasource_.attr("data_type");
long data_type_integer = boost::python::extract<long>(data_type);
return mapnik::datasource::datasource_t(data_type_integer);
}
catch ( boost::python::error_already_set )
{
throw mapnik::datasource_exception(extractException());
}
}
mapnik::box2d<double> python_datasource::envelope() const
{
mapnik::box2d<double> box;
try
{
ensure_gil lock;
if (!PyObject_HasAttrString(datasource_.ptr(), "envelope"))
{
throw mapnik::datasource_exception("Python: could not access envelope property");
}
else
{
boost::python::object py_envelope = datasource_.attr("envelope");
if (py_envelope.ptr() == boost::python::object().ptr())
{
throw mapnik::datasource_exception("Python: could not access envelope property");
}
else
{
boost::python::extract<double> ex(py_envelope.attr("minx"));
if (!ex.check()) throw mapnik::datasource_exception("Python: could not convert envelope.minx");
box.set_minx(ex());
boost::python::extract<double> ex1(py_envelope.attr("miny"));
if (!ex1.check()) throw mapnik::datasource_exception("Python: could not convert envelope.miny");
box.set_miny(ex1());
boost::python::extract<double> ex2(py_envelope.attr("maxx"));
if (!ex2.check()) throw mapnik::datasource_exception("Python: could not convert envelope.maxx");
box.set_maxx(ex2());
boost::python::extract<double> ex3(py_envelope.attr("maxy"));
if (!ex3.check()) throw mapnik::datasource_exception("Python: could not convert envelope.maxy");
box.set_maxy(ex3());
}
}
}
catch ( boost::python::error_already_set )
{
throw mapnik::datasource_exception(extractException());
}
return box;
}
boost::optional<mapnik::datasource_geometry_t> python_datasource::get_geometry_type() const
{
using return_type = boost::optional<mapnik::datasource_geometry_t>;
try
{
ensure_gil lock;
// if the datasource object has no geometry_type attribute, return a 'none' value
if (!PyObject_HasAttrString(datasource_.ptr(), "geometry_type"))
{
return return_type();
}
boost::python::object py_geometry_type = datasource_.attr("geometry_type");
// if the attribute value is 'None', return a 'none' value
if (py_geometry_type.ptr() == boost::python::object().ptr())
{
return return_type();
}
long geom_type_integer = boost::python::extract<long>(py_geometry_type);
return mapnik::datasource_geometry_t(geom_type_integer);
}
catch ( boost::python::error_already_set )
{
throw mapnik::datasource_exception(extractException());
}
}
mapnik::featureset_ptr python_datasource::features(mapnik::query const& q) const
{
try
{
// if the query box intersects our world extent then query for features
if (envelope().intersects(q.get_bbox()))
{
ensure_gil lock;
boost::python::object features(datasource_.attr("features")(q));
// if 'None' was returned, return an empty feature set
if(features.ptr() == boost::python::object().ptr())
{
return mapnik::featureset_ptr();
}
return std::make_shared<python_featureset>(features);
}
// otherwise return an empty featureset pointer
return mapnik::featureset_ptr();
}
catch ( boost::python::error_already_set )
{
throw mapnik::datasource_exception(extractException());
}
}
mapnik::featureset_ptr python_datasource::features_at_point(mapnik::coord2d const& pt, double tol) const
{
try
{
ensure_gil lock;
boost::python::object features(datasource_.attr("features_at_point")(pt));
// if we returned none, return an empty set
if(features.ptr() == boost::python::object().ptr())
{
return mapnik::featureset_ptr();
}
// otherwise, return a feature set which can iterate over the iterator
return std::make_shared<python_featureset>(features);
}
catch ( boost::python::error_already_set )
{
throw mapnik::datasource_exception(extractException());
}
}

View file

@ -1,58 +0,0 @@
#ifndef PYTHON_DATASOURCE_HPP
#define PYTHON_DATASOURCE_HPP
// mapnik
#include <mapnik/datasource.hpp>
// boost
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-local-typedef"
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#include <boost/python.hpp>
#pragma GCC diagnostic pop
class python_datasource : public mapnik::datasource
{
public:
// constructor
// arguments must not change
python_datasource(mapnik::parameters const& params);
// destructor
virtual ~python_datasource ();
// mandatory: type of the plugin, used to match at runtime
mapnik::datasource::datasource_t type() const;
// mandatory: name of the plugin
static const char* 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, double tol = 0) 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: optionally return the overal geometry type of the datasource
boost::optional<mapnik::datasource_geometry_t> get_geometry_type() const;
// mandatory: return the layer descriptor
mapnik::layer_descriptor get_descriptor() const;
private:
static const char* name_;
mapnik::layer_descriptor desc_;
const std::string factory_;
std::map<std::string, std::string> kwargs_;
boost::python::object datasource_;
};
#endif // PYTHON_DATASOURCE_HPP

View file

@ -1,30 +0,0 @@
#include "python_featureset.hpp"
#include "python_utils.hpp"
// boost
#include <boost/python.hpp>
python_featureset::python_featureset(boost::python::object iterator)
{
ensure_gil lock;
begin_ = boost::python::stl_input_iterator<mapnik::feature_ptr>(iterator);
}
python_featureset::~python_featureset()
{
ensure_gil lock;
begin_ = end_;
}
mapnik::feature_ptr python_featureset::next()
{
// checking to see if we've reached the end does not require the GIL.
if(begin_ == end_)
return mapnik::feature_ptr();
// getting the next feature might call into the interpreter and so the GIL must be held.
ensure_gil lock;
return *(begin_++);
}

View file

@ -1,38 +0,0 @@
#ifndef PYTHON_FEATURESET_HPP
#define PYTHON_FEATURESET_HPP
// mapnik
#include <mapnik/config.hpp>
#include <mapnik/feature.hpp>
// boost
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-local-typedef"
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#include <boost/python.hpp>
#include <boost/python/stl_iterator.hpp>
#pragma GCC diagnostic pop
// mapnik
#include <mapnik/datasource.hpp>
class python_featureset : public mapnik::Featureset
{
public:
// this constructor can have any arguments you need
python_featureset(boost::python::object iterator);
// desctructor
virtual ~python_featureset();
// mandatory: you must expose a next() method, called when rendering
mapnik::feature_ptr next();
private:
using feature_iter = boost::python::stl_input_iterator<mapnik::feature_ptr>;
feature_iter begin_, end_;
};
#endif // PYTHON_FEATURESET_HPP

View file

@ -1,23 +0,0 @@
#include "python_utils.hpp"
std::string extractException()
{
using namespace boost::python;
PyObject *exc,*val,*tb;
PyErr_Fetch(&exc,&val,&tb);
PyErr_NormalizeException(&exc,&val,&tb);
handle<> hexc(exc),hval(allow_null(val)),htb(allow_null(tb));
if(!hval)
{
return extract<std::string>(str(hexc));
}
else
{
object traceback(import("traceback"));
object format_exception(traceback.attr("format_exception"));
object formatted_list(format_exception(hexc,hval,htb));
object formatted(str("").join(formatted_list));
return extract<std::string>(formatted);
}
}

View file

@ -1,23 +0,0 @@
#ifndef PYTHON_UTILS_HPP
#define PYTHON_UTILS_HPP
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-local-typedef"
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#include <boost/python.hpp>
#pragma GCC diagnostic pop
// Use RAII to acquire and release the GIL as needed.
class ensure_gil
{
public:
ensure_gil() : gil_state_(PyGILState_Ensure()) {}
~ensure_gil() { PyGILState_Release( gil_state_ ); }
protected:
PyGILState_STATE gil_state_;
};
std::string extractException();
#endif // PYTHON_UTILS_HPP

View file

@ -1,160 +0,0 @@
# #!/usr/bin/env python
# # -*- coding: utf-8 -*-
# import os
# import math
# import mapnik
# import sys
# from utilities import execution_path, run_all
# from nose.tools import *
# def setup():
# # All of the paths used are relative, if we run the tests
# # from another directory we need to chdir()
# os.chdir(execution_path('.'))
# class PointDatasource(mapnik.PythonDatasource):
# def __init__(self):
# super(PointDatasource, self).__init__(
# geometry_type = mapnik.DataGeometryType.Point,
# envelope = mapnik.Box2d(0,-10,100,110),
# data_type = mapnik.DataType.Vector
# )
# def features(self, query):
# return mapnik.PythonDatasource.wkt_features(
# keys = ('label',),
# features = (
# ( 'POINT (5 6)', { 'label': 'foo-bar'} ),
# ( 'POINT (60 50)', { 'label': 'buzz-quux'} ),
# )
# )
# class ConcentricCircles(object):
# def __init__(self, centre, bounds, step=1):
# self.centre = centre
# self.bounds = bounds
# self.step = step
# class Iterator(object):
# def __init__(self, container):
# self.container = container
# centre = self.container.centre
# bounds = self.container.bounds
# step = self.container.step
# self.radius = step
# def next(self):
# points = []
# for alpha in xrange(0, 361, 5):
# x = math.sin(math.radians(alpha)) * self.radius + self.container.centre[0]
# y = math.cos(math.radians(alpha)) * self.radius + self.container.centre[1]
# points.append('%s %s' % (x,y))
# circle = 'POLYGON ((' + ','.join(points) + '))'
# # has the circle grown so large that the boundary is entirely within it?
# tl = (self.container.bounds.maxx, self.container.bounds.maxy)
# tr = (self.container.bounds.maxx, self.container.bounds.maxy)
# bl = (self.container.bounds.minx, self.container.bounds.miny)
# br = (self.container.bounds.minx, self.container.bounds.miny)
# def within_circle(p):
# delta_x = p[0] - self.container.centre[0]
# delta_y = p[0] - self.container.centre[0]
# return delta_x*delta_x + delta_y*delta_y < self.radius*self.radius
# if all(within_circle(p) for p in (tl,tr,bl,br)):
# raise StopIteration()
# self.radius += self.container.step
# return ( circle, { } )
# def __iter__(self):
# return ConcentricCircles.Iterator(self)
# class CirclesDatasource(mapnik.PythonDatasource):
# def __init__(self, centre_x=-20, centre_y=0, step=10):
# super(CirclesDatasource, self).__init__(
# geometry_type = mapnik.DataGeometryType.Polygon,
# envelope = mapnik.Box2d(-180, -90, 180, 90),
# data_type = mapnik.DataType.Vector
# )
# # note that the plugin loader will set all arguments to strings and will not try to parse them
# centre_x = int(centre_x)
# centre_y = int(centre_y)
# step = int(step)
# self.centre_x = centre_x
# self.centre_y = centre_y
# self.step = step
# def features(self, query):
# centre = (self.centre_x, self.centre_y)
# return mapnik.PythonDatasource.wkt_features(
# keys = (),
# features = ConcentricCircles(centre, query.bbox, self.step)
# )
# if 'python' in mapnik.DatasourceCache.plugin_names():
# # make sure we can load from ourself as a module
# sys.path.append(execution_path('.'))
# def test_python_point_init():
# ds = mapnik.Python(factory='python_plugin_test:PointDatasource')
# e = ds.envelope()
# assert_almost_equal(e.minx, 0, places=7)
# assert_almost_equal(e.miny, -10, places=7)
# assert_almost_equal(e.maxx, 100, places=7)
# assert_almost_equal(e.maxy, 110, places=7)
# def test_python_circle_init():
# ds = mapnik.Python(factory='python_plugin_test:CirclesDatasource')
# e = ds.envelope()
# assert_almost_equal(e.minx, -180, places=7)
# assert_almost_equal(e.miny, -90, places=7)
# assert_almost_equal(e.maxx, 180, places=7)
# assert_almost_equal(e.maxy, 90, places=7)
# def test_python_circle_init_with_args():
# ds = mapnik.Python(factory='python_plugin_test:CirclesDatasource', centre_x=40, centre_y=7)
# e = ds.envelope()
# assert_almost_equal(e.minx, -180, places=7)
# assert_almost_equal(e.miny, -90, places=7)
# assert_almost_equal(e.maxx, 180, places=7)
# assert_almost_equal(e.maxy, 90, places=7)
# def test_python_point_rendering():
# m = mapnik.Map(512,512)
# mapnik.load_map(m,'../data/python_plugin/python_point_datasource.xml')
# m.zoom_all()
# im = mapnik.Image(512,512)
# mapnik.render(m,im)
# actual = '/tmp/mapnik-python-point-render1.png'
# expected = 'images/support/mapnik-python-point-render1.png'
# im.save(actual)
# expected_im = mapnik.Image.open(expected)
# eq_(im.tostring('png32'),expected_im.tostring('png32'),
# 'failed comparing actual (%s) and expected (%s)' % (actual,'tests/python_tests/'+ expected))
# def test_python_circle_rendering():
# m = mapnik.Map(512,512)
# mapnik.load_map(m,'../data/python_plugin/python_circle_datasource.xml')
# m.zoom_all()
# im = mapnik.Image(512,512)
# mapnik.render(m,im)
# actual = '/tmp/mapnik-python-circle-render1.png'
# expected = 'images/support/mapnik-python-circle-render1.png'
# im.save(actual)
# expected_im = mapnik.Image.open(expected)
# eq_(im.tostring('png32'),expected_im.tostring('png32'),
# 'failed comparing actual (%s) and expected (%s)' % (actual,'tests/python_tests/'+ expected))
# if __name__ == "__main__":
# setup()
# run_all(eval(x) for x in dir() if x.startswith("test_"))