28
nvironmental ngineering ydroloGIS HydroloGIS S.r.l. - Via Siemens, 19 - 39100 Bolzano www.hydrologis.com 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 3: Vector data Tutor: Andrea Antonello

Opensource gis development - part 3

Embed Size (px)

DESCRIPTION

Thrid 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

Citation preview

Page 1: Opensource gis development - part 3

nvironmental ngineeringydroloGISHydroloGIS S.r.l. - Via Siemens, 19 - 39100 Bolzano www.hydrologis.com

Java Open Source GISDevelopment

From the building blocks to extendingan existing GIS application.

Geoinformation Research Group, Department of GeographyUniversity of Potsdam

August 2011

Part 3: Vector dataTutor: Andrea Antonello

Page 2: Opensource gis development - part 3

Vector basics

The first thing people approaching GIS development want to do is to

visualize the world boundaries map with main cities from a shapefile. In the

first part of this course we tried the quickstart mode, now we want to have

more control over the data we visualize and also want to write filtered data to

a new shapefile.

The parts we still need to learn are:

• read the features from the shapefile

• write features to a shapefile

• view the result in a simple viewer (or did we learn that already?)

Page 3: Opensource gis development - part 3

How to read data from a shapefile

A shapefile can be read through the appropriate data store. The

DataStoreFinder becomes handy to help in the case one doesn't know

the class to use:

// create a parameter map Map<String, URL> paramMap = Collections.singletonMap("url", shapeFileUrl); // the datastore finder supplies the appropriate store for the passed url DataStore store = DataStoreFinder.getDataStore(paramMap);

// get the data source to extract the features SimpleFeatureSource featureSource = store.getFeatureSource(store.getTypeNames()[0]); SimpleFeatureCollection featureCollection = featureSource.getFeatures();

The same could have been achieved by using directly the shapefile

datastore:

store = new ShapefileDataStore(shapeFile.toURI().toURL());

We already learned how to handle vector data.

Page 4: Opensource gis development - part 3

How to write data to a shapefile

Writing data to a shapefile requires the creation of a compatible datastore.

Right now we only want to write down the data read before, which means

that we can use the same blueprint of the data (SimpleFeatureType):

ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory(); ShapefileDataStore newDataStore = (ShapefileDataStore) dataStoreFactory.createNewDataStore(params); SimpleFeatureType schema = featureCollection.getSchema(); newDataStore.createSchema(schema);

The data are then written to file using a FeatureStore and a Transaction:

Transaction transaction = new DefaultTransaction("create"); String typeName = schema.getTypeName(); SimpleFeatureSource outFeatureSource = newDataStore.getFeatureSource(typeName); if (outFeatureSource instanceof SimpleFeatureStore) { SimpleFeatureStore featureStore = (SimpleFeatureStore) outFeatureSource; featureStore.setTransaction(transaction); try { featureStore.addFeatures(featureCollection); transaction.commit(); } catch (Exception problem) { problem.printStackTrace(); transaction.rollback(); } finally { transaction.close(); } }

Page 5: Opensource gis development - part 3

How to view a shapefile

To check if the file was properly created, we can use the simple map viewer

that GeoTools provides:

store = DataStoreFinder.getDataStore(params); featureSource = store.getFeatureSource(store.getTypeNames()[0]); MapContext map = new MapContext(); map.setTitle("Check"); Style style = SLD.createSimpleStyle(featureSource.getSchema()); Layer layer = new FeatureLayer(featureSource, style); map.addLayer(layer); JMapFrame.showMap(map);

You should see something like:

Page 6: Opensource gis development - part 3

You might argue that it is boring to read and write the original data. It is a

good point, which is why we will instead apply a filter in order to create a new

file containing only those countries that have an estimated population major

than 100 millions. Need a hint? Here it is, apply it where appropriate:

Filter filter = ECQL.toFilter("POP_EST > 100000000");

And it should look like the following map:

Page 7: Opensource gis development - part 3

How to write a shapefile from scratch

When creating data from scratch, the first step is the design of its structure.

Once the design is solid, it can be programmatically described through the

featuretype.

Let's assume we want to create a lat/long grid to overlay on the world

countries map. We want it to look like the following map:

Page 8: Opensource gis development - part 3

The grid we will create has a simple structure. We basically need a set of

lines and an attribute for every line that represents its name:

SimpleFeatureTypeBuilder ftBuilder = new SimpleFeatureTypeBuilder(); ftBuilder.setName("grid"); ftBuilder.setCRS(DefaultGeographicCRS.WGS84); ftBuilder.add("the_geom", LineString.class); ftBuilder.add("name", String.class); SimpleFeatureType ft = ftBuilder.buildFeatureType();

Next we create a hashmap containing as key the name of the line and as

value the line itself:

HashMap<String, LineString> name2LinesMap = new HashMap<String, LineString>(); // create lon lines for( int lon = -180; lon <= 180; lon = lon + 10 ) { Coordinate start = new Coordinate(lon, -90); Coordinate stop = new Coordinate(lon, 90); LineString line = geometryFactory.createLineString(new Coordinate[]{start, stop}); name2LinesMap.put("lon: " + lon, line); }

The same hase to be done for the latitude lines.

Page 9: Opensource gis development - part 3

Using the blueprint we created before, we now create the collection of

features of the grid:

SimpleFeatureBuilder builder = new SimpleFeatureBuilder(ft); SimpleFeatureCollection newCollection = FeatureCollections.newCollection(); Set<Entry<String, LineString>> name2LineEntrySet = name2LinesMap.entrySet(); for( Entry<String, LineString> name2Line : name2LineEntrySet ) { Object[] values = new Object[]{name2Line.getValue(), name2Line.getKey()}; builder.addAll(values); SimpleFeature feature = builder.buildFeature(null); newCollection.add(feature); }

It is left as an exercise to write this feature collection to a shapefile and

visualize it.

Page 10: Opensource gis development - part 3

A quick exercise before we start with geometries

Read a CSV file containing geographic points and transform it to a shapefile.

The data to use are the postal code data that can be found on the

GeoNames Download Server.

The attribute table has to contain:

• a name of the point (field 2)

• the country code (field 9)

• the elevation (field 16)

Also filter out only those points within a distance of about 1 degree from

Potsdam.

Page 11: Opensource gis development - part 3

Geometry basics

Handling vector data is about two things:

• geometry

• attributes

We already learned how to create attributes and query/filter them.

Now, we will dedicate some time to the essence of the spatial data: the

geometry.

In Java, GIS geometry is synonymous of Java Topology Suite (JTS), the API

for geometric operations.

In the following sections we will explore both JTS functions (buffer, convex

hull, intersection, symdifference, union) and predicates (contains, covers,

overlaps, intersects).

Page 12: Opensource gis development - part 3

A 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

Page 13: Opensource gis development - part 3

The JTS testbuilder

When testing code that handles geometries, it would be handy to see what

we are doing. If we test an intersection between two geometries, we are able

to print out the text representation of any of those geometries, but that

doesn't really help, we need a visual feedback.

The JTS suite supplies a tool called testbuilder, which is able to visualize the

text representation of the geometries, as well as perform every operation we

will see in the next section.

To install the testbuilder, go to the JTS download area, get the latest version

of JTS and uncompress it somewhere on your disk. Inside the extracted

folder you will find a file named bin/testbuilder.sh (.bat for

windows). Execute it with:

sh bin/testbuilder.sh

Page 14: Opensource gis development - part 3

Try to print to standard output the geometries created before through:

System.out.println(geometryName);

and paste the result in the testbuilder. This will help a lot throughout the

following exercises.

Page 15: Opensource gis development - part 3

Predicates

Intersects

Let's see which geometries intersect with g1 and print the result:

System.out.println(g1.intersects(g2)); // true System.out.println(g1.intersects(g3)); // true System.out.println(g1.intersects(g4)); // true System.out.println(g1.intersects(g5)); // true System.out.println(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:

System.out.println(g1.touches(g2)); // true System.out.println(g1.touches(g3)); // false System.out.println(g1.touches(g4)); // true System.out.println(g1.touches(g5)); // false System.out.println(g1.touches(g6)); // false

Page 16: Opensource gis development - part 3

Contains

Let's test which geometries are contained by g1 and print the result:

System.out.println(g1.contains(g2)); // false System.out.println(g1.contains(g3)); // true System.out.println(g1.contains(g4)); // false System.out.println(g1.contains(g5)); // false System.out.println(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

System.out.println(g1.covers(g2)); // false System.out.println(g1.covers(g3)); // true System.out.println(g1.covers(g4)); // true System.out.println(g1.covers(g5)); // false System.out.println(g1.covers(g6)); // false

As you can see, now also g4 is covered.

Page 17: Opensource gis development - part 3

Functions

Intersection

What is the resulting geometry of the intersection of different geometry

types?

// the intersection of polygons returns a polygon System.out.println(g1.intersection(g6)); // but the intersection of touching polygons returns a line System.out.println(g1.intersection(g2)); // the intersection of a polygon with a point is a point System.out.println(g1.intersection(g3)); // the intersection of a polygon with a line is a point System.out.println(g1.intersection(g5));

The following shows the intersection of polygons g1 and g6:

0

5

0 5

g1

g5

g2g3

g4

g6

Page 18: Opensource gis development - part 3

Symdifference

What is the resulting geometry of the symdifference of different geometry

types?

// the symDifference of intersecting polygons returns a multipolygon System.out.println(g1.symDifference(g6)); // but the symDifference of touching polygons returns the polygons union System.out.println(g1.symDifference(g2)); // the symDifference of a polygon with a contained point returns the original polygon System.out.println(g1.symDifference(g3)); // the symDifference of a polygon with a line is a hybrid collection (line + polygon) System.out.println(g1.symDifference(g5));

The following shows the symdifference of polygons g1 and g6:

0

5

0 5

g1

g5

g2g3

g4

g6

Page 19: Opensource gis development - part 3

Union

What is the resulting geometry of the union of different geometry types?

// the union of intersecting polygons returns a polygon System.out.println(g1.union(g6)); // same as the union of touching polygons System.out.println(g1.union(g2)); // the union of a polygon with a contained point is a point returns the original polygon System.out.println(g1.union(g3)); // the union of a polygon with a line is a hybrid collection (line + polygon) System.out.println(g1.union(g5));

The following shows the union of polygons g1 and g6:

0

5

0 5

g1

g5

g2g3

g4

g6

Page 20: Opensource gis development - part 3

Difference

The difference of geometries obviously depends on the calling object:

// this returns g1 minus the overlapping part of g6 System.out.println(g1.difference(g6)); // while this returns g6 minus the overlapping part of g1 System.out.println(g6.difference(g1)); // in the case of difference with lines, the result is the original polygon // with additional points in the intersections System.out.println(g1.difference(g2)); // the difference of polygon and point is the original polygon System.out.println(g1.difference(g3));

The following shows the difference of polygons g1 and g6:

0

5

0 5

g1

g5

g2g3

g4

g6

Page 21: Opensource gis development - part 3

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 point System.out.println(g3.buffer(1.0)); // the buffer of a point with few quandrant segments System.out.println(g3.buffer(1.0, 1)); // round end cap style, few points System.out.println(g5.buffer(1.0, 2, BufferParameters.CAP_ROUND)); // round end cap style, more points System.out.println(g5.buffer(1.0, 10, BufferParameters.CAP_ROUND)); // flat end cap style System.out.println(g5.buffer(1.0, -1, BufferParameters.CAP_FLAT));

Page 22: Opensource gis development - part 3

Convex Hull

To test the convext hull operation, first let's create a geometry collection

containing the line and all the polygons:

GeometryCollection geometryCollection = new GeometryCollection(new Geometry[]{g1, g2, g5, g6}, new GeometryFactory());

Since the GeometryCollection extends Geometry, we can apply on it the

convex hull operation the same way we would do with any geometry:

System.out.println(geometryCollection.convexHull());

Page 23: Opensource gis development - part 3

Frequently used operations and tricks

Find the shortest line between two geometries

The DistanceOp class gives the possibility to find the nearest points of two

geometries, that given, creating the line is easy:

Coordinate[] nearestPoints = DistanceOp.nearestPoints(g2, g5); LineString shortestLine = geometryFactory.createLineString(nearestPoints);

Offset line

To create an offset line, the single sided buffer operation can be used:

BufferParameters params = new BufferParameters(); params.setSingleSided(true); // positive buffer is left Geometry offsetBuffer = BufferOp.bufferOp(g5, 1, params);

Exercise: how can we extract the offset line from the single sided buffer?

Page 24: Opensource gis development - part 3

Perpendicular line

A perpendicular line can be created with the help of the LineSegment. It

has the ability to calculate points along the segment, even with offset.

Create the LineSegment based on the line coordinates:

Coordinate[] coordinates = g5.getCoordinates(); LineSegment lineSegment = new LineSegment(coordinates[0], coordinates[1]);

We will create a perpendicular line of the same length of the original line. We

therefore compute the points at the left and right side of the original line

through an offset starting from the middle point of the segment (0.5). The

obtained points are then used as start and endpoint of the perpendicular line:

double length = lineSegment.getLength(); Coordinate leftPoint = lineSegment.pointAlongOffset(0.5, length / 2.0); Coordinate rightPoint = lineSegment.pointAlongOffset(0.5, -length / 2.0); LineString perpendicularLine = geometryFactory.createLineString(new Coordinate[]{leftPoint, rightPoint});

Page 25: Opensource gis development - part 3

Optimizing operations with prepared geometries

To gain performance in repeating operations, the class PreparedGeometry is

supplied together with a factory to actually prepare any geometry. Operations

like contains or covers can execute more than 100 times faster than the

original Geometry methods:

PreparedGeometry preparedPolygon = PreparedGeometryFactory.prepare(g1); boolean contains = preparedPolygon.contains(g3);

Page 26: Opensource gis development - part 3

Exercises

• create train tracks from the WKT line: LINESTRING (50 380, 90 210, 180

160, 240 40, 240 40) (hint: LengthIndexedLine is useful).

• extract the offset line from a single sided buffer.

Page 27: Opensource gis development - part 3

Geometry/Attributes Exercises

For the following exercises use the countries layer (admin countries from

Natural Earth) and the main cities layer (populated places).

• find the smallest and largest country

• calculate, using the country layer, how many people live inside every UTM

zone (assuming an uniform distribution of population)

• calculate, using the country and city layer, the percentage of the people of

each country living in the main cities.

Page 28: Opensource gis development - part 3

This work is released under Creative Commons Attribution ShareAlike (CC-BY-SA)

Much of the knowledge needed to create this training material hasbeen produced by the sparkling knights of the GeoTools, JTS anduDig community. Another essential source has been the Wikipediacommunity effort.

Particular thanks go to those friends that directly or indirectly helpedout in the creation and review of this developer's handbook: JodyGarnett from the uDig/GeoTools community and the TANTO team.

This tutorial was written with the support of the GeoinformationResearch Group of the University of Potsdam and HydroloGIS.