9

I would like to create a map with GeoTools and save it to an image (e.g. JPEG). My requirements are simple:

  1. Create a map of the world with 2 layers: Political boundaries and a graticule. The layers are from different sources and different projections.
  2. Output the map to different projections (e.g. "EPSG:5070", "EPSG:4326", "EPSG:54012", "EPSG:54009", etc.)
  3. Clip the output to different AOIs (e.g. -124.79 to -66.9 lon, 24.4 to 49.4 lat).

I want to do this programmatically, via the API. So far, I have had limited success. I have learned to create a map and output in various projections using this approach:

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

The save method is straight from the GeoTools website:

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

The reproject method is my invention. It's a bit of a hack but its the only way I could find to output an image to a specific projection.

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

The output is really bad:

Output from reprojection

So, I guess I have a couple different questions:

  1. Is this the right approch? Do I really need to reproject layers manually or is the MapViewport supposed to do this for me?
  2. How do I clip the output to a specific AOI? I have tried setting the bounds using the MapViewport.setBounds(envelope) method but the saveImage method seems to ignore the bounds.
  3. How do I get my latitude lines to render as arcs? Is there a transform setting that I am missing?

I am using GeoTools 8.7.

Thanks in advance!

1

1) the map should handle the reprojection for you. See the QuickStart for an example.

2) you ask the map for it's maxBounds not current bounds, and you might want to clip by the CRS's DomainOfValidity to avoid unpleasant weirdness.

3) I'm not sure how you are generating your graticles but if you use the grids module you can densify the lines to make them become arcs.

Edit If I use the states.shp (from GeoServer) I get this:

enter image description here

using the code here.

end edit

Finally the projection handling has been improved recently so you might want to move to GeoTools 12 or 13.

example map

  • (1) Can you please share your code used to generate the image? If I take out the reproject() method in my original post the data does not render in the correct projection. (2) How do you clip? Please provide an example. – Peter Dec 1 '14 at 11:34
  • I haven't done any clipping at present (as you can see) – Ian Turton Dec 1 '14 at 12:20
  • Thanks for the code but it doesn't show how to clip or how to set the projection. – Peter Dec 1 '14 at 12:42
  • It uses the builtin status bar which calls line 141 of JCRSPopupMenu - mapPane.getMapContent().getViewport().setCoordinateReferenceSystem(newCRS); – Ian Turton Dec 1 '14 at 13:19
  • clipping is left as an exercise for the reader :-) – Ian Turton Dec 1 '14 at 13:20
2

Ian's answer is correct, and I have marked it as such. For the sake of completeness for anyone else that might be interested...


Question 1

No, you do not have to manually reproject layers. Specifying the projection on the viewport should suffice. Example:

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

Question 2

In order to clip the map, you need to set the viewport bounds AND update the saveImage function. Here's an example of how to set the bounds to the projection extents:

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

In addition to setting the viewport bounds, the saveImage function should be modified to use the viewport bounds instead of map.getMaxBounds().

Change:

mapBounds = map.getMaxBounds();

To this:

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

Here's the output:

US


Question 3

Thanks to Ian's suggestion, I was able to get the latitude lines to curve by densifying the line string. Here's the key snippit from the getGraticules() method referenced in the original post:

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

The output is as follows:

Output from reprojection 2

Although this approach works, I was hoping for a transform setting or something that would arc the lines for me.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.