使用GeoTools创建地图并将其保存到图像[关闭]


9

我想使用GeoTools创建地图并将其保存到图像(例如JPEG)。我的要求很简单:

  1. 创建一个包含两层的世界地图:政治边界和标线。这些图层来自不同的来源和不同的投影。
  2. 将地图输出到不同的投影(例如“ EPSG:5070”,“ 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 网站

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。

Answers:


1

1)地图应为您处理重新投影。有关示例,请参见快速入门

2)您要求地图提供的是maxBounds而不是当前范围,并且您可能希望剪切CRS的DomainOfValidity以避免令人不快的怪异现象。

3)我不确定您如何生成标线,但是如果使用网格模块,则可以使线致密以使其成为圆弧。

编辑 如果我使用(来自GeoServer的)states.shp,则会得到以下信息:

在此处输入图片说明

这里使用代码。

结束编辑

最后,投影处理最近得到了改进,因此您可能要转到GeoTools 12或13。

示例图


2

伊恩(Ian)的回答是正确的,因此我已将其标记为。为了完整起见,请其他感兴趣的人...


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

除了设置视口范围外,应该修改saveImage函数以使用视口范围,而不是map.getMaxBounds()。

更改:

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的输出

尽管这种方法有效,但我还是希望有一个转换设置或一些可以为我划清界限的东西。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.