Crear un mapa y guardarlo en una imagen con GeoTools [cerrado]

9

Me gustaría crear un mapa con GeoTools y guardarlo en una imagen (por ejemplo, JPEG). Mis requisitos son simples:

  1. Crea un mapa del mundo con 2 capas: límites políticos y una retícula. Las capas son de diferentes fuentes y diferentes proyecciones.
  2. Envíe el mapa a diferentes proyecciones (por ejemplo, "EPSG: 5070", "EPSG: 4326", "EPSG: 54012", "EPSG: 54009", etc.)
  3. Recorte la salida a diferentes AOI (por ejemplo, -124.79 a -66.9 lon, 24.4 a 49.4 lat).

Quiero hacer esto mediante programación, a través de la API. Hasta ahora, he tenido un éxito limitado. Aprendí a crear un mapa y generar resultados en varias proyecciones utilizando este enfoque:

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

El método de guardar es directo desde el sitio web de 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);
    }
}

El método de reproyección es mi invención. Es un truco, pero es la única forma que pude encontrar para emitir una imagen a una proyección específica.

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

La salida es realmente mala:

Salida de reproyección

Entonces, supongo que tengo un par de preguntas diferentes:

  1. ¿Es este el enfoque correcto? ¿Realmente necesito volver a proyectar capas manualmente o se supone que MapViewport lo hará por mí?
  2. ¿Cómo acorto la salida a un AOI específico? He intentado establecer los límites usando el método MapViewport.setBounds (sobre) pero el método saveImage parece ignorar los límites.
  3. ¿Cómo consigo que mis líneas de latitud se representen como arcos? ¿Hay una configuración de transformación que me falta?

Estoy usando GeoTools 8.7.

Peter
fuente

Respuestas:

1

1) el mapa debe manejar la reproyección por ti. Consulte el Inicio rápido para ver un ejemplo.

2) le pregunta al mapa por sus límites maxBounds no actuales, y es posible que desee recortar por DomainOfValidity del CRS para evitar rarezas desagradables.

3) No estoy seguro de cómo está generando sus gratículos, pero si usa el módulo de cuadrículas puede densificar las líneas para que se conviertan en arcos.

Editar Si uso el states.shp (de GeoServer) obtengo esto:

ingrese la descripción de la imagen aquí

usando el código aquí .

fin editar

Finalmente, el manejo de la proyección se ha mejorado recientemente, por lo que es posible que desee pasar a GeoTools 12 o 13.

mapa de ejemplo

Ian Turton
fuente
2

La respuesta de Ian es correcta, y la he marcado como tal. En aras de la integridad para cualquier otra persona que pueda estar interesada ...


Pregunta 1

No, no tiene que volver a proyectar capas manualmente. Especificar la proyección en la ventana gráfica debería ser suficiente. Ejemplo:

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

Pregunta 2

Para recortar el mapa, debe establecer los límites de la vista Y actualizar la función saveImage. Aquí hay un ejemplo de cómo establecer los límites a las extensiones de proyección:

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

Además de establecer los límites de la ventana gráfica , la función saveImage debe modificarse para usar los límites de la ventana gráfica en lugar de map.getMaxBounds ().

Cambio:

mapBounds = map.getMaxBounds();

A esto:

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

Aquí está la salida:

NOSOTROS


Pregunta 3

Gracias a la sugerencia de Ian, pude hacer que las líneas de latitud se curvaran densificando la cadena de línea. Aquí está el fragmento de clave del método getGraticules () al que se hace referencia en la publicación original:

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

El resultado es el siguiente:

Salida de reproyección 2

Aunque este enfoque funciona, esperaba una configuración de transformación o algo que me atravesara.

Peter
fuente