Membuat peta dan menyimpannya ke gambar dengan GeoTools [ditutup]

9

Saya ingin membuat peta dengan GeoTools dan menyimpannya ke gambar (misalnya JPEG). Persyaratan saya sederhana:

  1. Buat peta dunia dengan 2 lapisan: Batas-batas politik dan graticule. Lapisan-lapisan itu berasal dari berbagai sumber dan proyeksi yang berbeda.
  2. Keluarkan peta ke berbagai proyeksi (mis. "EPSG: 5070", "EPSG: 4326", "EPSG: 54012", "EPSG: 54009", dll.)
  3. Klip output ke AOI yang berbeda (misalnya -124,79 hingga -66,9 lon, 24,4 hingga 49,4 lat).

Saya ingin melakukan ini secara terprogram, melalui API. Sejauh ini, keberhasilan saya terbatas. Saya telah belajar membuat peta dan hasil dalam berbagai proyeksi menggunakan pendekatan ini:

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

Metode penyimpanan langsung dari situs 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);
    }
}

Metode proyek ulang adalah penemuan saya. Ini sedikit hack tetapi satu-satunya cara saya bisa menemukan untuk menghasilkan gambar ke proyeksi tertentu.

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

Outputnya sangat buruk:

Keluaran dari proyeksi ulang

Jadi, saya kira saya punya beberapa pertanyaan berbeda:

  1. Apakah ini pendekatan yang tepat? Apakah saya benar-benar perlu memproyeksikan ulang layer secara manual atau apakah MapViewport seharusnya melakukan ini untuk saya?
  2. Bagaimana cara klip output ke AOI tertentu? Saya telah mencoba mengatur batas menggunakan metode MapViewport.setBounds (amplop) tetapi metode saveImage tampaknya mengabaikan batas.
  3. Bagaimana cara mendapatkan garis lintang saya untuk dirender sebagai busur? Apakah ada pengaturan transformasi yang saya lewatkan?

Saya menggunakan GeoTools 8.7.

Peter
sumber

Jawaban:

1

1) peta harus menangani proyeksi ulang untuk Anda. Lihat QuickStart untuk contoh.

2) Anda meminta peta untuk itu maxBounds bukan batas saat ini, dan Anda mungkin ingin memotong dengan DomainOfValidity CRS untuk menghindari keanehan yang tidak menyenangkan.

3) Saya tidak yakin bagaimana Anda menghasilkan graticle Anda, tetapi jika Anda menggunakan modul grid Anda dapat memadatkan garis untuk membuat mereka menjadi busur.

Sunting Jika saya menggunakan States.shp (dari GeoServer) saya mendapatkan ini:

masukkan deskripsi gambar di sini

menggunakan kode di sini .

akhiri edit

Akhirnya penanganan proyeksi telah ditingkatkan baru-baru ini sehingga Anda mungkin ingin pindah ke GeoTools 12 atau 13.

contoh peta

Ian Turton
sumber
2

Jawaban Ian benar, dan saya telah menandainya. Demi kelengkapan bagi siapa pun yang mungkin tertarik ...


pertanyaan 1

Tidak, Anda tidak perlu memproyeksi ulang layer secara manual. Menentukan proyeksi pada viewport harus cukup. Contoh:

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

Pertanyaan 2

Untuk klip peta, Anda perlu mengatur batas viewport DAN memperbarui fungsi saveImage. Berikut adalah contoh cara mengatur batas ke luasan proyeksi:

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

Selain mengatur batas viewport, fungsi saveImage harus dimodifikasi untuk menggunakan batas viewport alih-alih map.getMaxBounds ().

Perubahan:

mapBounds = map.getMaxBounds();

Untuk ini:

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

Inilah hasilnya:

KAMI


Pertanyaan 3

Berkat saran Ian, saya bisa membuat garis lintang melengkung dengan memadatkan string garis. Inilah potongan kunci dari metode getGraticules () yang dirujuk dalam pos asli:

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

Outputnya adalah sebagai berikut:

Keluaran dari proyeksi ulang 2

Meskipun pendekatan ini berhasil, saya berharap untuk mengubah pengaturan atau sesuatu yang akan memengaruhi saya.

Peter
sumber