Tuesday 28 July 2015

masking - How to crop an image based on a shapefile using GeoTools


I'm writing a web application using GeoTools (it's Java running in a web container). I need to crop a raster file based on a shape file. The core GeoTools documentation and examples I can follow suggest that cropping is limited to Envelope (rectangular) cuts.


I came across the ImageWorker class which provides "helper methods for applying JAI operations on an image." It has a promisingly named method "mask" method ... which seems like what I want ... but it's poorly documented and seems really slow.


I'm looking for examples and/or suggestions for alternative strategies.



Answer



I appreciate the suggestions by Moovida and John but it turns out that GeoTools does indeed support the ability to crop a raster based on an arbitrary (....) geometry. There are no good examples out there but a colleague passed me a tip suggesting the CoverageProcessor and its "ROI" argument.


private Coverage clipImageToFeatureSource(RenderedImage image,
ReferencedEnvelope bounds,
FeatureSource featureSource)

throws IOException, FactoryException, MismatchedDimensionException, TransformException {
FeatureCollection collection = featureSource
.getFeatures();

CoordinateReferenceSystem crsFeatures = featureSource.getSchema().getCoordinateReferenceSystem();
CoordinateReferenceSystem crsMap = bounds.getCoordinateReferenceSystem();
boolean needsReproject = !CRS.equalsIgnoreMetadata(crsFeatures, crsMap);
MathTransform transform = CRS.findMathTransform(crsFeatures, crsMap, true);

FeatureIterator iterator = collection.features();

List all = new ArrayList();
try {
while (iterator.hasNext()) {
SimpleFeature feature = iterator.next();
Geometry geometry = (Geometry) feature.getDefaultGeometry();
if (geometry == null)
continue;
if (!geometry.isSimple())
continue;
if (needsReproject) {

geometry = JTS.transform(geometry, transform);
System.out.println("Reprojected a geometry. Result is " + geometry.toString());
}
Geometry intersection = geometry.intersection(JTS.toGeometry(bounds));
if (intersection.isEmpty()) {
continue;
}
//String name = (String) feature.getAttribute("NAME");
//if (name == null)
// name = (String) feature.getAttribute("CNTRY_NAME");

if(intersection instanceof MultiPolygon) {
MultiPolygon mp = (MultiPolygon)intersection;
for (int i = 0; i < mp.getNumGeometries(); i++) {
com.vividsolutions.jts.geom.Polygon g = (com.vividsolutions.jts.geom.Polygon)mp.getGeometryN(i);
Geometry gIntersection = IntersectUtils.intersection(g, JTS.toGeometry(bounds));
if (gIntersection.isEmpty()) {
continue;
}
all.add(g);
}

}
else if (intersection instanceof Polygon)
all.add(intersection);
else
continue;
}
} finally {
if (iterator != null) {
iterator.close();
}

}
GridCoverageFactory gridCoverageFactory = new GridCoverageFactory();
Coverage coverage = gridCoverageFactory.create("Raster", image, bounds);
Coverage clippedCoverage = null;
if (all.size() > 0) {
CoverageProcessor processor = new CoverageProcessor();
ParameterValueGroup params = processor.getOperation("CoverageCrop")
.getParameters();
params.parameter("Source").setValue(coverage);
GeometryFactory factory = JTSFactoryFinder.getGeometryFactory(null);

Geometry[] a = all.toArray(new Geometry[0]);
GeometryCollection c = new GeometryCollection(a, factory);
//params.parameter("ENVELOPE").setValue(bounds);
params.parameter("ROI").setValue(c);
params.parameter("ForceMosaic").setValue(true);
clippedCoverage = processor.doOperation(params);
}
if (all.size() == 0){
logger.info("Crop by shapefile requested but no simple features matched extent!");
}

return clippedCoverage;
}

This clips the image but may also reduce the original extent. If you want to preserve that, then you'll need to "matt" the clippedCoverage like this:


    private BufferedImage mattCroppedImage(final BufferedImage source, GridCoverage2D cropped) 
{
RenderedImage raster = cropped.getRenderedImage();
int height = source.getHeight();
int width = source.getWidth();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

Graphics2D gr = image.createGraphics();
gr.setPaint(Color.green);
gr.fill(new Rectangle2D.Double(0,0, image.getWidth(), image.getHeight()));
AffineTransform at = AffineTransform.getTranslateInstance(0, 0);
gr.drawRenderedImage(cropped.getRenderedImage(), at);
return image;
}

No comments:

Post a Comment

arcpy - Changing output name when exporting data driven pages to JPG?

Is there a way to save the output JPG, changing the output file name to the page name, instead of page number? I mean changing the script fo...