In the article, I show how to read such GIS data from ESRI type database files using open source library GeoTools.
The data itself comes for free from www.naturalearthdata.com.
Sample code can be found here: Browse on GitHub.
GIS data usually comes in form of SHP and DBF files. In order to read it, we use GeoTools parser. Following code iterates over so called "features" from within data files and retrieves name attribute and shape geometry.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File file = new File("110m_cultural\\110m_admin_0_countries.shp"); | |
FileDataStore store = FileDataStoreFinder.getDataStore(file); | |
SimpleFeatureSource featureSource = store.getFeatureSource(); | |
SimpleFeatureCollection c = featureSource.getFeatures(); | |
SimpleFeatureIterator featuresIterator = c.features(); | |
Coordinate[] coords; | |
Geometry polygon; | |
Point centroid; | |
Bounds bounds; | |
while (featuresIterator.hasNext()) { | |
SimpleFeature o = featuresIterator.next(); | |
String name = (String) o.getAttribute("NAME"); | |
Object geometry = o.getDefaultGeometry(); | |
} | |
Next, we need to create JavaFX polygons for each feature from iteration. Small note here. Each feature may comprise of multiple polygons. For example "United States" shape may contain separate polygon for Alaska. So we need additional loop to generate such polygons.
In order to create a polygon in JavaFX, we use Path class along with MoveTo and LineTo path elements. Following snippet does the job.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if (geometry instanceof MultiPolygon) { | |
MultiPolygon multiPolygon = (MultiPolygon) geometry; | |
centroid = multiPolygon.getCentroid(); | |
final Text text = new Text(name); | |
bounds = text.getBoundsInLocal(); | |
text.getTransforms().add(new Translate(centroid.getX(), centroid.getY())); | |
text.getTransforms().add(new Scale(0.1,-0.1)); | |
text.getTransforms().add(new Translate(-bounds.getWidth()/2., bounds.getHeight()/2.)); | |
texts.getChildren().add(text); | |
for (int geometryI=0;geometryI<multiPolygon.getNumGeometries();geometryI++) { | |
polygon = multiPolygon.getGeometryN(geometryI); | |
coords = polygon.getCoordinates(); | |
Path path = new Path(); | |
path.setStrokeWidth(0.05); | |
currentColor = (currentColor+1)%colors.length; | |
path.setFill(colors[currentColor]); | |
path.getElements().add(new MoveTo(coords[0].x, coords[0].y)); | |
for (int i=1;i<coords.length;i++) { | |
path.getElements().add(new LineTo(coords[i].x, coords[i].y)); | |
} | |
path.getElements().add(new LineTo(coords[0].x, coords[0].y)); | |
map1.getChildren().add(path); | |
} | |
} |
The remaining part is to implement zoom and panning functionality. This is fairly easy in JavaFX. We can use translate and scale properties from main Group shape. Panning functionality is handled using following snippet:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
map.setOnMousePressed(new EventHandler<MouseEvent>() { | |
public void handle(MouseEvent event) { | |
scene.setCursor(Cursor.MOVE); | |
dragBaseX = map.translateXProperty().get(); | |
dragBaseY = map.translateYProperty().get(); | |
dragBase2X = event.getSceneX(); | |
dragBase2Y = event.getSceneY(); | |
} | |
}); | |
map.setOnMouseDragged(new EventHandler<MouseEvent>() { | |
public void handle(MouseEvent event) { | |
map.setTranslateX(dragBaseX + (event.getSceneX()-dragBase2X)); | |
map.setTranslateY(dragBaseY + (event.getSceneY()-dragBase2Y)); | |
} | |
}); | |
map.setOnMouseReleased(new EventHandler<MouseEvent>() { | |
public void handle(MouseEvent event) { | |
scene.setCursor(Cursor.DEFAULT); | |
} | |
}); |
Zoom is coded this way:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private void zoom(double d) { | |
map.scaleXProperty().set(map.scaleXProperty().get() * d); | |
map.scaleYProperty().set(map.scaleYProperty().get() * d); | |
} | |
... | |
VBox vbox = new VBox(); | |
final Button plus = new Button("+"); | |
plus.setOnAction(new EventHandler<ActionEvent>() { | |
public void handle(ActionEvent event) { | |
zoom(1.4); | |
} | |
}); | |
vbox.getChildren().add(plus); | |
final Button minus = new Button("-"); | |
minus.setOnAction(new EventHandler<ActionEvent>() { | |
public void handle(ActionEvent event) { | |
zoom(1./1.4); | |
} | |
}); | |
vbox.getChildren().add(minus); | |
root.getChildren().add(vbox); |
That's it. Now we have basic GIS data viewer in JavaFX 2.