04 Geographic scripting in uDig - halfway between user and developer
Click here to load reader
-
Upload
andrea-antonello -
Category
Technology
-
view
2.181 -
download
2
Transcript of 04 Geographic scripting in uDig - halfway between user and developer
nvironmental ngineeringydroloGISHydroloGIS S.r.l. - Via Siemens, 19 - 39100 Bolzano www.hydrologis.com
Open Source GIS
Geographic scripting in uDig - halfwaybetween user and developer
Geoinformation Research Group, Department of GeographyUniversity of Potsdam
March 2013
GeoscriptTutor: Andrea Antonello
Introduction to Geoscript
Geoscript is a geo processing library that is provided in various scripting
environments and is supported in the uDig scripting editor.
As for every scripting language, modules have to be enabled to be used.
Once one knows the language very well, he can proceed with importing the
necessary modules. The scripting editor has a button that helps by adding
the most used imports.
Most used packages for vector geoscripting
A list of imports and a short description of their purpose:
// most used packages
// handles geometries objectsimport geoscript.geom.*// handles projectionsimport geoscript.proj.*// handles rendering and plottingimport geoscript.render.*// enables layer managementimport geoscript.layer.*// enables tools to work with styleimport geoscript.style.*// handles various viewersimport geoscript.viewer.*// the package that works with filtersimport geoscript.filter.*// the package that handles workspacesimport geoscript.workspace.*// support for jgrasstools modulesimport org.jgrasstools.modules.*
Building Geometries
Geometries can be built through the use of their constructors:
// build geometries by constructors
// simple geometriesdef geom = new Point(30,10)println geomgeom = new LineString([30,10], [10,30], [20,40], [40,40])println geomgeom = new Polygon([30,10], [10,20], [20,40], [40,40], [30,10])println geomgeom = new Polygon([[[35,10],[10,20],[15,40],[45,45],[35,10]], [[20,30],[35,35],[30,20],[20,30]]])println geom
// multi-geometriesgeom = new MultiPoint([10,40],[40,30],[20,20],[30,10])println geomgeom = new MultiLineString([[10,10],[20,20],[10,40]], [[40,40],[30,30],[40,20],[30,10]])println geomgeom = new MultiPolygon([[[30,20], [10,40], [45,40], [30,20]]], [[[15,5], [40,10], [10,20], [5,10], [15,5]]])println geom
or through their well known text representation:
// build geometries by wktgeom = Geometry.fromWKT("POINT (30 10)")println geomgeom = Geometry.fromWKT("LINESTRING (30 10, 10 30, 20 40, 40 40)")println geomgeom = Geometry.fromWKT("POLYGON ((30 10, 10 20, 20 40, 40 40, 30 10))")println geomgeom = Geometry.fromWKT("POLYGON ((35 10, 10 20, 15 40, 45 45, 35 10), " + "(20 30, 35 35, 30 20, 20 30))")println geomgeom = Geometry.fromWKT("MULTIPOINT ((10 40), (40 30), (20 20), (30 10))")println geomgeom = Geometry.fromWKT("MULTILINESTRING ((10 10, 20 20, 10 40), " + "(40 40, 30 30, 40 20, 30 10))")println geomgeom = Geometry.fromWKT("MULTIPOLYGON (((30 20, 10 40, 45 40, 30 20)), " + "((15 5, 40 10, 10 20, 5 10, 15 5)))")println geom
A test set of geometries to use as reference
To better explain the various functions and predicates we will start by
creating a set of geometries on which to apply the operations.
You are now able to create the following points, line and polygons:
0
5
0 5
g1
g5
g2g3
g4
g6
Build the test set
Let's create the geometries that make up the test set:
// build the example datasetdef g1 = Geometry.fromWKT("POLYGON ((0 0, 0 5, 5 5, 5 0, 0 0))")def g2 = Geometry.fromWKT("POLYGON ((5 0, 5 2, 7 2, 7 0, 5 0))")def g3 = Geometry.fromWKT("POINT (4 1)")def g4 = Geometry.fromWKT("POINT (5 4)")def g5 = Geometry.fromWKT("LINESTRING (1 0, 1 6)")def g6 = Geometry.fromWKT("POLYGON ((3 3, 3 6, 6 6, 6 3, 3 3))")
Geoscript has a plotting utility that makes it possible to quickly check:
// plot geometriesPlot.plot([g1, g2, g3, g4, g5, g6])
Predicates
Intersects
Let's see which geometries intersect with g1 and print the result:
println g1.intersects(g2) // trueprintln g1.intersects(g3) // trueprintln g1.intersects(g4) // trueprintln g1.intersects(g5) // trueprintln g1.intersects(g6) // true
Note that geometries that touch (like g1 and g2) also intersect.
Touches
Let's test which geometries touch g1 and print the result:
println g1.touches(g2) // trueprintln g1.touches(g3) // falseprintln g1.touches(g4) // trueprintln g1.touches(g5) // falseprintln g1.touches(g6) // false
Contains
Let's test which geometries are contained by g1 and print the result:
println g1.contains(g2) // falseprintln g1.contains(g3) // trueprintln g1.contains(g4) // falseprintln g1.contains(g5) // falseprintln g1.contains(g6) // false
Mind that a point on the border is not contained, so only g3 is contained. This
can be solved through the covers predicate.
Covers
println g1.covers(g2) // falseprintln g1.covers(g3) // trueprintln g1.covers(g4) // trueprintln g1.covers(g5) // falseprintln g1.covers(g6) // false
As you can see, now also g4 is covered.
Functions
Intersection
// the intersection of polygons returns a polygondef g1_inter_g6 = g1.intersection(g6)println g1_inter_g6Plot.plot([g1_inter_g6, g1, g6])// but the intersection of touching polygons returns a lineprintln g1.intersection(g2)// the intersection of a polygon with a point is a pointprintln g1.intersection(g3)// the intersection of a polygon with a line is a pointprintln g1.intersection(g5)
The intersection of polygons g1 and g6:
0
5
0 5
g1
g5
g2g3
g4
g6
Symdifference
What is the resulting geometry of the symdifference of different geometry
types?
// the symDifference of intersecting polygons returns a multipolygonprintln g1.symDifference(g6)// but the symDifference of touching polygons returns the polygons unionprintln g1.symDifference(g2)// the symDifference of a polygon with a contained point returns the original polygonprintln g1.symDifference(g3)// the symDifference of a polygon with a line is a hybrid collection (line + polygon)println g1.symDifference(g5)
The following shows the symdifference of polygons g1 and g6:
0
5
0 5
g1
g5
g2g3
g4
g6
Union
What is the resulting geometry of the union of different geometry types?
// the union of intersecting polygons returns a polygonprintln g1.union(g6)// same as the union of touching polygonsprintln g1.union(g2)// the union of a polygon with a contained point is a point returns the original polygonprintln g1.union(g3)// the union of a polygon with a line is a hybrid collection (line + polygon)println g1.union(g5)
The following shows the union of polygons g1 and g6:
0
5
0 5
g1
g5
g2g3
g4
g6
Difference
The difference of geometries obviously depends on the calling object:
// this returns g1 minus the overlapping part of g6println g1.difference(g6)// while this returns g6 minus the overlapping part of g1println g6.difference(g1)// in the case of difference with lines, the result is the original polygon// with additional points in the intersectionsprintln g1.difference(g2)// the difference of polygon and point is the original polygonprintln g1.difference(g3)
The following shows the difference of polygons g1 and g6:
0
5
0 5
g1
g5
g2g3
g4
g6
Buffer
Creating a buffer around a
geometry always generates a
polygon geometry. The behaviour
can be tweaked, depending on the
geometry type:
// the buffer of a pointdef b1 = g3.buffer(1.0)
// the buffer of a point with few quandrant segmentsdef b2 = g3.buffer(1.0, 1)
// round end cap style, few pointsdef b3 = g5.buffer(1.0, 2, Geometry.CAP_ROUND)
// round end cap style, more pointsdef b4 = g5.buffer(1.0, 10, Geometry.CAP_ROUND)
// square end cap styledef b5 = g5.buffer(1.0, -1, Geometry.CAP_SQUARE)
// single sided bufferdef b6 = g5.singleSidedBuffer(-0.5)
// plot the geometriesPlot.plot([b6, b5, b4, b3, b2, b1])
Convex Hull
To test the convext hull operation, let's create a geometry collection
containing the line and all the polygons. Then simply apply the convex hull
function:
// let's create a geometry collection with the polygons and line in itdef collection = new GeometryCollection(g1, g2, g5, g6)// and apply the convex hulldef convexhull = collection.convexHullPlot.plot([convexhull, g1, g2, g5, g6])
Transformations
def square = new Polygon([[[0,0],[1,0],[1,1],[0,1],[0,0]]])// scale the sqaure by 4 timesdef squareLarge = square.scale(4,4)// move it by x, y unitsdef squareTranslate = square.translate(2,2)// move it and then rotate it by 45 degreesdef squareTranslateRotate = square.translate(2,2).rotate(Math.toRadians(45))// realize that the order of things are there for a reasondef squareRotateTranslate = square.rotate(Math.toRadians(45)).translate(2,2)// rotate around a defined centerdef squareTranslateRotateCenter = square.translate(2,2).rotate(Math.toRadians(45), 2.5, 2.5)// shear the squaredef squareShear = square.shear(0.75,0)// check the resultsPlot.plot([square, squareLarge, squareTranslate, squareTranslateRotate, squareRotateTranslate, squareTranslateRotateCenter, squareShear])
Projections
// create a projection objectdef latLonPrj = new Projection("epsg:4326")println latLonPrj.wkt
def latLonPoint = new Point(11, 46)
// transform the point to the new prjdef utm32nPoint = latLonPrj.transform(latLonPoint, "epsg:32632")println "Transformed ${latLonPoint} to ${utm32nPoint}"
// a simple way to do so isdef utm32nPoint1 = Projection.transform(latLonPoint, 'epsg:4326', 'epsg:32632')println "Transformed ${latLonPoint} to ${utm32nPoint1}"
// one can also create projections from the wkt representationdef wkt = """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"]]"""
def projFromWkt = new Projection(wkt)def utm32nPoint2 = projFromWkt.transform(latLonPoint, "epsg:32632")println "Transformed ${latLonPoint} to ${utm32nPoint2}"
Reading and writing GIS stuff
Geoscript supplies some facilities to read and write the most common GIS
data.
For example it is quite simple to get the KML representation of a given
geometry:
point = new Point(30,10)println "GML2 = " + point.gml2println "GML3 = " + point.gml3println "KML = " + point.kmlprintln "JSON = " + point.geoJSON
But usually we will have to deal with Shapefiles. Let's see how that works.
Creating the first shapefile
To create a shapefile, one first has to create a new layer defining the
geometry to use and the attributes to add to each feature.
// define a working folderDirectory dir = new Directory("/home/moovida/giscourse/mydata/")
// create a new layer of points with just one string attributedef simpleLayer = dir.create('just_two_cities',[['geom','Point','epsg:4326'],['name','string']])println "features in layer = " + simpleLayer.count()
// add the featuressimpleLayer.add([new Point(-122.42, 37.78),'San Francisco'])simpleLayer.add([new Point(-73.98, 40.47),'New York'])println "features in layer = " + simpleLayer.count()
// create a layer with different attributes typesdef complexLayer = dir.create('more_than_just_two_cities', [ ['geom','Point','epsg:4326'], ['name','string'], ['population','int'], ['lat','float'], ['lon','float'] ])complexLayer.add([new Point(-73.98, 40.47),'New York',19040000,40.749979064,-73.9800169288])
After running the above script you have a shapefile named
just_two_cities.shp that looks like:
Reading an existing shapefile
Reading data from a shapefile is quite straightforward, the Shapefile class
does everything for you. Let's read some general information about the layer
from 10m_admin_0_countries.shp layer and print out only the attributes of
the feature representing Germany.
countriesShp = new Shapefile("/home/moovida/giscourse/data_1_3/10m_admin_0_countries.shp")println "Layer: ${countriesShp.name}"println "Schema: ${countriesShp.schema}"println "Projection: ${countriesShp.proj}"println "Spatial extent: ${countriesShp.bounds}"println "Feature count: ${countriesShp.count}"
countriesShp.features.each(){ feature -> name = feature."NAME" if(name == "Germany"){ geomStr = feature.geom.toString() println geomStr.substring(0, 50) + "..." println "List of attributes: " println "----------------------------" feature.attributes.each(){ name, value -> println "\t${name}: ${value}" } }}
Reading from Postgis
Reading from Postgis is a bit more complex, but still really simple. Once one
knows the connection parameters, connecting is a smooth procedure. In the
following example the test postgis database kindly provided by Refractions
Research will be used:
// define the connection parameters
// the server to connect toserver = "www.refractions.net"// the port to connect toport = "5432"// the database namedatabaseName = "demo-bc"// the database schemadatabaseSchema = "public"// user and passworduser = "demo" pwd = "demo"
// connect and retrieve layerspostgis = new PostGIS(databaseName, server, port, databaseSchema, user, pwd)println postgis.layers
Converting from Postgis to Shapefile
Since reading and writing always passes through the concept of layer, it is
quite simple to convert the data to a shapefile:
// read from postgisserver = "www.refractions.net"port = "5432"databaseName = "demo-bc"databaseSchema = "public"user = "demo" pwd = "demo"postgis = new PostGIS(databaseName, server, port, databaseSchema, user, pwd)println "Layers read: ${postgis.layers}"println """Layer to copy: ${postgis."bc_pubs"}"""
// write to shapefiledir = new Directory("/home/moovida/giscourse/mydata/")dir.add(postgis."bc_pubs")
Create a countries centroids layer
It is no rocket science to apply all we have seen until this point to create a
shapefile containing the centroids of the countries.
All you need to know is that the geometry has a method that extracts the
centroid for you: centroid
countriesShp = new Shapefile("/home/moovida/giscourse/data_1_3/10m_admin_0_countries.shp")
// create the new layerdir = new Directory("/home/moovida/giscourse/mydata/")centroidsLayer = dir.create('countries_centroids', [['geom','Point','epsg:4326'],['country','string']])
// populate the layer on the flycountriesShp.features.each(){ feature -> centr = feature.geom.centroid centroidsLayer.add([centr,feature."NAME"])}
Is a centroid always contained?
France is a nice example:
Why is the centroid of
France in Spain?
Overseas departments and
territories drag the
baricenter around...
How can we check if the centroid lies inside the boundaries of the generating
country polygon?
countriesShp = new Shapefile("/home/moovida/giscourse/data_1_3/10m_admin_0_countries.shp")
countriesShp.features.each(){ feature -> polygon = feature.geom centr = polygon.centroid if(!polygon.intersects(centr)){ println """${feature."NAME"} has a non touching centroid.""" }}
Reproject a layer
Let's assume we want to retrieve the cities of Germany in UTM32N
projection. One way would be this (there are many different, but this shows
some new methods):
dir = new Directory("/home/moovida/giscourse/data_1_3")countries = dir."10m_admin_0_countries"cities = dir."10m_populated_places_simple"
// define the projectionsutm32Prj = new Projection("epsg:32632")
// get Germany filtering on the layergermanyFeatures = countries.getFeatures("NAME = 'Germany'")// check if something was foundif(germanyFeatures.size() > 0) { // get geometry wkt germanyPolygonWKT = germanyFeatures[0].geom.wkt // filter out only cities inside Germany germanyCities = cities.filter("INTERSECTS (the_geom, ${germanyPolygonWKT})") // reproject to UTM32 germanyCities.reproject(utm32Prj, "germancities_utm")} else { println "No layer Germany found!" }
Rendering data
Geoscript has some capabilities to create images from layers. All that needs
to be created, is a Map object, to which the layers to be rendered are added:
// read the necessary layers
// define the working folderdir = new Directory("/home/moovida/giscourse/data_1_3")// get the layers by name and add them to a Mapcountries = dir."10m_admin_0_countries"cities = dir."10m_populated_places_simple"rivers = dir."10m_rivers_lake_centerlines"
// create a map of 1200x900 pixelsmap = new Map(width:1200, height:900)
// the rendering order follows the order of additionmap.addLayer(countries)map.addLayer(rivers)map.addLayer(cities)
// dump the layers to an imagemap.render("/home/moovida/giscourse/mydata/world.png")
Which would look like:
Style a point layer
Points can be styled through the Shape class. It allows to tweak type, size,
color, stroke, opacity and rotation:
dir = new Directory("/home/moovida/giscourse/data_1_3")countries = dir."10m_admin_0_countries"cities = dir."10m_populated_places_simple"rivers = dir."10m_rivers_lake_centerlines"
cStroke = new Stroke("white", 0.1)cities.style = new Shape( type: "square", size: 10, color: "#FF0000", // red stroke: cStroke, opacity: 0.5, rotation: 45 )
map = new Map(width:2400, height:1800)map.addLayer(countries)map.addLayer(rivers)map.addLayer(cities)map.render("/home/moovida/giscourse/mydata/world1.png")
Which would look like:
Style a line layer
A line can be styles with a Stroke object, which allows beyond other
properties, color, width and cap:
dir = new Directory("/home/moovida/giscourse/data_1_3")countries = dir."10m_admin_0_countries"cities = dir."10m_populated_places_simple"rivers = dir."10m_rivers_lake_centerlines"
cStroke = new Stroke("white", 0.1)cities.style = new Shape(type: "square", size: 4, color: "#FF0000", stroke: cStroke, opacity: 0.5,rotation: 45)
// make rivers blue, thick and with rounded endingsrivers.style = new Stroke( color: "#0000FF", width: 2, cap: 'round' )
map = new Map(width:2400, height:1800)map.addLayer(countries)map.addLayer(rivers)map.addLayer(cities)map.render("/home/moovida/giscourse/mydata/world2.png")
Which would look like:
Style a polygon layer
Polygons can be styled with transparent fill and as for all other types, they
can be labeled:
dir = new Directory("/home/moovida/giscourse/data_1_3")countries = dir."10m_admin_0_countries"cities = dir."10m_populated_places_simple"rivers = dir."10m_rivers_lake_centerlines"
cStroke = new Stroke("white", 0.1)cities.style = new Shape(type: "square", size: 4, color: "#FF0000", stroke: cStroke, opacity: 0.5,rotation: 45)rivers.style = new Stroke(color: "#0000FF", width: 2, cap: 'round')
// make countries green with 80% transparend fill // and labeled with the name attributecountries.style = new Fill("green", 0.2) + new Stroke("green", 1) + new Label("NAME").font(size:10)
map = new Map(width:2400, height:1800)map.addLayer(countries)map.addLayer(rivers)map.addLayer(cities)map.render("/home/moovida/giscourse/mydata/world3.png")
Which would look like:
Advanced thematic styling
dir = new Directory("/home/moovida/giscourse/data_1_3")countries = dir."10m_admin_0_countries"cities = dir."10m_populated_places_simple"rivers = dir."10m_rivers_lake_centerlines"
cStroke = new Stroke("white", 0.1)cities.style = ( new Shape(type: "square", size: 10, color: "#FF0000", stroke: cStroke, opacity: 0.5,rotation: 45) + new Label("NAME").font(size:10) ).where("POP_MIN > 3000000")rivers.style = new Stroke(color: "blue", width: 3, cap: 'round').where("ScaleRank < 4") + new Stroke(color: "blue", width: 1, cap: 'round').where("ScaleRank >= 4")countries.style = (new Fill("red", 0.2) + new Stroke("red", 1)) .where("POP_EST > 80000000") + (new Fill("cyan", 0.2) + new Stroke("cyan", 1)) .where("POP_EST > 1000000 AND POP_EST <= 80000000") + (new Fill("green", 0.2) + new Stroke("green", 1)) .where("POP_EST < 1000000")
map = new Map(width:2400, height:1800)map.addLayer(countries)map.addLayer(rivers)map.addLayer(cities)map.render("/home/moovida/giscourse/mydata/world4.png")
Which would look like:
Create an SLD file
Assume you want to create a style file for the countries to use with a
shapefile in uDig:
// create the stylecountriesStyle = (new Fill("red", 0.2) + new Stroke("red", 1)) .where("POP_EST > 80000000") + (new Fill("cyan", 0.2) + new Stroke("cyan", 1)) .where("POP_EST > 1000000 AND POP_EST <= 80000000") + (new Fill("green", 0.2) + new Stroke("green", 1)) .where("POP_EST < 1000000") // wite the style to console (or file) new geoscript.style.io.SLDWriter().write(countriesStyle, System.out)
which will output something like:
<?xml version="1.0" encoding="UTF-8"?> <sld:UserStyle xmlns="http://www.opengis.net/sld" ...> <sld:Name>Default Styler</sld:Name> <sld:Title/> <sld:FeatureTypeStyle> <sld:Name>name</sld:Name> <sld:Rule> <ogc:Filter> ...