Tworzenie mapy i zapisywanie jej na obrazie za pomocą GeoTools [zamknięte]

9

Chciałbym stworzyć mapę za pomocą GeoTools i zapisać ją na obrazie (np. JPEG). Moje wymagania są proste:

  1. Utwórz mapę świata z 2 warstwami: granicami politycznymi i siatką. Warstwy pochodzą z różnych źródeł i różnych rzutów.
  2. Wyjście mapy do różnych rzutów (np. „EPSG: 5070”, „EPSG: 4326”, „EPSG: 54012”, „EPSG: 54009” itp.)
  3. Przypnij dane wyjściowe do różnych AOI (np. -124,79 do -66,9 lon, 24,4 do 49,4 lat).

Chcę to zrobić programowo za pośrednictwem interfejsu API. Jak dotąd miałem ograniczony sukces. Nauczyłem się tworzyć mapę i wyniki w różnych projekcjach przy użyciu tego podejścia:

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

Zapisuj metody jest prosto z GeoTools stronie :

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

Metoda powtórzenia jest moim wynalazkiem. To trochę hack, ale to jedyny sposób, w jaki mogłem znaleźć wyjście obrazu do określonej projekcji.

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

Wynik jest naprawdę zły:

Wynik z ponownej odrzucenia

Myślę, że mam kilka różnych pytań:

  1. Czy to właściwa aplikacja? Czy naprawdę muszę ręcznie ponownie rzutować warstwy, czy może MapViewport powinien to dla mnie zrobić?
  2. Jak przypiąć dane wyjściowe do określonego AOI? Próbowałem ustawić granice przy użyciu metody MapViewport.setBounds (koperta), ale metoda saveImage wydaje się ignorować granice.
  3. Jak uzyskać renderowanie linii szerokości geograficznej jako łuków? Czy brakuje mi ustawienia transformacji?

Używam GeoTools 8.7.

Piotr
źródło

Odpowiedzi:

1

1) mapa powinna obsłużyć cię ponownie. Przykład zawiera QuickStart .

2) pytasz mapę o jej maksymalne granice, a nie bieżące granice, i możesz chcieć przyciąć DomainOfValidity CRS, aby uniknąć nieprzyjemnego dziwactwa.

3) Nie jestem pewien, w jaki sposób generujesz swoje graticles, ale jeśli użyjesz modułu grids , możesz zagęścić linie, aby stały się łukami.

Edytuj Jeśli korzystam z pliku states.shp (z GeoServer), otrzymuję to:

wprowadź opis zdjęcia tutaj

używając kodu tutaj .

zakończ edycję

Wreszcie, obsługa projekcji została ostatnio ulepszona, więc możesz chcieć przejść do GeoTools 12 lub 13.

przykładowa mapa

Ian Turton
źródło
2

Odpowiedź Iana jest poprawna i tak ją oznaczyłem. Ze względu na kompletność dla każdego, kto może być zainteresowany ...


Pytanie 1

Nie, nie musisz ręcznie ponownie zmieniać warstw. Określenie rzutu w rzutni powinno wystarczyć. Przykład:

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

pytanie 2

Aby przyciąć mapę, musisz ustawić granice rzutni ORAZ zaktualizować funkcję saveImage. Oto przykład, jak ustawić granice zasięgu projekcji:

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

Oprócz ustawienia granic rzutni należy zmodyfikować funkcję saveImage, aby używała granic rzutni zamiast map.getMaxBounds ().

Zmiana:

mapBounds = map.getMaxBounds();

Do tego:

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

Oto wynik:

NAS


pytanie 3

Dzięki sugestii Iana udało mi się wygiąć linie szerokości geograficznej, zagęszczając ciąg linii. Oto kluczowy fragment metody getGraticules (), o której mowa w oryginalnym poście:

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

Dane wyjściowe są następujące:

Wynik ponownej odrzucenia 2

Chociaż to podejście działa, miałem nadzieję na ustawienie transformacji lub coś, co wyprofiluje linie dla mnie.

Piotr
źródło