geoscript - spatial capabilities for scripting languages
DESCRIPTION
GeoScript adds spatial capabilities to dynamic scripting languages. With implementations in Python, JavaScript, Scala, and Groovy, GeoScript provides an interface to the powerful geospatial data access, processing and rendering functionality of the GeoTools library.GeoScript provides concise and simple apis that allow developers to perform a variety of tasks quickly. Some include:Geoprocessing - Coordinate reference system transforms, geometry operations, and feature queries.Data Juggling - GeoScript allows you to read and write feature data in a variety of formats. Converting Shapefiles to PostGIS tables, and processing on the fly while converting formats.Mapping Services - Using a simple web framework in conjunction with GeoScript, it is possible to write custom geospatial services quickly and easily like a simple WMS or WFS implementation. GeoScript is a new project but is growing in the number of users and contributors. Come check this talk out if you are interested in learning about a new tool to add to your geospatial hacking toolbox. Maybe you have tried to use GeoTools but find it too difficult and complex to use. Or perhaps your java skills are not where you would like them to be. If that is the case this talk, and GeoScript, might be just what you are looking for.TRANSCRIPT
GeoScriptSpatial Capabilities for Scripting Languages
Justin Deolivera, Tim Schaub, and Jared Erickson
Introduction• What is GeoScript?
o Overview of languages• Motivation
o GeoTools hard, scripting easyo Development turnaround
• GeoScript Modules/API Overviewo Geometryo Projectiono Data Accesso Styling
Scripting Platform for JVM Languages
• Similar API • Respect languages differences
Groovy
• Groovy o Dynamic languageo Easy for Java programmers to learn o Closures, DSLso REPL, GUI Consoleo Compiles to Java Byte Codeo Full access to Java libraries
• http://geoscript.org/groovy• https://github.com/jericks/geoscript-groovy
JavaScript
Not just for the browser any more!
Common JS module loading with Rhino.
One language for client & server code.
Docs: http://geoscript.org/js/Source: https://github.com/tschaub/geoscript-js/
Python
• Jython o Java implementation of Python o Jython 2.5 = CPython 2.5o Full access to Java libraries
• http://geoscript.org/py• https://github.com/jdeolive/geoscript-py
• Scalao Combine functional and object-oriented
programmingo Statically typedo REPLo Compiles to Java bytecodeo Full access to Java libraries
• http://geoscript.org/scala/• https://github.com/dwins/geoscript.scala/
Scala
On the shoulders of giants...
GeoScript Modules
Geometry
• Easy to use constructors• I/O
o WKT/WKBo JSONo GML
• Plotting• Transforms
Geometry
>>> from geoscript import geom>>> geom.Point(30, 10)POINT(30 10)
Geometry
>>> import geoscript.geom.*>>> line = new LineString([[111.0, -47], [123.0, -48], [110.0, -47]])
LINESTRING (111 -47, 123 -48, 110 -47)
Geometry
js> var poly = geom.Point([10, 30]). > buffer(5)js> poly<Polygon [[[15, 30], [14.90...>
js> poly.area78.03612880645133
Geometry - I/O
>>> from geoscript import geom>>> point = geom.Point(30, 10)>>> geom.writeKML(point)<kml:Point xmlns:kml="http://earth.google.com/kml/2.1"> <kml:coordinates>0.0,0.0</kml:coordinates></kml:Point>
Geometry - I/O
>>> import geoscript.geom.Point>>> import geoscript.geom.io.Gml2Writer
>>> p = new Point(111, -47)
>>> gml = new Gml2Writer()>>> gml.write(p)<gml:Point> <gml:coordinates>111.0,-47.0</gml:coordinates></gml:Point>
Geometry - I/O
js> var geom = require("geoscript/geom");
js> var point = geom.Point([1, 2])js> point.json{"type":"Point","coordinates":[1,2]}
Geometry - Visualization
>>> 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 - Visualization
js> var geom = require("geoscript/geom")js> require("geoscript/viewer").bind() js> var poly1 = geom.Point([0, 0]).buffer(1)js> var poly2 = poly1.transform({dx: 0.5, dy: 0.5})js> poly1.difference(poly2)<Polygon [[[0.9095298326166407, -0.409529...>
Projection
• Parse/encode WKT• Full GeoTools EPSG
database• Re-projection
Projection
js> var proj = require("geoscript/proj");js> var p = proj.Projection("epsg:4326");js> p.wktGEOGCS["WGS 84", DATUM["World Geodetic System 1984", ...
Projection
>>> from geoscript import geom>>> 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)]])
>>> p.transform(g, 'epsg:26912')>>> p.transform(g, 'epsg:3005')
Reprojection
WGS 84 UTM Albers
Data Access
• Read and Write Layers• Query Layers using CQL• I/O
o GeoJSONo GML
Data Access - Workspace
js> var ws = require("geoscript/workspace"); js> var dir = ws.Directory("data"); js> dir <Directory ["states"]>
js> var states = dir.get("states");js> states<Layer name: states, count: 49>
Data Access - Workspace
>> from geoscript.workspace import PostGIS
>> pg = PostGIS('spearfish')>> pg.layers()['archsites', 'bugsites', ..., 'streams']>> l = pg['archsites']
Data Access - Workspace
>>> import geoscript.workspace.H2>>> import geoscript.geom.Point>>> import geoscript.feature.Feature
>>> h2 = new H2("name", "path")
>>> layer = h2.create("points", [ new Field("geom","Point"), new Field("name","String")])
>>> layer.add([new Point[1,1],"one"])
Data Access - Layers
>>> from geoscript.layer import Shapefile
>>> states = Shapefile('states.shp')>>> states = states.reproject('epsg:3005')
Data Access - Layer Info
>>> import geoscript.layer.Shapefile>>> shp = new Shapefile("states.shp")>>> shp.count49>>> shp.bounds(-124.73142200000001, 24.955967,-66.969849, 49.371735, EPSG:4326)
>>> shp.schema.fields.each { fld -> println fld}the_geom: MultiPolygon(EPSG:4326)STATE_NAME: StringSTATE_FIPS: StringSUB_REGION: StringSTATE_ABBR: String
Data Access - Layers
js> var ws = require("geoscript/workspace"); js> var dir = ws.Directory("data"); js> var states = dir.get("states");
js> states.query("STATE_ABBR like 'M%'").forEach( > function(feature) { > print(feature.get("STATE_NAME")); > } > )MarylandMissouriMississippi...
Styling and Rendering
• Taming SLDo Symbolizerso Scale dependenceo Thematics
Styling - Stroke
>>> from geoscript.style import Stroke
>>> Stroke('#000000', width=2)>>> Stroke('black', width=2, dash=[5,5])>>> Stroke((0,0,0),width=2).hatch('vertline')
Styling - Fill
>>> import geoscript.style.Fill
>>> new Fill("gray")>>> new Fill("gray", 0.5))>>> new Fill("gray").hatch("backslash")>>> new Stroke("red",2) + new Fill("gray").hatch("times")
Styling - Shape and Icon
js> var style = require("geoscript/style");
js> style.Shape({name: "star", fill: "yellow"})<Shape name: 'star', size: 6>
js> style.Icon("rainy.svg")<Icon url: 'rainy.svg'>
Styling - Labels
>>> from geoscript.style import Label,Stroke,Fill,Shape
>>> font = 'bold 16px Arial'>>> Shape() + Label('name',font) .point(displace=(20,0))
>>> Stroke() + Label('name',font) .linear(offset=10)
>>> Fill() + Label('name',font).halo('white',2)
Styling - Scale
>>> new Shape('#004d96', 5).range(3000) + new Icon('school20.png').range(1500, 3000)+ new Icon('school40.png').range(-1, 1500)
Styling - Theming
>>> from geoscript.style Stroke, Fill, Label
>>> style = Stroke() + Label('STATE_ABBR', 14, 'Serif')>>> 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')
Demos
Voronoi Diagram Exampleimport geoscript.layer.*import geoscript.feature.*import geoscript.geom.*
def shp = new Shapefile('states.shp')def schema = new Schema('states_voronoi', [['the_geom','MultiPolygon','EPSG:4326']])
def diagramLayer = shp.workspace.create(schema)def geoms = shp.features.collect{f-> f.geom.centroid}def geomCol = new GeometryCollection(geoms)def voronoiGeom = geomCol.voronoiDiagramdiagramLayer.add(schema.feature([voronoiGeom]))
Gradient Examplevar Directory = require("geoscript/workspace").Directory;var {Fill, gradient} = require("geoscript/style");var Map = require("geoscript/map").Map;
var states = Directory("data").get("states");
states.style = gradient({ expression: "PERSONS / LAND_KM", values: [0, 200], styles: [Fill("#000066"), Fill("red")], classes: 10, method: "exponential"}).and( Fill("red").where("PERSONS / LAND_KM > 200"));
var map = Map([states]);
map.render({path: "states.png"});
Shapefile to PostGISfrom geoscript.workspace import Directory, PostGISshps = Directory('shapefiles')shps.layers()
archsites = shps['archsites']archsites.proj.id
pg = PostGIS('demo')pg.layers()for layer in shps: reprojected = shps[layer].reproject('epsg:4326') pg.add(reprojected, name=layer)
pg.layers()archsites = pg['archsites']archsites.proj.id
Road Map
• Raster• Rendering• WPS/GeoServer• 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!
Centroidsimport 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.cursorwhile(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()
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.Layerimport 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 = 0String 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
@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 thisGraffiti.start()
Graffiti Micro Web Framework
Geometry Web Services
Geometry Web ServicesOpen 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 Serverimport com.sun.grizzly.http.embed.GrizzlyWebServerimport com.sun.grizzly.http.servlet.ServletAdapterimport 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.Mapimport geoscript.style.*import geoscript.layer.Shapefileimport 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 = boundsresponse.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
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) returnif (opt.h || !opt.d) cli.usage()else println geoscript.geom.Geometry.fromWKT(System.in.text).buffer(opt.d as double).wkt
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) returnif (opt.h) cli.usage()else println geoscript.geom.Geometry.fromWKT(System.in.text).bounds.geometry.wkt
geom_buffer.groovy
geom_envelope.groovy