/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2015 - 2016, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.coverage.grid.io.footprint; import java.awt.Rectangle; import java.awt.Shape; import java.awt.geom.AffineTransform; import javax.imageio.ImageReadParam; import javax.media.jai.ROI; import javax.media.jai.ROIShape; import org.geotools.coverage.grid.io.imageio.ReadType; import org.geotools.geometry.jts.GeometryClipper; import org.geotools.util.SoftValueHashMap; import it.geosolutions.jaiext.vectorbin.ROIGeometry; import com.vividsolutions.jts.awt.ShapeReader; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.util.AffineTransformation; import com.vividsolutions.jts.simplify.TopologyPreservingSimplifier; /** * A ROIGeometry provider that handles multi-scale ROI with some extras: * <ul> * <li>Caching of reduced resolution of the same ROI</li> * <li>Management of the footprint inset</li> * </ul> * * @author Andrea Aime - GeoSolutions * */ public class MultiLevelROIGeometry implements MultiLevelROI { private Geometry originalFootprint; private Geometry insetFootprint; private Geometry granuleBounds; private double inset; private FootprintInsetPolicy insetPolicy; private SoftValueHashMap<AffineTransform, ROIGeometry> roiCache = new SoftValueHashMap<AffineTransform, ROIGeometry>( 10); private boolean empty; public MultiLevelROIGeometry(Geometry footprint, Geometry granuleBounds, double inset, FootprintInsetPolicy insetPolicy) { this.originalFootprint = footprint; this.granuleBounds = granuleBounds; this.inset = inset; this.insetPolicy = insetPolicy; if(inset > 0) { insetFootprint = insetPolicy.applyInset(originalFootprint, granuleBounds, inset); this.empty = insetFootprint.isEmpty(); } else { this.empty = originalFootprint.isEmpty(); } } public ROIGeometry getTransformedROI(AffineTransform at, int imageIndex, Rectangle imgBounds, ImageReadParam params, ReadType readType) { if(empty) { return null; } if (at == null) { at = new AffineTransform(); } ROIGeometry roiGeometry = roiCache.get(at); if (roiGeometry == null) { Geometry rescaled; AffineTransformation geometryAT = new AffineTransformation(at.getScaleX(), at.getShearX(), at .getTranslateX(), at.getShearY(), at.getScaleY(), at.getTranslateY()); if (inset > 0) { double scale = Math.min(Math.abs(at.getScaleX()), Math.abs(at.getScaleY())); double rescaledInset = scale * inset; if (rescaledInset < 1) { // just apply a 1 pixel inset on the rescaled geometry Geometry cloned = (Geometry) originalFootprint.clone(); cloned.apply(geometryAT); Geometry bounds = (Geometry) granuleBounds.clone(); bounds.apply(geometryAT); rescaled = insetPolicy.applyInset(cloned, bounds, 1.5); } else { // use the original footprint rescaled = (Geometry) insetFootprint.clone(); rescaled.apply(geometryAT); } } else { rescaled = (Geometry) originalFootprint.clone(); rescaled.apply(geometryAT); } if(!rescaled.isEmpty()) { // the geometry is likely to have way more precision than needed, simplify it // so that the error is significantly less than one pixel Geometry simplified = TopologyPreservingSimplifier.simplify(rescaled, 0.333); // build a ROI geometry optimized for rectangle clipping roiGeometry = new FastClipROIGeometry(simplified); roiCache.put(at, roiGeometry); } else { return null; } } return roiGeometry; } public boolean isEmpty() { return empty; } public Geometry getFootprint() { if(inset == 0) { return originalFootprint; } else { return insetFootprint; } } /** * A ROIGeometry leveraging {@link GeometryClipper} for fast clipping against rectangles * * @author Andrea Aime - GeoSolutions */ static class FastClipROIGeometry extends ROIGeometry { private static final long serialVersionUID = -4283288388988174306L; private static final AffineTransformation Y_INVERSION = new AffineTransformation(1, 0, 0, 0, -1, 0); public FastClipROIGeometry(Geometry geom) { super(geom); } @Override public ROI intersect(ROI roi) { final Geometry geom = getGeometry(roi); // is it a rectangle? if (geom != null && geom.equalsExact(geom.getEnvelope())) { GeometryClipper clipper = new GeometryClipper(geom.getEnvelopeInternal()); Geometry intersect = clipper.clip(getAsGeometry(), true); return new ROIGeometry(intersect); } else { return super.intersect(roi); } } /** * Gets a {@link Geometry} from an input {@link ROI}. * * @param roi the ROI * @return a {@link Geometry} instance from the provided input; * null in case the input roi is neither a geometry, nor a shape. */ private Geometry getGeometry(ROI roi){ if (roi instanceof ROIGeometry){ return ((ROIGeometry) roi).getAsGeometry(); } else if (roi instanceof ROIShape){ final Shape shape = ((ROIShape) roi).getAsShape(); final Geometry geom = ShapeReader.read(shape, 0, new GeometryFactory()); geom.apply(Y_INVERSION); return geom; } return null; } } }