Matplotlib (http://matplotlib.sourceforge.net), a 2D plotting library, is attracting well deserved attention from Python users. It is a readily installed and rich environment for 2D plotting and visualization with a fat users guide and many nice examples. There even exists a cartographic module to generate background basemaps for plots of geophysical data. What intrigues me the most is its Agg backend and API that provides an easy way to learn about anti-grain geometry. It seemed like it wouldn't take long to find out if matplotlib and Agg were a good fit to PCL's mapping classes.
I began by revisiting the two slightly overlapping triangle polygons from a previous entry, then creating a polygon symbolizer, and defining map image output parameters. PCL polygon symbolizers have a default opacity of 50% -- we'll see the overlap effect in the output image:
from cartography import mapping, spatial
from cartography.spatial import Point, LinearRing, Polygon
t1wkt = 'POLYGON ((-1.0 50.5, -0.5 51.2, 0.3 50.9, -1 50.5))'
triangle1 = spatial.Polygon(wkt=t1wkt)
t2wkt = 'POLYGON((-0.7 50.3, 0.1 51.0, 0.6 50.1, -0.7 50.3))'
triangle2 = spatial.Polygon(wkt=t2wkt)
# Symbolizer for triangles
symb = mapping.PolygonSymbolizer(fill={'fill': '#FF8080'})
# Define output map parameters
epsg4326 = spatial.SpatialReference(epsg=4326)
view = mapping.View(epsg4326, spatial.Point(-1.2,51.25),
spatial.Point(1,50.05))
width = 550
height = 300
Define two utility functions: hex_to_rgb() converts PCL colors to matplotlib's normalized tuple, and transform_point() maps PCL points in world units to matplotlib image units. Image coordinates in matplotlib have their origin at the lower left corner, contrary to convention:
def hex_to_rgb(color):
"""convert a hex color such as #FFFFFF to a red, green, blue tuple"""
c = eval('0x' + color[1:])
r = (c >> 16) & 0xFF
g = (c >> 8) & 0xFF
b = c & 0xFF
return (r, g, b)
def transform_point(point, view, width, height):
"""transform from world to image coordinates"""
dx = (view.lr.x - view.ul.x)/width
dy = (view.lr.y - view.ul.y)/height
p = (point.x - view.ul.x)/dx
l = (point.y - view.lr.y)/dy
return (p, l)
Import the matplotlib module and render the two triangles. The matplotlib graphic context 'gc' is configured from properties of the PCL polygon symbolizer:
import matplotlib
matplotlib.use('Agg')
from matplotlib.backends.backend_agg import RendererAgg
from matplotlib.transforms import Value
dpi = Value(72.0)
o = RendererAgg(w, h, dpi)
gc = o.new_gc()
for polygon in [triangle1, triangle2]:
points = [transform_point(p, view, width, height) for p in polygon[0]]
# Fill
gc.set_alpha(symb.fill['fill-opacity'])
rgb = hex_to_rgb(symb.fill['fill'])
face = (rgb[0]/255.0, rgb[1]/255.0, rgb[2]/255.0)
o.draw_polygon(gc, face, points)
# Stroke
gc.set_alpha(symb.stroke['stroke-opacity'])
gc.set_foreground(symb.stroke['stroke'])
gc.set_linewidth(symb.stroke['stroke-width'])
o.draw_polygon(gc, None, points)
o._renderer.write_png('agg_rendered.png')
the result, agg_rendered.png:
My impressions? So far, I like what I see. The matplotlib API is better suited to PCL's SLD-ish mapping objects than MapServer's mapscript module. That's a big plus for me. I don't yet appreciate why matplotlib excludes shape fill colors from the graphic context, or the quirky choice of lower left for image coordinate origin.
Clearly, the code within the loop over triangles needs to be written in C++ (like Agg) rather than Python to get decent performance when rendering hundreds or thousands of polygons at a time. I haven't written any C++ extensions for Python yet (C only), but this might be my first opportunity.
Comments
Re: Python vs Perl vs PHP vs ...
Author: Anantha Prasad
After struggling with the ugliness of php/mapscript, i finally coded an application (with the help of Bill Kropla's book) using mod_python's psp with postgis layers. Python/PSP is the way to go. I struggled for years with perl - but for a part-time coder like me, switching to python was the right way to go. The climate change tree atlas on mapserver will go online sometime this summer.