remove python plugin from core
- now at https://github.com/mapnik/non-core-plugins - refs #2773, #1875, #1337,
This commit is contained in:
parent
d310806485
commit
686731cade
14 changed files with 3 additions and 1053 deletions
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
```
|
|
@ -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')
|
|
@ -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')
|
|
@ -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')
|
|
@ -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
|
|
@ -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>
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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_++);
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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_"))
|
Loading…
Reference in a new issue