Geotools in der Kartenwerkstatt
BearbeitenGeotools ist eine (mittlerweile sehr umfangreiche) freie Java-Bibliothek zum Manipulieren, Verarbeiten und Darstellen von Geodaten. Für die Kartenherstellung ist Geotools insbesondere mit seinen Fähigkeiten interessant, aus diversen Geodatenformaten Karten in PNG oder auch SVG zu zeichnen.
Ein ganz einfaches Beispiel
BearbeitenAls Quelldaten haben wir eine Shape-Datei (zB aus der TIGER-Datenbank der US-Census-Behörde) und möchte diese in eine SVG-Datei überführen.
Der folgende Code erstellt ein MapContext-Objekt mit einem Layer aus den Daten der Shape-Datei. Als Kartenprojektion wird hier die "North America Lambert Conformal Conic" gewählt. Der Layer bekommt noch einen Symbolizer als Darstellungsmerkmal (hier einfach: Farbe Schwarz und 2px Breite).
// Aus einem Shapefile wird zunächst ein Datastore geöffnet
File shapeFile = new File("tl_2008_us_state.shp");
ShapefileDataStore dataStore = new ShapefileDataStore(shapeFile.toURI().toURL());
List<MapLayer> layers = new ArrayList<MapLayer>();
// .. mit dem Datastore wird ein Layer erstellt (mitsamt grafischer Ausprägung)
StyleBuilder sb = new StyleBuilder();
Symbolizer sym = sb.createLineSymbolizer(Color.BLACK, 2);
layers.add(new DefaultMapLayer(dataStore.getFeatureSource(), sb.createStyle(sym)));
// Erstelle eine Karte aus 1 Layer und Referenzsystem "North America Lambert Conformal Conic"
MapContext mapContext = new DefaultMapContext(layers.toArray(new MapLayer[]{})
, CRS.decode("EPSG:102009"));
// Es folgt das Erstellen der SVG-Datei
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.getDOMImplementation().createDocument(
"http://www.w3.org/2000/svg", "svg", null);
SVGGeneratorContext context = SVGGeneratorContext.createDefault(document);
SVGGraphics2D g = new SVGGraphics2D(context, true);
g.setSVGCanvasSize(new Dimension(900, 500));
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_OFF);
Rectangle rect = new Rectangle(g.getSVGCanvasSize());
// Der Renderer übernimmt schliesslich das "Zeichnen" der Karte,
// in dem Fall direkt in die SVG-Datei rein
StreamingRenderer renderer = new StreamingRenderer();
renderer.setContext(mapContext);
Map rendererParams = new HashMap();
rendererParams.put("optimizedDataLoadingEnabled", Boolean.TRUE);
rendererParams.put(StreamingRenderer.OPTIMIZE_FTS_RENDERING_KEY, Boolean.TRUE);
rendererParams.put(StreamingRenderer.LINE_WIDTH_OPTIMIZATION_KEY, Boolean.FALSE);
renderer.setRendererHints(rendererParams);
// Als Parameter werden hier noch übergeben:
// paintArea: Ausgabebereich der SVG-Seite
// mapArea: Eine Boundingbox mit Kartenkoordinaten
renderer.paint(g, rect, mapContext.getAreaOfInterest());
g.stream("test.svg");
Das Resultat dieses Beispiels sieht dann wie folgt aus:
Darstellungsregeln mit dem Symbolizer
BearbeitenAls nächstes soll mit Hilfe des Symbolizers abhängig von einer Filterregel eine abweichende Darstellung gewählt werden. Im folgenden Beispiel also: wenn NAME=Texas, dann andere Farbe
StyleBuilder sb = new StyleBuilder();
Symbolizer sym = sb.createPolygonSymbolizer(new Color(246,180,160));
Rule rule1 = sb.createRule(sym);
rule1.setFilter(CQL.toFilter("NAME = 'Texas'"));
Symbolizer sym2 = sb.createPolygonSymbolizer(new Color(200,200,200));
Rule rule2 = sb.createRule(sym2);
rule2.setIsElseFilter(true);
FeatureTypeStyle fts = sb.createFeatureTypeStyle("Feature", new Rule[]{rule1, rule2});
Style style = sb.createStyle();
style.addFeatureTypeStyle(fts);
layers.add(new DefaultMapLayer(dataStore.getFeatureSource(), style));
sym = sb.createLineSymbolizer(Color.BLACK, 1);
Am oberen Beispiel erkennt man auch ein kleines Manko des SVG-Renderers von Geotools: nämlich dass man entweder nur Linien oder nur Flächen in einem Layer zeichnen kann - jedoch keine gefüllten Objekte mit Umrandund. Dazu muss man 2 separate Layer übereinander legen. Der Grund dieses Mankos liegt in der Grafik-Klasse von Java, die dies nicht unterstützt. Eine andere Alternative wäre: man lässt den Renderer nur die Flächen zeichnen und setzt die Umrandung später selbst (zB in einem SVG-Editor wie Inkscape).
Den Kartenausschnitt wählen (Bounding Box)
BearbeitenIm oberen Beispiel wurde mittels mapContext.getAreaOfInterest() einfach der gesamte Kartenbereich exportiert. Möchte man aber zB nur den Staat Vermont exportieren, so kann man zB die Bounding Box des Staates Vermonts heraussuchen und diese dann als Paramter beim Export angeben.
ReferencedEnvelope bbox = null;
FeatureCollection<SimpleFeatureType, SimpleFeature> collection = dataStore.getFeatureSource().getFeatures();
FeatureIterator<SimpleFeature> iterator=collection.features();
while(iterator.hasNext()) {
SimpleFeature feature = iterator.next();
String state = (String)feature.getProperty("NAME").getValue();
// Nach einem Feature namens "Vermont" suchen
if (state.equalsIgnoreCase("Vermont")) {
bbox = (ReferencedEnvelope)feature.getBounds();
// und noch um 0.2° erweitern
bbox.expandBy(0.2);
}
}
// Wichtig: die BBOX muss noch in das Referenzsystem der Karte umgerechnet werden
bbox = bbox.transform(mapContext.getCoordinateReferenceSystem(), true);
Der nächste Schritt beschäftigt sich mit dem Berechnen der Höhe und Breite (entspr. des Ausgangsseitenverhältnisses)
double width = -1;
double height = -1;
if ((bbox.getHeight() > 0) && (bbox.getWidth() > 0)) {
if (bbox.getHeight() >= bbox.getWidth()) {
height = 500;
width = height * (bbox.getWidth() / bbox.getHeight());
} else {
width = 500;
height = width * (bbox.getHeight() / bbox.getWidth());
}
Anschließend geht es dann wieder zum Rendern. Doch diesmal wird vorher noch ein Clipping-Rechteck (also ein Ausschneidepfad) gesetzt, da sonst einige Teile der Karte über den Seitenrand der SVG-Datei ragen.
g.setSVGCanvasSize(new Dimension((int)width, (int)height));
…
g.setClip(0, 0, (int) width, (int) height);
renderer.paint(g, rect, bbox);