mapnik/plugins/input/python/examples/concentric_circles.py
Rich Wareham 156a7590f4 python: a new plugin to use arbitrary Python as a data source
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.

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.
2012-07-31 17:05:27 +01:00

83 lines
2.7 KiB
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')