Access information from GeoTIFF using Java
I’m always very interested in combining information from various open data sources together. The last couple of weeks I’ve been working with GIS related information which is freely available within the Netherlands. To be more precise I’ve been experimenting with data from:
- BAG: Is dutch for Basis Administratie Gebouwen, and contains information on all the building in the netherlands.
- OSM: Openstreetmap provides user provisioned maps and additional information
I loaded this data into Postgis, so I could easily play around and experiment with it. Using this data its relatively easy to create maps like this:
My original goal was to use this information and create 3D models from them using Three.js. But, unfortunately, the height of buildings wasn’t provided by these data sources. Luckily though there was an additional source of information that could be used. Through the AHN (Actueel Hoogtebestand Nederland) you can download heightmaps from different parts of the Netherlands. One of the sources they provide is the raw unfiltered data as a raster file. This means that the building, bridges etc. are still in there and not filtered out. For instance, such a file looks like this:
This file contains coordinate information and for each coordinate the height is represented as a gray scale value. The information in this file is stored in a format called geoTIFF. So my idea was, to get the coordinate from the buildings in the database. Stored as normal GIS objects in postGIS, and correlate that with the height information from the geoTIFF file to determine the height. Probably won’t have the best accuracy, but should give a good enough value.
I first looked at directly loading this information in postGIS and using some queries to get the correct values, but apparently this only works from postGIS 2.0 and higher, and I didn’t feel like upgrading and reloading all the GIS data. So after some searching I ran into the org.geotools library which has a large amount of GIS related javatools. And one of them handles loading geoTIFF files for further processing. So I created a simple java program to load the data from postGIS, enrich it with information from the geoTIFF and update the database.
To get everything working I used the following maven pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>geotiff</groupId>
<artifactId>geotiff</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-geotiff</artifactId>
<version>12.0.1</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-epsg-hsql</artifactId>
<version>12.0.1</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.3-1102-jdbc41</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.6</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>maven2-repository.dev.java.net</id>
<name>Java.net repository</name>
<url>http://download.java.net/maven/2</url>
</repository>
<repository>
<id>osgeo</id>
<name>Open Source Geospatial Foundation Repository</name>
<url>http://download.osgeo.org/webdav/geotools/</url>
</repository>
<repository>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>boundless</id>
<name>Boundless Maven Repository</name>
<url>http://repo.boundlessgeo.com/main</url>
</repository>
</repositories>
</project>
With the libraries in place the actual code isn’t that complex. It takes some time experimenting with all the different objects and methods, since I couldn’t find any good docmentation. The following, though, is the complete java code which loads the geoTIFF and accesses the height information based on a location from postGIS. Note that in this case both projections are the same (SRID 28992), so I didn’t need to convert projections:
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.geotools.coverage.grid.GridCoordinates2D;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.gce.geotiff.GeoTiffReader;
import org.geotools.geometry.DirectPosition2D;
import java.awt.image.Raster;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
/**
* Created with IntelliJ IDEA.
* User: jos
* Date: 26/10/14
* Time: 19:26
* To change this template use File | Settings | File Templates.
*/
public class GeoTIFFTest {
private final static String url = "jdbc:postgresql://localhost/bag";
private static GridCoverage2D grid;
private static Raster gridData;
public static void main(String[] args) throws Exception {
initTif();
loadData();
}
private static void initTif() throws Exception {
File tiffFile = new File("/Volumes/Iomega_HDD/mac/data/r44hn1.tif");
GeoTiffReader reader = new GeoTiffReader(tiffFile);
grid =reader.read(null);
gridData = grid.getRenderedImage().getData();
}
private static double getValue(double x, double y) throws Exception {
GridGeometry2D gg = grid.getGridGeometry();
DirectPosition2D posWorld = new DirectPosition2D(x,y);
GridCoordinates2D posGrid = gg.worldToGrid(posWorld);
// envelope is the size in the target projection
double[] pixel=new double[1];
double[] data = gridData.getPixel(posGrid.x, posGrid.y, pixel);
return data[0];
}
private static void loadData() throws Exception {
Connection conn = DriverManager.getConnection(url);
QueryRunner runner = new QueryRunner();
final Map<Long, Double> map = new HashMap<Long, Double>();
ResultSetHandler handler = new ResultSetHandler() {
@Override
public Object handle(ResultSet resultSet) throws SQLException {
while (resultSet.next()) {
String point = resultSet.getString("point");
double x = Double.parseDouble(point.substring(
point.indexOf('(') + 1,
point.indexOf(' ')
));
double y = Double.parseDouble(point.substring(
point.indexOf(' ') + 1,
point.indexOf(')')
));
try {
double hoogte = getValue(x, y);
map.put(resultSet.getLong("gid"),hoogte);
} catch (Exception e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
return null; //To change body of implemented methods use File | Settings | File Templates.
}
};
runner.query(conn, "SELECT gid, ST_AsText(ST_Centroid(geovlak)) as point \n" +
"FROM bag8mrt2014.pand\n" +
"WHERE geovlak && ST_MakeEnvelope(130153, 408769,132896, 410774, 28992) ORDER by gid ;", handler);
int count = 0;
for (Long key : map.keySet()) {
System.out.println("Inserting for key = " + key + " value: " + map.get(key));
int col = runner.update(conn, "UPDATE bag8mrt2014.pand SET hoogte= ? where gid = ?",
map.get(key), key);
count++;
if (count%100 == 0) {
System.out.println("count = " + count);
}
}
}
}
So with the information updated I now also have height information in the database, so I can start looking at rendering this information in 3D. First experiments are pretty hopeful :)