Karte erstellen und mit GeoTools im Bild speichern [geschlossen]

9

Ich möchte mit GeoTools eine Karte erstellen und in einem Bild (zB JPEG) speichern. Meine Anforderungen sind einfach:

  1. Erstellen Sie eine Weltkarte mit zwei Ebenen: Politische Grenzen und ein Raster. Die Ebenen stammen aus verschiedenen Quellen und verschiedenen Projektionen.
  2. Geben Sie die Karte in verschiedenen Projektionen aus (z. B. "EPSG: 5070", "EPSG: 4326", "EPSG: 54012", "EPSG: 54009" usw.).
  3. Clip die Ausgabe auf verschiedene AOIs (zB -124,79 bis -66,9 lon, 24,4 bis 49,4 lat).

Ich möchte dies programmgesteuert über die API tun. Bisher hatte ich nur begrenzten Erfolg. Ich habe gelernt, mit diesem Ansatz eine Karte zu erstellen und in verschiedenen Projektionen auszugeben:

//Step 1: Create map
MapContent map = new MapContent();
map.setTitle("World");

//Step 2: Set projection
CoordinateReferenceSystem crs = CRS.decode("EPSG:5070"); //Conic projection over US
MapViewport vp = map.getViewport();
vp.setCoordinateReferenceSystem(crs);

//Step 3: Add layers to map
CoordinateReferenceSystem mapCRS = map.getCoordinateReferenceSystem();
map.addLayer(reproject(getPoliticalBoundaries(), mapCRS));
map.addLayer(reproject(getGraticules(), mapCRS));

//Step 4: Save image
saveImage(map, "/temp/graticules.jpg", 800);

Die Speichermethode stammt direkt von der GeoTools- Website :

public void saveImage(final MapContent map, final String file, final int imageWidth) {

    GTRenderer renderer = new StreamingRenderer();
    renderer.setMapContent(map);

    Rectangle imageBounds = null;
    ReferencedEnvelope mapBounds = null;
    try {
        mapBounds = map.getMaxBounds();
        double heightToWidth = mapBounds.getSpan(1) / mapBounds.getSpan(0);
        imageBounds = new Rectangle(
                0, 0, imageWidth, (int) Math.round(imageWidth * heightToWidth));

    } catch (Exception e) {
        // failed to access map layers
        throw new RuntimeException(e);
    }

    BufferedImage image = new BufferedImage(imageBounds.width, imageBounds.height, BufferedImage.TYPE_INT_RGB);

    Graphics2D gr = image.createGraphics();
    gr.setPaint(Color.WHITE);
    gr.fill(imageBounds);

    try {
        renderer.paint(gr, imageBounds, mapBounds);
        File fileToSave = new File(file);
        ImageIO.write(image, "jpeg", fileToSave);

    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

Das Reprojektionsverfahren ist meine Erfindung. Es ist ein bisschen wie ein Hack, aber es ist die einzige Möglichkeit, ein Bild für eine bestimmte Projektion auszugeben.

private static Layer reproject(Layer layer, CoordinateReferenceSystem mapCRS) throws Exception {

    SimpleFeatureSource featureSource = (SimpleFeatureSource) layer.getFeatureSource();


  //Define coordinate transformation
    CoordinateReferenceSystem dataCRS = featureSource.getSchema().getCoordinateReferenceSystem();
    boolean lenient = true; // allow for some error due to different datums
    MathTransform transform = CRS.findMathTransform(dataCRS, mapCRS, lenient);


  //Create new feature collection
    SimpleFeatureCollection copy = FeatureCollections.newCollection("internal");
    SimpleFeatureType featureType = SimpleFeatureTypeBuilder.retype(featureSource.getSchema(), mapCRS);
    SimpleFeatureIterator iterator = featureSource.getFeatures().features();
    try {

        while (iterator.hasNext()) {

            SimpleFeature feature = iterator.next();
            Geometry geometry = (Geometry) feature.getDefaultGeometry();
            Geometry geometry2 = JTS.transform(geometry, transform);
            copy.add( SimpleFeatureBuilder.build( featureType, new Object[]{ geometry2 }, null) );
        }

    }
    catch (Exception e) {
        e.printStackTrace();
    }
    finally {
        iterator.close();
    }


  //Return new layer
    Style style = SLD.createLineStyle(Color.BLACK, 1);
    layer = new FeatureLayer(copy, style);
    layer.setTitle("Graticules");
    return layer;
}

Die Ausgabe ist wirklich schlecht:

Ausgabe von Reprojektion

Ich glaube, ich habe ein paar verschiedene Fragen:

  1. Ist das der richtige Ansatz? Muss ich Ebenen wirklich manuell neu projizieren oder soll das MapViewport dies für mich tun?
  2. Wie schneide ich die Ausgabe an eine bestimmte AOI? Ich habe versucht, die Grenzen mit der MapViewport.setBounds-Methode (Hüllkurve) festzulegen, aber die saveImage-Methode scheint die Grenzen zu ignorieren.
  3. Wie kann ich meine Breitengradlinien als Bögen rendern lassen? Gibt es eine Transformationseinstellung, die mir fehlt?

Ich benutze GeoTools 8.7.

Peter
quelle

Antworten:

1

1) Die Karte sollte die Neuprojektion für Sie übernehmen. Ein Beispiel finden Sie im QuickStart .

2) Sie fragen die Karte nach den maximalen Grenzen, nicht nach den aktuellen Grenzen, und möchten möglicherweise die DomainOfValidity des CRS verwenden, um unangenehme Verrücktheiten zu vermeiden.

3) Ich bin mir nicht sicher , wie Sie Ihre graticles generieren , aber wenn Sie die Verwendung Gitter - Modul , um die Linien verdichten kann , um sie Bögen werden zu lassen.

Bearbeiten Wenn ich die Datei state.shp (von GeoServer) verwende, erhalte ich Folgendes:

Geben Sie hier die Bildbeschreibung ein

mit dem Code hier .

Ende bearbeiten

Schließlich wurde die Projektionshandhabung kürzlich verbessert, sodass Sie möglicherweise zu GeoTools 12 oder 13 wechseln möchten.

Beispielkarte

Ian Turton
quelle
2

Ians Antwort ist richtig und ich habe sie als solche markiert. Der Vollständigkeit halber für alle anderen, die interessiert sein könnten ...


Frage 1

Nein, Sie müssen Ebenen nicht manuell neu projizieren. Die Angabe der Projektion im Ansichtsfenster sollte ausreichen. Beispiel:

    MapViewport vp = map.getViewport();
    CoordinateReferenceSystem crs = CRS.decode("EPSG:5070");
    vp.setCoordinateReferenceSystem(crs);

Frage 2

Um die Karte zu beschneiden , müssen Sie die Grenzen des Ansichtsfensters festlegen UND die Funktion saveImage aktualisieren. Hier ist ein Beispiel für das Festlegen der Grenzen für die Projektionsbereiche:

    Extent crsExtent = crs.getDomainOfValidity();
    for (GeographicExtent element : crsExtent.getGeographicElements()) {
        if (element instanceof GeographicBoundingBox) {
            GeographicBoundingBox bounds = (GeographicBoundingBox) element;
            ReferencedEnvelope bbox = new ReferencedEnvelope(
                bounds.getSouthBoundLatitude(),
                bounds.getNorthBoundLatitude(),
                bounds.getWestBoundLongitude(),
                bounds.getEastBoundLongitude(),

                CRS.decode("EPSG:4326")
            );
            ReferencedEnvelope envelope = bbox.transform(crs, true);
            vp.setBounds(envelope);
        }
    }

Zusätzlich zum Festlegen der Ansichtsfenstergrenzen sollte die Funktion saveImage geändert werden, um die Ansichtsfenstergrenzen anstelle von map.getMaxBounds () zu verwenden.

Veränderung:

mapBounds = map.getMaxBounds();

Dazu:

mapBounds = map.getViewport().getBounds();

Hier ist die Ausgabe:

UNS


Frage 3

Dank Ians Vorschlag konnte ich die Breitengradlinien durch Verdichten der Linienkette krümmen. Hier ist der Schlüsselausschnitt aus der Methode getGraticules (), auf die im ursprünglichen Beitrag verwiesen wurde:

  //Add lines of latitude
    for (int y=-90; y<=90; y+=15){
        java.util.ArrayList<Coordinate> coords = new java.util.ArrayList<Coordinate>();
        for (double x=-135; x<=-45; x+=0.5){
            coords.add(new Coordinate(y,x,0));
        }
        LineString line = new LineString(coords.toArray(new Coordinate[coords.size()]), precisionModel, 4326);
        collection.add( SimpleFeatureBuilder.build( TYPE, new Object[]{ line }, null) );
    }

Die Ausgabe ist wie folgt:

Ausgabe von Reprojektion 2

Obwohl dieser Ansatz funktioniert, hoffte ich auf eine Transformationseinstellung oder etwas, das die Linien für mich biegt.

Peter
quelle