Tuesday, June 29, 2010

Unwrapped JTS with Python

While Justin has been hard at work wrapping JTS and GeoTools in clear and simple Python syntax, I’ve been tinkering with some JTS classes that aren’t wrapped in GeoScript Python. Well, not wrapped yet. One of the great things about using Jython (or any scripting language written for the Java platform) is that you can still explore JTS and GeoTools even if some functionality isn’t implemented in GeoScript.

Jared produced a convex hull and minimum bounding circle with GeoScript Groovy in a recent post. I tried to replicate this workflow but quickly realized certain classes and functions weren’t available in GeoScript Python. Did it matter? Not very much in this case. Here’s the convex hull and minimum bounding circle for a shapefile of Washington weather stations produced in a previous post. Pay attention to what’s being imported.

from geoscript.layer import Shapefile
from geoscript.viewer import plot
from com.vividsolutions.jts.geom import GeometryCollection
from com.vividsolutions.jts.geom import GeometryFactory
from com.vividsolutions.jts.geom import PrecisionModel
from com.vividsolutions.jts.algorithm import MinimumBoundingCircle

#Get the point shapefile
staLayer = Shapefile('/home/gregcorradini/wa_stations.shp')

#Create a list of feature geometries
pntGeoms = [f.geom for f in staLayer.features()]

#Create a GeometryCollection from our list of points
geomColl = GeometryCollection(pntGeoms,GeometryFactory(PreciscionModel(),4326))

#Get the geometry collection's convex hull
geomConvexH = geomColl.convexHull()

#Get the geometry collection's minimum bounding circle
mCircle = (MinimumBoundingCircle(geomColl)).getCircle()

# Plot the geometries, BANG
plot(pntGeoms + [geomConvexH] + [mCircle])


I imported GeoScript modules and JTS Java classes in the example above. Jython can interoperate with Java libraries and classes because under the hood it’s Java. But how did I know what parameters a GeometryCollection accepted? I took a peek at the JTS Java doc and followed the bouncing argument ball. The weaving of colorful geometries happens after that thanks to the great plotting functionality from Justin.

There are tricks and trapdoors to this unwrapped exploration - the syntax is not as clean and intuitive as working with GeoScript Python. But a lot of magic still happens. I try to use functions that return geometry objects so I can plot them with the geoscript.viewer.plot function. This way I can see each step visually. Another advantage is that JTS and GeoTools have very well organized Java docs (here and there). It’s hard to fail at finding your way with road maps this good.

For example, mkgeomatics was deciding how to visually represent the output of a least-cost analysis he performed on a Bellingham, WA dataset. Part of the cartographic workflow used Voronoi diagrams that were built in ArcGIS. Both of us wondered if we could leverage GeoScript Python and JTS to solve the Voronoi diagram work. After a little sandbox scripting I came up with the following test code. It outlines how to derive a Voronoi diagram from a Delaunay triangulation (A more succinct way to build Voronoi diagrams would use the com.vividsolutions.jts.triangulate.VoronoiDiagramBuilder class. But Dr JTS produced an informative post on the relationship between both that I wanted to explore).
from geoscript.layer import Shapefile
from geoscript.viewer import plot
from com.vividsolutions.jts.geom import GeometryCollection
from com.vividsolutions.jts.geom import GeometryFactory
from com.vividsolutions.jts.geom import PrecisionModel
from com.vividsolutions.jts.algorithm import MinimumBoundingCircle
from com.vividsolutions.jts.triangulate import DelaunayTriangulationBuilder

#Get a point shapefile
staLayer = Shapefile('/home/gregcorradini/wa_stations.shp')

#Create a list of feature geometries
pntGeoms = [f.geom for f in staLayer.features()]

#Create a GeometryCollection from our list of points
geomColl = GeometryCollection(pntGeoms,GeometryFactory(PreciscionModel(),4326))

#Create a DelaunayTriangulationBuilder object and pass our geometry collection
dtb = DelaunayTriangulationBuilder()
dtb.setSites(geomColl)

#Get the Delaunay triangle faces
triangles = dtb.getTriangles(GeometryFactory(PrecisionModel(),4326))

#Make a pretty triangle picture
plot(pntGeoms + [triangles])


#Get the object's corresponding voronoi polygons in a java array
voronoiArray = dtb.getSubdivision().getVoronoiCellPolygons(GeometryFactory(PrecisionModel(),4326))

#Make a pretty picture
plot(pntGeoms + [i for i in voronoiArray])
That last Voronoi picture is hard to see in detail. So let's save off the Voronoi polygons to a shapefile and give each one the attributes of the original point that falls within it. The function 'changeGeometryType' is from a previous post.

#Create a voronoi geom.Poly schema based on old layer schema
voronoiSchema = changeGeometryType('voronoiWAStations',geom.Polygon,staLayer)

#Create voronoi layer
vLayer = ws.create(schema=voronoiSchema)

#For each voronoi polygon test which point is within it and pass attributes
for poly in voronoiArray:
for f in staLayer.features():

#Is my point within the polygon?
if f.geom.within(poly):

#Copy all point feature attributes into a dictionary
dictFeat = dict(f.iteritems())

#Overwrite the_geom with voronoi polygon
dictFeat['the_geom'] = poly

#Create a new feature and add it to voronoi layer
vLayer.add(voronoiSchema.feature(dictFeat))

The zoomed-in shot above shows the original points in pink, Delaunay triangles in gray and Voronoi diagram in green.

No comments:

Post a Comment

Introducing GeoScript

GeoScript adds geo capabilities to dynamic scripting languages such as JavaScript, Python, Scala and Groovy.