Criando mapa e salvando-o em imagem com GeoTools [fechado]

9

Gostaria de criar um mapa com o GeoTools e salvá-lo em uma imagem (por exemplo, JPEG). Meus requisitos são simples:

  1. Crie um mapa do mundo com 2 camadas: limites políticos e uma gratícula. As camadas são de diferentes fontes e diferentes projeções.
  2. Envie o mapa para diferentes projeções (por exemplo, "EPSG: 5070", "EPSG: 4326", "EPSG: 54012", "EPSG: 54009" etc.)
  3. Clipe a saída para diferentes AOIs (por exemplo, -124,79 a -66,9 lon, 24,4 a 49,4 lat).

Eu quero fazer isso programaticamente, através da API. Até agora, tive sucesso limitado. Aprendi a criar um mapa e saída em várias projeções usando esta abordagem:

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

O método save é direto do site da 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);
    }
}

O método de reprojeto é minha invenção. É um pouco complicado, mas é a única maneira de encontrar uma imagem para uma projeção 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;
}

A saída é muito ruim:

Saída da reprojeção

Então, acho que tenho algumas perguntas diferentes:

  1. Esta é a abordagem correta? Eu realmente preciso reprojetar camadas manualmente ou o MapViewport deve fazer isso por mim?
  2. Como cortar a saída para uma AOI específica? Tentei definir os limites usando o método MapViewport.setBounds (envelope), mas o método saveImage parece ignorar os limites.
  3. Como faço para que minhas linhas de latitude sejam renderizadas como arcos? Há uma configuração de transformação que estou ausente?

Estou usando o GeoTools 8.7.

Peter
fonte

Respostas:

1

1) o mapa deve lidar com a reprojeção para você. Veja o QuickStart para um exemplo.

2) você solicita ao mapa seus limites máximos e não atuais, e convém cortar pelo DomainOfValidity do CRS para evitar estranhezas desagradáveis.

3) Não sei ao certo como você está gerando suas gratículas, mas se você usar o módulo de grades, poderá densificar as linhas para torná-las arcos.

Editar Se eu usar o states.shp (do GeoServer), recebo o seguinte:

insira a descrição da imagem aqui

usando o código aqui .

finalizar edição

Finalmente, o manuseio da projeção foi aprimorado recentemente, portanto, você pode mudar para o GeoTools 12 ou 13.

mapa de exemplo

Ian Turton
fonte
2

A resposta de Ian está correta, e eu a marquei como tal. Por uma questão de integridade para qualquer pessoa que possa estar interessada ...


Questão 1

Não, você não precisa reprojetar manualmente as camadas. A especificação da projeção na viewport deve ser suficiente. Exemplo:

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

Questão 2

Para recortar o mapa, você precisa definir os limites da viewport E atualizar a função saveImage. Aqui está um exemplo de como definir os limites para as extensões da projeção:

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

Além de definir os limites da viewport, a função saveImage deve ser modificada para usar os limites da viewport em vez de map.getMaxBounds ().

Mudança:

mapBounds = map.getMaxBounds();

Para isso:

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

Aqui está a saída:

NOS


Questão 3

Graças à sugestão de Ian, consegui fazer com que as linhas de latitude se curvassem, densificando a sequência de linhas. Aqui está o snippit principal do método getGraticules () mencionado na postagem 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) );
    }

A saída é a seguinte:

Saída da reprojeção 2

Embora essa abordagem funcione, eu esperava uma configuração de transformação ou algo que fosse as linhas para mim.

Peter
fonte