GeoScript Spatial Capabilities for Scripting Languages Justin Deolivera and Jared Erickson Scripting Platform for JVM Languages Similar API Respect languages differences Python GeoScript should be Pythonic Make easy things easy, Make hard things possible Groovy • Groovy o Dynamic language o Easy for Java programmers to learn o Closures, DSLs o REPL, GUI Console o Compiles to Java Byte Code o Full access to Java libraries • http://geoscript.org/groovy • https://github.com/jericks/geoscript-groovy JavaScript Python • Jython o Java implementation of Python o Jython 2.5 = CPython 2.5 o Full access to Java libraries • http://geoscript.org/py • https://github.com/jdeolive/geoscript-py Scala • Scala o Combine functional and object-oriented programming o Statically typed??? o REPL o Compiles to Java bytecode o Full access to Java libraries • http://geoscript.org/scala/ • https://github.com/dwins/geoscript.scala/ On the shoulders of giants... On the shoulders of giants... • Java Topology Suite (JTS) o Geometry API and Algorithms • GeoTools o DataStores o Features o Coverages/Rasters o Referencing o Renderering • Java Tribe Base libraries o GeoServer o uDig o Geomajas GeoScript Modules GeoScript Modules • • • • • • • Geometry Projection Feature Layer Workspace Style Renderer Geometry • Convenient constructors • I/O o WKT/WKB o JSON o GML • Plotting • Transforms Geometry Constructors >>> from geoscript import geom >>> geom.Point(30, 10) POINT(30 10) >>> geom.LineString((30,10), (10,20), (20,40), (40,40), (30,10)) LINESTRING (30 10, 10 20, 20 40, 40 40, 30 10) >>> geom.Polygon([(35,10), (10,20), (15,40), (45,45), (35,10)], [(20,30), (35,35), (30,20), (20,30)]) POLYGON ((35 10, 10 20, ... 30 20, 20 30)) >>> geom.MultiLineString([(10,10), (20,20), (10,40)], [(40,40), (30,30), (40,20), (30,10)]) MULTILINESTRING ((10 10, 20 20, ... 40 20, 30 10)) Geometry Input/Output >>> from geoscript import geom >>> point = geom.Point(30, 10) >>> geom.writeGML(point) <gml:Point xmlns:gml="http://www.opengis.net/gml"> <gml:coord> <gml:X>30.0</gml:X> <gml:Y>10.0</gml:Y> </gml:coord> </gml:Point> >>> geom.readJSON('{"type":"Point","coordinates":[30,10]}') POINT(30 10) >>> geom.writeWKB(point) array('b', [0, 0, 0, 0, 1, 64, 62, ... 64, 36, 0, 0, 0, 0, 0, 0]) Geometry Plotting >>> from geoscript.render import plot >>> from geoscript import geom >>> poly = geom.Polygon([(35,10), (10,20), (15,40), (45,45), (35,10)], [(20,30), (35,35), (30,20), (20,30)]) >>> plot(poly) Geometry Transforms >>> from geoscript.geom import Point, transform >>> from geoscript.render import draw >>> c = geom.Point(0, 0).buffer(10) >>> d = transform(c, dx=5) # translate >>> draw([c,d]) >>> e,f = transform(c, shx=0.5), transform(c, shx=-0.5) # shear >>> g = transform(c, sx=0.7) # scale >>> draw([e,f,g]) Projection • Parse/encode WKT • Full GeoTools EPSG database • Re-projection Projection Constructors >>> from geoscript.proj import Projection >>> Projection('EPSG:4326') GEOGCS["WGS 84", DATUM["World Geodetic System 1984", SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], AUTHORITY["EPSG","6326"]], PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], UNIT["degree", 0.017453292519943295], AXIS["Geodetic longitude", EAST], AXIS["Geodetic latitude", NORTH], AUTHORITY["EPSG","4326"]] >>> p = Projection('GEOGCS[..."Degree",0.017453292519943295]]') >>> p.id 'EPSG:4326' Projection Reprojection >>> from geoscript import geom, render >>> from geoscript.proj import Projection >>> p = Projection('epsg:4326') >>> p.transform((-111, 45.7), 'epsg:26912') (500000.0, 5060716.313515949) >>> g = geom.Point(0, 0).buffer(4) >>> g = reduce(lambda x,y:x.union(y),[geom.transform(g,dx=x,dy=y) for x,y in [(3,0),(0,3),(-3,0),(0,-3)]]) >>> render.draw(g) >>> render.draw(p.transform(g, 'epsg:26912')) >>> render.draw(p.transform(g, 'epsg:3005')) WGS 84 UTM Albers Data Access • Read and Write Layers • Query Layers using CQL • I/O o GeoJSON o GML Data Access Workspaces >>> from geoscript.workspace import PostGIS >>> from geoscript import geom >>> pg = PostGIS('spearfish') >>> pg.layers() ['archsites', 'bugsites', 'railroads', 'restricted', 'roads', 'streams'] >>> l = pg['bugsites'] >>> l.schema bugsites [cat: long, str1: str, the_geom: Point] >>> l = Layer('foo') >>> l.add([geom.Point(0,0)]) >>> l = pg.add(l) >>> l = pg.create('bar', [('geom',geom.Point),('name',str)]) Data Access Layers >>> from geoscript.layer import Shapefile >>> shp = Shapefile('states.shp') >>> shp.count() 49 >>> shp.count('CARPOOL/PERSONS > 0.06') 26 >>> shp.bounds() (-124.731422, 24.955967, -66.969849, 49.371735, EPSG:4326) >>> shp.bounds("STATE_NAME = 'Colorado'") (-109.055199, 36.988972, -102.036758, 41.00341, EPSG:4326) >>> for f in shp.features(): ... print '%s has %f people' % (f['STATE_NAME'],f['PERSONS']) >>> for f in shp.features('CARPOOL/PERSONS > 0.06'): ... print '%s likes to carpool' % f['STATE_NAME'] Data Access Features >>> from geoscript.feature import Schema, Feature >>> from geoscript.geom import * >>> schema = Schema('shapes', [('geom',Polygon), ('name',str)]) >>> square = Feature([LineString((0,0),(1,1)).bounds().toPolygon(), 'square'], 0, schema) >>> square.id 0 >>> square['name'] 'square' >>> square.geom POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0)) >>> square.geom = LineString((0,0),(2,1)).bounds().toPolygon() >>> square['name'] = 'rectangle' >>> circle = Feature({'geom':Point(0,0).buffer(1),'name':'circle'}) Data Access Input/Output >>> from geoscript.feature import * >>> from geoscript.geom import Polygon >>> triangle = Feature({'geom':Polygon([(0,0),(1,1),(2,0),(0,0)])}) >>> writeGML(triangle) <gsf:feature fid="fid--272f9bd3_1322c34ca2d_-8000" xmlns:gml="http://www.opengis.net/gml" xmlns:gsf="http://geoscript.org/feature"> <gsf:geom> <gml:Polygon>...</gml:Polygon> </gsf:geom> </gsf:feature> >>> writeGML(triangle, version=3.2) ... >>> writeJSON(triangle) {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[0.0,0.0],[1,1],[2,0.0],[0.0,0.0]]]},"properties":{},"id": "fid--272f9bd3_1322c34ca2d_-8000"} >>> f = readJSON('{"type":"Feature", "geometry":{"type":"Point", "coordinates":[1.0, 2.0]}}') feature.fid--709b2d80_1322c512876_-8000 {geometry: POINT (1 2)} Styling and Rendering • Taming SLD o Symbolizers o Scale dependence o Thematics Styling Stroke >>> from geoscript.style import Stroke >>> from geoscript.render import draw >>> from geoscript.geom import * >>> line = LineString((0,0), (1,1)) >>> draw(line, Stroke('#000000', width=2)) # normal >>> draw(line, Stroke('black', width=2, dash=[5,5])) # dash >>> draw(line, Stroke((0,0,0), width=2).hatch('vertline')) # hatch Styling Fill >>> from geoscript.style import Fill, Stroke >>> from geoscript.render import draw >>> from geoscript.geom import * >>> poly = Point(0,0).buffer(1) >>> draw(poly, Fill('gray')) # normal >>> draw(poly, Fill('gray', opacity=0.5)) # transparent >>> draw(poly, Fill('gray').hatch('backslash')) # hatch >>> draw(poly, Stroke('red',2) + Fill().hatch('times')) Styling Shape >>> from geoscript.style import Shape >>> from geoscript.render import draw >>> from geoscript.geom import * >>> mpoint = MultiPoint((0,0), (0,1), (1,1), (1,0), (0.5,0.5)) >>> draw(mpoint, Shape('black', 10)) >>> draw(mpoint, Shape('yellow', 20, 'star')) >>> draw(mpoint, Shape('red', 20, 'x')) Styling Icon >>> from geoscript.style import Icon >>> from geoscript.render import draw >>> from geoscript.geom import * >>> mpoint = MultiPoint((0,0), (0,1), (1,1), (1,0), (0.5,0.5)) >>> draw(mpoint, Icon('hospital.png')) >>> draw(mpoint, Icon('rainy.svg')) Styling Labels >>> from geoscript.style import Label, Stroke, Fill, Shape >>> from geoscript.feature import Feature >>> from geoscript.render import draw >>> from geoscript.geom import * >>> font = 'bold 16px Arial' >>> f = Feature({'geom': Point(0,0), 'name': 'foo'}) >>> draw(f, Shape() + Label('name', font).point(displace=(20,0))) >>> f = Feature({'geom': LineString((0,0), (1,1)), 'name': 'bar'}) >>> draw(f, Stroke() + Label('name', font).linear(offset=10)) >>> f = Feature({'geom': Point(0,0).buffer(1), 'name': 'baz'}) >>> draw(f, Fill() + Label('name', font).halo('white', 2)) Styling Scale >>> from geoscript.style Shape, Icon, Label >>> from geoscript.render import draw >>> from geoscript.feature import Feature >>> from geoscript.geom import * >>> school = Feature({'loc': Point(0,0), 'name': 'J.L. Crowe'}) >>> style = Shape('#004d96', 5).range(min=3000) >>> style += Icon('school20.png').range(max=3000, min=1500) >>> style += Icon('school40.png').range(max=1500) >>> bounds = Bounds(-100, -100, 100, 100, 'epsg:404000') >>> draw(school, style, bounds) >>> draw(school, style, bounds.scale(0.5)) >>> draw(school, style, bounds.scale(0.1)) Styling Theming >>> from geoscript.style Stroke, Fill, Label >>> from geoscript.render import draw >>> from geoscript.layer import Shapefile >>> states = Shapefile('states.shp') >>> style = Stroke() + Label('STATE_ABBR', '14 "Times New Roman"') >>> style += Fill('#4DFF4D', 0.7).where('PERSONS < 2000000') >>> style += Fill('#FF4D4D', 0.7).where('PERSONS BETWEEN 2000000 AND 4000000') >>> style += Fill('#4D4DFF', 0.7).where('PERSONS > 4000000') >>> draw(states, style) Centroids import geoscript.layer.* import geoscript.feature.* import geoscript.geom.* Shapefile shp = new Shapefile('states.shp') Schema schema = shp.schema.changeGeometryType('Point', 'states_centroids') Layer centroidLayer = shp.workspace.create(schema) Cursor cursor = shp.cursor while(cursor.hasNext()) { Feature f = cursor.next() Map attributes = [:] f.attributes.each{k,v -> if (v instanceof Geometry) { attributes[k] = v.centroid } else { attributes[k] = v } } Feature feature = schema.feature(attributes, f.id) centroidLayer.add(feature) } cursor.close() Voronoi Diagram import geoscript.layer.* import geoscript.feature.* import geoscript.geom.* Shapefile shp = new Shapefile('states.shp') Schema schema = new Schema('states_voronoi', [['the_geom','MultiPolygon','EPSG:4326']]) Layer diagramLayer = shp.workspace.create(schema) List geoms = shp.features.collect{f-> f.geom.centroid } GeometryCollection geomCol = new GeometryCollection(geoms) Geometry voronoiGeom = geomCol.voronoiDiagram diagramLayer.add(schema.feature([voronoiGeom])) Shapefiles to PostGIS import geoscript.workspace.* import geoscript.layer.* def dir = new Directory("/Users/jericks/Downloads/wash") println("Shapefiles: ${dir.layers}") def postgis = new PostGIS('postgres','localhost','5432','public','postgres', 'postgres') println("PostGIS Layers: ${postgis.layers}") dir.layers.each{name-> def layer = dir.get(name) println("Adding ${layer.name}...") postgis.add(layer) } USGS Earth Quakes Read RSS Feed to a Shapefile import geoscript.geom.* import geoscript.feature.* import geoscript.layer.Layer import geoscript.workspace.Directory Schema s = new Schema('earthquakes'[['the_geom', 'Point', 'EPSG:4326'], ['title','String'], ['date', 'java.util.Date'], ['elevation', 'Double']]) Directory dir = new Directory('.') Layer layer = dir.create(s) def url = "http://earthquake.usgs.gov/earthquakes/catalogs/1day-M2.5.xml" def rss = new XmlParser().parse(url) int c = 0 String dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'" rss.entry.each{e -> def title = e.title.text() def date = Date.parse(dateFormat, e.updated.text()) def coordinate = e."georss:point".text().split(" ") double x = coordinate[1] as Double double y = coordinate[0] as Double def point = new Point(x,y) def elev = e."georss:elev".text() as Double Feature f = s.feature(['title':title,'date':date, 'elevation': elev, 'the_geom': point],"earthquake_${c}") layer.add(f) c++ } Web Applications Graffiti Micro Web Framework @GrabResolver(name="graffiti", root="http://simple-dm.googlecode.com/svn/repository") @Grab("com.goodercode:graffiti:1.0-SNAPSHOT") import graffiti.* import geoscript.geom.Geometry @Get("/buffer") def buffer() { Geometry.fromWKT(params.geom).buffer(params.distance as double).wkt } @Get("/centroid") def centroid() { Geometry.fromWKT(params.geom).centroid.wkt } @Get("/convexHull") def convexHull() { Geometry.fromWKT(params.geom).convexHull.wkt } Graffiti.root 'graffiti' Graffiti.serve this Graffiti.start() Geometry Web Services Geometry Web Services Open Layers function centroid() { var features = vectorLayer.features; if (features.length == 0) { alert("Please add some features!"); } else { OpenLayers.loadURL('centroid', { geom: wktFormat.write(features) }, this, function(request) { var wkt = request.responseText; var features = wktFormat.read(wkt); if (features) vectorLayer.addFeatures(features); }, function() { alert("Error calculating centroids!"); } ); } } WMS Server import com.sun.grizzly.http.embed.GrizzlyWebServer import com.sun.grizzly.http.servlet.ServletAdapter import groovy.servlet.GroovySerlvet @Grab(group='com.sun.grizzly',module='grizzly-servlet-webserver', version='1.9.10') def start() { def server = new GrizzlyWebServer(8080, "web") def servlet = new ServletAdapter() servlet.contextPath = "/geoscript" servlet.servletInstance = new GroovyServlet() server.addGrizzlyAdapter(servlet, ["/geoscript"] as String[]) server.start() } start() WMS Server... import geoscript.map.Map import geoscript.style.* import geoscript.layer.Shapefile import geoscript.geom.Bounds def file = new File("states.shp") def shp = new Shapefile(file) shp.style = new Fill("steelblue") + new Stroke("wheat", 0.1) def map = new Map( width: 256, height: 256, layers: [shp], proj: shp.proj, fixAspectRatio: false ) def bbox = request.getParameter("BBOX").split(",") def bounds = new Bounds(bbox[0] as double, bbox[1] as double, bbox[2] as double, bbox[3] as double) map.bounds = bounds response.contentType = "image/png" map.render(response.outputStream) map.close() Geometry Command line echo "POINT (1 1)" | geoscript-groovy geom_buffer.groovy -d 10 | geoscript-groovy geom_envelope.groovy geom_buffer.groovy def cli = new CliBuilder(usage: 'geoscript-groovy geom_buffer.groovy -d') cli.d(longOpt: 'distance', 'buffer distance', args:1) cli.h(longOpt: 'help', 'Show usage information and quit') def opt = cli.parse(args) if(!opt) return if (opt.h || !opt.d) cli.usage() else println geoscript.geom.Geometry.fromWKT(System.in.text).buffer(opt.d as double).wkt geom_envelope.groovy def cli = new CliBuilder(usage: 'geoscript-groovy geom_envelope.groovy') cli.h(longOpt: 'help', 'Show usage information and quit') def opt = cli.parse(args) if(!opt) return if (opt.h) cli.usage() else println geoscript.geom.Geometry.fromWKT(System.in.text).bounds.geometry.wkt Road Map • • • • Rendering Module Raster Module WPS/GeoServer plugin(s) Map Printing Resources Web Site http://geoscript.org Google Group http://groups.google.com/group/geoscript Blog http://geoscriptblog.blogspot.com GitHub https://github.com/jdeolive/geoscript-py https://github.com/tschaub/geoscript-js https://github.com/dwins/geoscript.scala https://github.com/jericks/geoscript-groovy Thank you!