Creare una mappa e salvarla sull'immagine con GeoTools [chiuso]


9

Vorrei creare una mappa con GeoTools e salvarla su un'immagine (ad es. JPEG). Le mie esigenze sono semplici:

  1. Crea una mappa del mondo con 2 livelli: confini politici e un reticolo. I livelli provengono da fonti diverse e proiezioni diverse.
  2. Invia la mappa a diverse proiezioni (ad esempio "EPSG: 5070", "EPSG: 4326", "EPSG: 54012", "EPSG: 54009", ecc.)
  3. Agganciare l'output a diversi AOI (ad es. Da -124,79 a -66,9 lon, da 24,4 a 49,4 lat).

Voglio farlo a livello di programmazione, tramite l'API. Finora ho avuto un successo limitato. Ho imparato a creare una mappa e l'output in varie proiezioni usando questo approccio:

//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);

Il metodo di salvataggio è direttamente dal sito Web di GeoTools :

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);
    }
}

Il metodo di riproiezione è la mia invenzione. È un po 'un trucco ma è l'unico modo in cui sono riuscito a trovare un'immagine su una proiezione specifica.

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;
}

L'output è davvero pessimo:

Uscita dalla riproiezione

Quindi, immagino di avere un paio di domande diverse:

  1. È questo il giusto approccio? Devo davvero riproiettare i layer manualmente o MapViewport dovrebbe farlo per me?
  2. Come posso tagliare l'output su un AOI specifico? Ho provato a impostare i limiti utilizzando il metodo MapViewport.setBounds (busta) ma il metodo saveImage sembra ignorare i limiti.
  3. Come posso ottenere il rendering delle mie linee di latitudine come archi? C'è un'impostazione di trasformazione che mi manca?

Sto usando GeoTools 8.7.

Risposte:


1

1) la mappa dovrebbe gestire la riproiezione per te. Vedi QuickStart per un esempio.

2) chiedi alla mappa i suoi maxBounds non i limiti attuali e potresti voler aggirare DomainOfValidity del CRS per evitare spiacevoli stranezze.

3) Non sono sicuro di come stai generando i tuoi reticoli ma se usi il modulo griglie puoi densificare le linee per farle diventare archi.

Modifica Se uso gli stati.shp (da GeoServer) ottengo questo:

inserisci qui la descrizione dell'immagine

usando il codice qui .

fine modifica

Infine, la gestione della proiezione è stata recentemente migliorata, quindi potresti voler passare a GeoTools 12 o 13.

mappa di esempio


2

La risposta di Ian è corretta e l'ho contrassegnata come tale. Per completezza per chiunque possa essere interessato ...


Domanda 1

No, non è necessario riproiettare manualmente i livelli. È sufficiente specificare la proiezione sul viewport. Esempio:

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

Domanda 2

Per tagliare la mappa, è necessario impostare i limiti del viewport E aggiornare la funzione saveImage. Ecco un esempio di come impostare i limiti alle estensioni di proiezione:

    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);
        }
    }

Oltre a impostare i limiti del viewport, la funzione saveImage deve essere modificata per utilizzare i limiti del viewport anziché map.getMaxBounds ().

Modificare:

mapBounds = map.getMaxBounds();

A questo:

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

Ecco l'output:

NOI


Domanda 3

Grazie al suggerimento di Ian, sono stato in grado di far curvare le linee di latitudine densificando la stringa di linea. Ecco lo snippit chiave del metodo getGraticules () a cui fa riferimento il post originale:

  //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) );
    }

L'output è il seguente:

Uscita dalla riproiezione 2

Sebbene questo approccio funzioni, speravo in un'impostazione di trasformazione o in qualcosa che potesse segnare le linee per me.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.