Créer une carte et l'enregistrer dans une image avec GeoTools [fermé]

9

Je voudrais créer une carte avec GeoTools et l'enregistrer dans une image (par exemple JPEG). Mes exigences sont simples:

  1. Créez une carte du monde avec 2 couches: limites politiques et graticule. Les couches proviennent de différentes sources et de différentes projections.
  2. Sortie de la carte dans différentes projections (par exemple "EPSG: 5070", "EPSG: 4326", "EPSG: 54012", "EPSG: 54009", etc.)
  3. Coupez la sortie sur différents AOI (par exemple -124,79 à -66,9 lon, 24,4 à 49,4 lat).

Je veux le faire par programme, via l'API. Jusqu'à présent, j'ai eu un succès limité. J'ai appris à créer une carte et une sortie dans diverses projections en utilisant cette approche:

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

La méthode de sauvegarde est directement issue du site Web 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);
    }
}

La méthode du reprojet est mon invention. C'est un peu un hack mais c'est le seul moyen que j'ai pu trouver pour sortir une image sur une projection spécifique.

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 sortie est vraiment mauvaise:

Sortie de reprojection

Donc, je suppose que j'ai quelques questions différentes:

  1. Est-ce la bonne approche? Dois-je vraiment reprojeter les couches manuellement ou est-ce que MapViewport est censé le faire pour moi?
  2. Comment découper la sortie sur une zone d'intérêt spécifique? J'ai essayé de définir les limites à l'aide de la méthode MapViewport.setBounds (enveloppe) mais la méthode saveImage semble ignorer les limites.
  3. Comment obtenir le rendu de mes lignes de latitude sous forme d'arcs? Y a-t-il un paramètre de transformation qui me manque?

J'utilise GeoTools 8.7.

Peter
la source

Réponses:

1

1) la carte doit gérer la reprojection pour vous. Voir le QuickStart pour un exemple.

2) vous demandez à la carte ses maxBounds pas les limites actuelles, et vous voudrez peut-être couper par DomainOfValidity du CRS pour éviter la bizarrerie désagréable.

3) Je ne sais pas comment vous générez vos graticules mais si vous utilisez le module grilles, vous pouvez densifier les lignes pour les transformer en arcs.

Modifier Si j'utilise le states.shp (de GeoServer) j'obtiens ceci:

entrez la description de l'image ici

en utilisant le code ici .

terminer l'édition

Enfin, la gestion de la projection a été améliorée récemment, vous pouvez donc passer à GeoTools 12 ou 13.

exemple de carte

Ian Turton
la source
2

La réponse d'Ian est correcte et je l'ai marquée comme telle. Par souci d'exhaustivité pour toute autre personne susceptible d'être intéressée ...


question 1

Non, vous n'avez pas à reprojeter manuellement les calques. La spécification de la projection sur la fenêtre devrait suffire. Exemple:

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

question 2

Pour découper la carte, vous devez définir les limites de la fenêtre ET mettre à jour la fonction saveImage. Voici un exemple de la façon de définir les limites des extensions de projection:

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

En plus de définir les limites de la fenêtre, la fonction saveImage doit être modifiée pour utiliser les limites de la fenêtre au lieu de map.getMaxBounds ().

Changement:

mapBounds = map.getMaxBounds();

Pour ça:

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

Voici la sortie:

NOUS


question 3

Grâce à la suggestion de Ian, j'ai pu obtenir la courbe des lignes de latitude en densifiant la chaîne de lignes. Voici l'extrait de clé de la méthode getGraticules () référencée dans le message d'origine:

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

La sortie est la suivante:

Sortie de reprojection 2

Bien que cette approche fonctionne, j'espérais un paramètre de transformation ou quelque chose qui arquerait les lignes pour moi.

Peter
la source