Benutzer:Alexrk2/Geotools

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen

Geotools in der Kartenwerkstatt

[Bearbeiten | Quelltext bearbeiten]

Geotools 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

[Bearbeiten | Quelltext bearbeiten]

Als 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:

Beispiel USA


Darstellungsregeln mit dem Symbolizer

[Bearbeiten | Quelltext bearbeiten]

Als 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

Texas in einer anderen 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)

[Bearbeiten | Quelltext bearbeiten]

Im 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);
Ausschnitt auf Vermont begrenzt
Ohne Transformation der BBOX in das Ziel-Referenzsystem