地図を作成し、GeoToolsで画像に保存する[終了]


9

GeoToolsでマップを作成し、画像(JPEGなど)に保存したいと思います。私の要件は単純です:

  1. 政治境界線と経緯線の2つのレイヤーで世界地図を作成します。レイヤーは、さまざまなソースとさまざまな投影からのものです。
  2. マップを別の投影法で出力します(例:「EPSG:5700」、「EPSG:4326」、「EPSG:54012」、「EPSG:54009」など)
  3. 出力をさまざまなAOIにクリップします(例:-124.79から-66.9 lon、24.4から49.4 lat)。

APIを介してプログラムでこれを実行したいと考えています。これまでのところ、私は限られた成功しか収めていません。このアプローチを使用して、さまざまな投影法でマップと出力を作成する方法を学びました。

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

保存方法は、GeoTools Webサイトから直接です。

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

再投影法は私の発明です。これは少しハックですが、特定の投影に画像を出力するために見つけることができる唯一の方法です。

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

出力は本当に悪いです:

再投影からの出力

だから、私はいくつかの異なる質問があると思います:

  1. これは正しいアプローチですか?本当に手動でレイヤーを再投影する必要がありますか、それともMapViewportがこれを行うことになっていますか?
  2. 出力を特定のAOIにクリップするにはどうすればよいですか?MapViewport.setBounds(envelope)メソッドを使用して境界を設定しようとしましたが、saveImageメソッドは境界を無視しているようです。
  3. 緯度線を円弧としてレンダリングするにはどうすればよいですか?欠けている変換設定はありますか?

GeoTools 8.7を使用しています。

回答:


1

1)マップが再投影を処理します。例については、クイックスタートを参照してください。

2)マップに現在の境界ではなくmaxBoundsを要求します。不快な奇妙さを避けるために、CRSのDomainOfValidityでクリッピングしたい場合があります。

3)経緯線をどのように生成しているのかはわかりませんが、グリッドモジュールを使用すると、線を高密度化して円弧にすることができます。

編集 (GeoServerの)states.shpを使用すると、次のようになります。

ここに画像の説明を入力してください

ここでコードを使用します。

編集を終了

最後に、プロジェクションの処理が最近改善されたため、GeoTools 12または13に移行することをお勧めします。

地図の例


2

イアンの答えは正しいので、そのようにマークしました。興味があるかもしれない他の人のための完全性のために...


質問1

いいえ、レイヤーを手動で再投影する必要はありません。ビューポートに投影を指定するだけで十分です。例:

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

質問2

マップをクリップするためには、ビューポートの境界を設定する必要がある saveImage機能を更新します。以下は、境界を投影範囲に設定する方法の例です。

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

ビューポートの境界の設定に加えて、map.getMaxBounds()の代わりにビューポートの境界を使用するようにsaveImage関数を変更する必要があります。

変化する:

mapBounds = map.getMaxBounds();

これに:

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

出力は次のとおりです。

我ら


質問3

Ianの提案のおかげで、ラインストリングを高密度化することにより、緯度のラインを湾曲させることができました。元の投稿で参照されているgetGraticules()メソッドの主要な抜粋は次のとおりです。

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

出力は次のとおりです。

再投影2からの出力

このアプローチは機能しますが、変換設定または私にとって線を弧を描く何かを望んでいました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.