From 686731cade5ad2320690eddaea92924d7811f7a0 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Fri, 24 Apr 2015 08:51:56 +0200 Subject: [PATCH] remove python plugin from core - now at https://github.com/mapnik/non-core-plugins - refs #2773, #1875, #1337, --- SConstruct | 7 +- plugins/input/python/README.md | 241 ------------------ plugins/input/python/build.py | 97 ------- .../python/examples/concentric_circles.py | 83 ------ .../input/python/examples/simple_points.py | 34 --- plugins/input/python/examples/simple_xml.py | 8 - plugins/input/python/examples/simple_xml.xml | 16 -- plugins/input/python/python_datasource.cpp | 238 ----------------- plugins/input/python/python_datasource.hpp | 58 ----- plugins/input/python/python_featureset.cpp | 30 --- plugins/input/python/python_featureset.hpp | 38 --- plugins/input/python/python_utils.cpp | 23 -- plugins/input/python/python_utils.hpp | 23 -- tests/python_tests/python_plugin_test.py | 160 ------------ 14 files changed, 3 insertions(+), 1053 deletions(-) delete mode 100644 plugins/input/python/README.md delete mode 100644 plugins/input/python/build.py delete mode 100644 plugins/input/python/examples/concentric_circles.py delete mode 100644 plugins/input/python/examples/simple_points.py delete mode 100644 plugins/input/python/examples/simple_xml.py delete mode 100644 plugins/input/python/examples/simple_xml.xml delete mode 100644 plugins/input/python/python_datasource.cpp delete mode 100644 plugins/input/python/python_datasource.hpp delete mode 100644 plugins/input/python/python_featureset.cpp delete mode 100644 plugins/input/python/python_featureset.hpp delete mode 100644 plugins/input/python/python_utils.cpp delete mode 100644 plugins/input/python/python_utils.hpp delete mode 100644 tests/python_tests/python_plugin_test.py diff --git a/SConstruct b/SConstruct index b4daccc5a..4c2159552 100644 --- a/SConstruct +++ b/SConstruct @@ -126,8 +126,7 @@ PLUGINS = { # plugins with external dependencies 'csv': {'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++'}, - 'topojson':{'default':True,'path':None,'inc':None,'lib':None,'lang':'C++'}, - 'python': {'default':False,'path':None,'inc':None,'lib':None,'lang':'C++'}, + 'topojson':{'default':True,'path':None,'inc':None,'lib':None,'lang':'C++'} } @@ -1647,7 +1646,7 @@ if not preconfigured: env['SKIPPED_DEPS'].append('cairo') 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): color_print(1,"Cannot run python interpreter at '%s', make sure that you have the permissions to execute it." % env['PYTHON']) Exit(1) @@ -1865,7 +1864,7 @@ if not preconfigured: if env['DEBUG_UNDEFINED']: 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('.') # we don't want the includes it in the main environment... # as they are later set in the python build.py diff --git a/plugins/input/python/README.md b/plugins/input/python/README.md deleted file mode 100644 index f1a4c57e3..000000000 --- a/plugins/input/python/README.md +++ /dev/null @@ -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 - - - - - style - - python - test:TestDatasource - - - -``` - -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') -``` diff --git a/plugins/input/python/build.py b/plugins/input/python/build.py deleted file mode 100644 index e5d20ec5b..000000000 --- a/plugins/input/python/build.py +++ /dev/null @@ -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') diff --git a/plugins/input/python/examples/concentric_circles.py b/plugins/input/python/examples/concentric_circles.py deleted file mode 100644 index b80531251..000000000 --- a/plugins/input/python/examples/concentric_circles.py +++ /dev/null @@ -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') diff --git a/plugins/input/python/examples/simple_points.py b/plugins/input/python/examples/simple_points.py deleted file mode 100644 index 1a51fc3cd..000000000 --- a/plugins/input/python/examples/simple_points.py +++ /dev/null @@ -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') diff --git a/plugins/input/python/examples/simple_xml.py b/plugins/input/python/examples/simple_xml.py deleted file mode 100644 index 8dbe3261b..000000000 --- a/plugins/input/python/examples/simple_xml.py +++ /dev/null @@ -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 diff --git a/plugins/input/python/examples/simple_xml.xml b/plugins/input/python/examples/simple_xml.xml deleted file mode 100644 index a745f5f55..000000000 --- a/plugins/input/python/examples/simple_xml.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - style - - python - test:TestDatasource - - - diff --git a/plugins/input/python/python_datasource.cpp b/plugins/input/python/python_datasource.cpp deleted file mode 100644 index dc132fd31..000000000 --- a/plugins/input/python/python_datasource.cpp +++ /dev/null @@ -1,238 +0,0 @@ -// file plugin -#include "python_datasource.hpp" -#include "python_featureset.hpp" - -// stl -#include -#include - -// 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 -#include -#include -#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("encoding","utf-8")), - factory_(*params.get("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(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 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::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(data_type); - return mapnik::datasource::datasource_t(data_type_integer); - } - catch ( boost::python::error_already_set ) - { - throw mapnik::datasource_exception(extractException()); - } - -} - -mapnik::box2d python_datasource::envelope() const -{ - mapnik::box2d 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 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 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 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 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 python_datasource::get_geometry_type() const -{ - using return_type = boost::optional; - - 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(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(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(features); - } - catch ( boost::python::error_already_set ) - { - throw mapnik::datasource_exception(extractException()); - } - -} diff --git a/plugins/input/python/python_datasource.hpp b/plugins/input/python/python_datasource.hpp deleted file mode 100644 index e6239c722..000000000 --- a/plugins/input/python/python_datasource.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef PYTHON_DATASOURCE_HPP -#define PYTHON_DATASOURCE_HPP - -// mapnik -#include - -// 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 -#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 envelope() const; - - // mandatory: optionally return the overal geometry type of the datasource - boost::optional 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 kwargs_; - boost::python::object datasource_; -}; - - -#endif // PYTHON_DATASOURCE_HPP diff --git a/plugins/input/python/python_featureset.cpp b/plugins/input/python/python_featureset.cpp deleted file mode 100644 index bbd5939e9..000000000 --- a/plugins/input/python/python_featureset.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "python_featureset.hpp" -#include "python_utils.hpp" - -// boost -#include - -python_featureset::python_featureset(boost::python::object iterator) -{ - ensure_gil lock; - begin_ = boost::python::stl_input_iterator(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_++); -} - diff --git a/plugins/input/python/python_featureset.hpp b/plugins/input/python/python_featureset.hpp deleted file mode 100644 index af024beab..000000000 --- a/plugins/input/python/python_featureset.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef PYTHON_FEATURESET_HPP -#define PYTHON_FEATURESET_HPP - -// mapnik -#include -#include - -// 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 -#include -#pragma GCC diagnostic pop - -// mapnik -#include - -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; - - feature_iter begin_, end_; -}; - -#endif // PYTHON_FEATURESET_HPP diff --git a/plugins/input/python/python_utils.cpp b/plugins/input/python/python_utils.cpp deleted file mode 100644 index 159bf70e1..000000000 --- a/plugins/input/python/python_utils.cpp +++ /dev/null @@ -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(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(formatted); - } -} diff --git a/plugins/input/python/python_utils.hpp b/plugins/input/python/python_utils.hpp deleted file mode 100644 index 5ddc3800b..000000000 --- a/plugins/input/python/python_utils.hpp +++ /dev/null @@ -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 -#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 diff --git a/tests/python_tests/python_plugin_test.py b/tests/python_tests/python_plugin_test.py deleted file mode 100644 index a39272f6b..000000000 --- a/tests/python_tests/python_plugin_test.py +++ /dev/null @@ -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_"))