Second part of the Course "Java Open Source GIS Development - From the building blocks to extending an existing GIS application." held at the University of Potsdam in August 2011
LESTO - a GIS toolbox for LiDAR empowered sciences
Opensource gis development - part 2
1. Java Open Source GIS
Development
From the building blocks to extending
an existing GIS application.
Geoinformation Research Group, Department of Geography
University of Potsdam
August 2011
Part 2: Introduction of the main Geo-Objects
Tutor: Andrea Antonello
ydroloGIS nvironmental ngineering
HydroloGIS S.r.l. - Via Siemens, 19 - 39100 Bolzano www.hydrologis.com
2. A gentle introduction to the main geo Objects
This part aims to give the student, even if not a fully fledged developer, a
good usage-knowledge about the main geo-objects, leaving aside all the
architectural design and deep and dark computer science part. All in all the
aim is to get people to develop small algorithms in order to be able to extend
existing GIS frameworks such as uDig.
Some of the often used geo-objects that will be presented throughout this
part are:
Coordinate, GeometryFactory, Point, LineString, Polygon, Geometry,
CoordinateReferenceSystem, SimpleFeature, SimpleFeatureType, Simple-
FeatureCollection, GridCoverage2D, Filter
3. Two eclipse shortcuts before we start
Before we start right away with coding our first geo-objects, a short note
about shortcuts. Using shortcuts inside an IDE is extremely helpful. In eclipse
two shortcuts can save tons of time:
Ctrl^SPACE
This shortcut provides the context menu. Depending on the cursor position it
can for example complete variable or class names and supply a list of
possible methods on the current object.
Ctrl^1
This shortcut provides a quickfix menu, different depending on cursor
position. It can be used to rename variables, extract variable names, split
declarations and much more. It can even help you out to solve syntax errors.
4. Geometry (JTS)
The JTS library provides the following set of spatial data types:
Point
GeometryCollection
MultiPoint
LineString
MultiLineString
LinearRing
Polygon MultiPolygon
5. Coordinate
Coordinates are the building blocks of all spatial data types.
A Coordinate is a class that stores coordinates on the 2-dimensional
Cartesian plane. It has a simple constructor:
Coordinate coordinate2d = new Coordinate(11.0, 46.0);
It allows for a Z-coordinate, even if it is ignored for any operation:
Coordinate coordinate3d = new Coordinate(11.5, 46.0, 1200.0);
Which is why the only available operation on coordinates, the distance
calculation, returns 0.5 in the following case:
double distance = coordinate2d.distance(coordinate3d);
6. GeometryFactory
The GeometryFactory supplies a set of utility methods for building
Geometry objects. The following examples use it to build the main
geometries based on the main geographic building block, the Coordinate.
GeometryFactory factory = new GeometryFactory();
Create a Point:
Point point = factory.createPoint(coordinate1);
Create a LineString:
LineString line = factory.createLineString(new Coordinate[]{coordinate1, coordinate2});
Create a Polygon:
LinearRing ring = factory.createLinearRing(new Coordinate[]{coordinate1, coordinate2, coordinate3, coordinate1});
Polygon polygon = factory.createPolygon(ring, null);
7. Well Known Text (WKT)
The Well Known Text (WKT) representation of a geometry can be quite
handy, especially when creating testcases. It can be printed out simply by
invoking the toString() method of any geometry. A few examples:
• POINT (130 120)
• LINESTRING (50 380, 90 210, 180 160, 240 40, 240 40)
• POLYGON ((210 350, 230 310, 290 350, 290 350, 210 350))
The inverse, i.e. generating a Geometry object from WKT can be done
through the WKTReader:
String wktLine = "LINESTRING (50 380, 90 210, 180 160, 240 40, 240 40)";
WKTReader reader = new WKTReader();
Geometry readLine = reader.read(wktLine);
This representation of the geometries will help us to gain better
understanding of the different geometry types and operations.
8. (Multi)Point
A Point models a single Coordinate, a MultiPoint models a collection of
points.
A MultiPoint can be built through the factory:
MultiPoint multiPoint = factory.createMultiPoint(new Point[]{point1, point2, point3});
While the envelope of a point is the point itself, the envelope of a MultiPoint
geometry is the smalles envelop that contains all the points in the collection.
Geometry pointEnvelope = point1.getEnvelope();
Geometry multiPointEnvelope = multiPoint.getEnvelope();
9. (Multi)LineString
The line has a couple more properties than a point:
Geometry envelope = line.getEnvelope();
double length = line.getLength();
Point startPoint = line.getStartPoint();
Point endPoint = line.getEndPoint();
You can bundle lines in a collection:
MultiLineString multiLineString = factory.createMultiLineString(new LineString[]{line, line2});
Riddle: what is the area of a line?
double area = line.getArea();
And of a multiline?
double marea = multiLineString.getArea();
10. (Multi)Polygon
Polygons can also have holes, that are passed as second argument:
Polygon polygonWithHole = factory.createPolygon(linearRing, new LinearRing[]{hole});
The polygon has a couple more properties than a line:
Geometry envelope = polygon.getEnvelope();
double perimeter = polygon.getLength();
double area = polygon.getArea();
double areaWithHole = polygonWithHole.getArea();
Point centroid = polygon.getCentroid();
Geometry
The abstract class Geometry is the mother of all geometry classes.
The abstraction can be used for all geometric operations (for example
intersection and union).
11. Prj (GeoTools)
CoordinateReferenceSystem
WIKIPEDIA: "A spatial reference system (SRS) or coordinate reference
system (CRS) is a coordinate-based local, regional or global system used
to locate geographical entities. A spatial reference system defines a
specific map projection, as well as transformations between different
spatial reference systems. Spatial reference systems are defined by the
OGC's Simple feature access using well-known text, and support has been
implemented by several standards-based geographic information systems.
Spatial reference systems can be referred to using a SRID integer, including
EPSG codes defined by the International Association of Oil and Gas
Producers."
12. The Datum
The datum is a reference surface from which measurements are made
(Wikipedia).
Datums can be local, which are locally orientated ellissoid (no deviation on
the vertical, locally tangent), or global, which are used to cover the whole
globe and designed to support satellitar measurements.
global ellipsoid
local ellipsoid
geoid
13. Example Datums
Roma 40
local datum based on Hayford ellipsoid, with prime meridian on Monte
Mario
European Datum 50
local datum based on Hayford ellipsoid, tangent in Potsdam area,
with prime meridian in Greenwich. Used for UTM-ED50 CRS.
World Geodetic System WGS84
global datum with origin on the earth's mass center. Used for
example in the classic GPS CRS (EPSG:4326)
14. The Universal Transverse Mercator (UTM)
UTM maps the Earth with a transverse cylinder projection using 60 different
meridians, each of which is a standard "UTM Zone". By rotating the cylinder
in 60 steps (six degrees per step, about 800Km) UTM assures that all spots
on the globe will be within 3 degrees from the center of one of the 60
cylindrical projections.
15. Coordinate reprojection and transform, the (not so small) difference
Often reproject and transform are used the same way without much care.
There is a big difference though.
reproject
This is what we would call coordinate transform (CT). A CT can be
resolved in a well defined mathematical manner that doesn't lead to
precision loss (even if usually there is some minor due to data
precision and roundings).
transform
This is what we could call datum transform. Since datums are local
approximations of the geoid, transformations between datums are
based on statistical methods and lead most of the times to precision
loss.
16. GeoTools javadoc
• usually defined by a coordinate system (CS) and a datum
• captures the choice of values for the parameters that constitute the
degrees of freedom of the coordinate space
• since the choice is made either arbitrarily or by adopting values from
survey measurements, it leads to the large number of CRS in use around
the world
• is also the cause of the little understood fact that the latitude and longitude
of a point are not unique
• without the full specification of the CRS, coordinates are ambiguous at
best and meaningless at worst
17. CoordinateReferenceSystem (this time for real)
We now have the necessary (minimum) information to make use of CRS
inside our code and do some reprojecting. First let's create two CRS to work
with, one based on the Lat/long WGS84:
CoordinateReferenceSystem epsg4326Crs = CRS.decode("EPSG:4326");
and one based on WGS 84 / UTM zone 32N:
CoordinateReferenceSystem epsg32632Crs = CRS.decode("EPSG:32632");
To reproject coordinates first a math transform has to be created:
MathTransform transform = CRS.findMathTransform(epsg4326Crs, epsg32632Crs, lenient);
To actually reproject the JTS class supplies the method:
Coordinate newCoordinate = JTS.transform(coordinate, null, transform);
18. Vector (GeoTools)
SimpleFeature and SimpleFeatureType
In the course SimpleFeature will be used for Vector Data and represents
probably the most central object for GIS applications. The
SimpleFeatureType can be seen as the blueprint of the data. Vector data
are composed of a geometry part and an attribute table.
SimpleFeatureType:
the_geom: LineString
id: Integer
road1 name: String
length: Double
id name length
1 road1 56.4
road2
2 road2 120.0
geometry attributes
19. To create a feature, first the blueprint has to be created (feature type):
SimpleFeatureTypeBuilder featureTypeBuilder = new SimpleFeatureTypeBuilder();
featureTypeBuilder.setName("road");
featureTypeBuilder.setCRS(crs);
featureTypeBuilder.add("the_geom", LineString.class);
featureTypeBuilder.add("id", Integer.class);
featureTypeBuilder.add("name", String.class);
featureTypeBuilder.add("length", Double.class);
SimpleFeatureType featureType = featureTypeBuilder.buildFeatureType();
Using the blueprint, any feature can be created by defining its contents:
// feature 1
SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(featureType);
int id = 1;
String name = "road1";
double length = line1.getLength();
Object[] values = new Object[]{line1, id, name, length};
featureBuilder.addAll(values);
SimpleFeature feature1 = featureBuilder.buildFeature(null);
// feature 2
id = 2;
name = "road2";
length = line2.getLength();
values = new Object[]{line2, id, name, length};
featureBuilder.addAll(values);
SimpleFeature feature2 = featureBuilder.buildFeature(null);
20. How to extract the information once the features are packaged?
List<AttributeDescriptor> attributeDescriptors = featureType.getAttributeDescriptors();
for( AttributeDescriptor attributeDescriptor : attributeDescriptors ) {
// get the name of the attribute from the descriptor
String attributeName = attributeDescriptor.getLocalName();
// get the attribute value
Object attribute = feature1.getAttribute(attributeName);
// get the type of the attribute
AttributeType attributeType = attributeDescriptor.getType();
Class< ? > binding = attributeType.getBinding();
// print a summary
System.out.println(attributeName + " (type = " + binding.getSimpleName() + "): " + attribute);
}
Being the geometric part really important, for the default geometry we have a
direct access:
Geometry geometry = (Geometry) feature1.getDefaultGeometry();
21. SimpleFeatureCollection
Features can be bundled into collections (which is how they are usually
served by readers and fed into writers):
SimpleFeatureCollection newCollection = FeatureCollections.newCollection();
newCollection.add(feature1);
newCollection.add(feature2);
FeatureCollections are best accessed through an iterator, in order to assure
that they are not all read into memory:
SimpleFeatureIterator featureIterator = newCollection.features();
while( featureIterator.hasNext() ) {
SimpleFeature feature = featureIterator.next();
// do something with it
}
// remember to close the handle!
featureIterator.close();
22. Raster (GeoTools)
GridCoverage2D
A GridCoverage2D is what in the real world we usually call Raster or Grid,
i.e. a rectangular regular grid of pixels, each containing a value. The
following schema contains the main definitions we will use:
cols
grid space
raster values
north
rows
0,0 1200 1800 1800 1800
1200 1170 1130 1130
y (northing)
2,1 1200 1170 1130 1100
west
east
x res
y res
equator
x (easting) south
world space
23. Raster maps are not all that famous in GeoTools. People usually need them
to show nice ortophoto or similar imagery. This is probably the reason why
they have a quite tricky API. Building a GridCoverage2D object is quite a
pain.
In this course we will need to read and create raster data as for example
DTMs. To do so the JGrasstools API will be of help.
Let's start by defining some elevation data in a matrix:
double[][] elevationData = new double[][]{//
{800, 900, 1000, 1000, 1200, 1250, 1300, 1350, 1450, 1500}, //
{600, NaN, 750, 850, 860, 900, 1000, 1200, 1250, 1500}, //
{500, 550, 700, 750, 800, 850, 900, 1000, 1100, 1500}, //
{400, 410, 650, 700, 750, 800, 850, 490, 450, 1500}, //
{450, 550, 430, 500, 600, 700, 800, 500, 450, 1500}, //
{500, 600, 700, 750, 760, 770, 850, 1000, 1150, 1500}, //
{600, 700, 750, 800, 780, 790, 1000, 1100, 1250, 1500}, //
{800, 910, 980, 1001, 1150, 1200, 1250, 1300, 1450, 1500}//
};
24. We now need to place them somewhere in the world, which means defining
boundaries and reference system:
double n = 5140020.0;
double s = 5139780.0;
double w = 1640650.0;
double e = 1640950.0;
double xRes = 30.0;
double yRes = 30.0;
CoordinateReferenceSystem crs = CRS.decode("EPSG:32632");
The JGrasstools API supplies a method to create a GridCoverage from the
data defined before:
RegionMap envelopeParams = new RegionMap();
envelopeParams.put(CoverageUtilities.NORTH, n);
envelopeParams.put(CoverageUtilities.SOUTH, s);
envelopeParams.put(CoverageUtilities.WEST, w);
envelopeParams.put(CoverageUtilities.EAST, e);
envelopeParams.put(CoverageUtilities.XRES, xRes);
envelopeParams.put(CoverageUtilities.YRES, yRes);
// build the coverage
GridCoverage2D elevationRaster = CoverageUtilities.buildCoverage( //
"elevation", elevationData, envelopeParams, crs, true);
25. To access data in a raster, we can either work in the world space (easting,
northing):
double[] value = elevationRaster.evaluate(new Point2D.Double(1640680, 5139820), (double[]) null);
or in the grid space (col, row):
value = elevationRaster.evaluate(new GridCoordinates2D(1, 6), (double[]) null);
It is possible to access the underlying image to iterate over it:
RenderedImage elevationRI = elevationRaster.getRenderedImage();
RandomIter iter = RandomIterFactory.create(elevationRI, null);
for( int col = 0; col < elevationRI.getWidth(); col++ ) {
for( int row = 0; row < elevationRI.getHeight(); row++ ) {
double elev = iter.getSampleDouble(col, row, 0);
System.out.print(elev + " ");
}
System.out.println();
}
26. Filters
A Filter defines a constraint that can be checked against an instance of an
object.
We will handle filters only with regard to vector data.
A filter can be seen as the WHERE clause of an SQL statement. It can apply
both to the alphanumeric values of an attribute table as well as to the
geometry.
One example could be: give me all the cities of Canada that count more than
10000 inhabitants.
27. CQL and ECQL
To help people in the construction of filters GeoTools supplies the class
ECQL.
Creating a filter that compares the string inside a field is therefore simple:
// name comparison filter
Filter filter = ECQL.toFilter("name = 'road1'");
as is the comparison of numeric values:
// numeric comparison filter
filter = ECQL.toFilter("length < 1.0");
one could also check the real length of a geometry, instead of the value in a
field:
// geometric filter
filter = ECQL.toFilter("geomLength(the_geom) < 1.0");
28. it is possible to check if a field exists:
// field existence
filter = ECQL.toFilter("name EXISTS");
regular expressions can be used to compare strings:
// like comparison
filter = ECQL.toFilter("name LIKE 'roa%' AND length > 1.0");
and maybe the most used check, the bounding box:
// bounding box
double w = 10.5;
double e = 11.6;
double s = 46.0;
double n = 47.0;
String filterString = "BBOX(the_geom, " + w + "," + s + "," + e + "," + n + ")";
filter = ECQL.toFilter(filterString);
29. The created filter can then for example be applied to a collection to extract a
subcollection based on the filter constraint:
SimpleFeatureCollection subCollection = collection.subCollection(filter);
30. Style
Style is that part that allows us to make maps look pretty and get the needed
symbolization and coloring of the contained data.
The OGC defines the Styled Layer Descriptor (SLD) as the standard to
style maps. GeoTools supplies an API to generate styles.
The programmatic approach to Style won't be handled in this course. To
create SLD styles, the style editor of uDig can be used.
31. Conclusions
In part 2 we played with the main objects used when doing GIS development.
We learned about the atomic entity, the Coordinate, how to build various
geometries through the GeometryFactory. We built Point, LineString and
Polygon geometries and now know that all their common behaviour can be
accessed through the Geometry class.
We then added the spatial context to the geometries by approaching the
CoordinateReferenceSystem. In the case of vectors, we created a blueprint
of the vector data as a SimpleFeatureType, we "populated" it creating the
actual SimpleFeature and bundled it to a SimpleFeatureCollection. We also
learned how to extract data from collections based on a Filter.
In the case of rasters we learned how to create a GridCoverage2D.
32. This work is released under Creative Commons Attribution Share
Alike (CC-BY-SA)
Much of the knowledge needed to create this training material has
been produced by the sparkling knights of the GeoTools, JTS and
uDig community. Another essential source has been the Wikipedia
community effort.
Particular thanks go to those friends that directly or indirectly helped
out in the creation and review of this developer's handbook: Jody
Garnett from the uDig/GeoTools community and the TANTO team.
This tutorial was written with the support of the Geoinformation
Research Group of the University of Potsdam and HydroloGIS.