/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2015, 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.renderer.lite.gridcoverage2d; import java.awt.Rectangle; import java.util.logging.Logger; import org.geotools.coverage.grid.GridEnvelope2D; import org.geotools.coverage.grid.GridGeometry2D; import org.geotools.geometry.Envelope2D; import org.geotools.geometry.GeneralEnvelope; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.operation.transform.AffineTransform2D; import org.geotools.util.logging.Logging; import org.opengis.coverage.Coverage; import org.opengis.geometry.Envelope; import org.opengis.referencing.operation.MathTransform2D; /** * Helper class used to reduce a {@link Coverage} {@link GridGeometry2D} to be completely inside a * given valid area, assuming the coverage is already cut to it, but might have portions of pixel * going beyond the valid area limits * * @author Andrea Aime - GeoSolutions * */ class GridGeometryReducer { static final Logger LOGGER = Logging.getLogger(GridGeometryReducer.class); /** * The side of the valid area we are inspecting, or the side of the grid geometry range that we * are reducing (if the grid to world does not contain rotation, they are the same, otherwise * not) * * @author Andrea Aime - GeoSolutions */ static enum Side { TOP(0), RIGHT(1), BOTTOM(2), LEFT(3); private int value; Side(int value) { this.value = value; } public Side next() { int next = (value + 1) % 4; return values()[next]; } }; ReferencedEnvelope validArea; /** * Builds a reduce with a given valid area * * @param validArea */ public GridGeometryReducer(ReferencedEnvelope validArea) { super(); this.validArea = validArea; } /** * Reduces the given grid geometry by at most one pixel on each side, in an attempt to make it * fit the * * @param gg * @return */ public GridGeometry2D reduce(GridGeometry2D gg) { if (gg.getEnvelope().getMaximum(1) > validArea.getMaximum(1)) { gg = reduceGridGeometrySide(gg, Side.TOP); } if (gg.getEnvelope().getMaximum(0) > validArea.getMaximum(0)) { gg = reduceGridGeometrySide(gg, Side.RIGHT); } if (gg.getEnvelope().getMinimum(1) < validArea.getMinimum(1)) { gg = reduceGridGeometrySide(gg, Side.BOTTOM); } if (gg.getEnvelope().getMinimum(0) < validArea.getMinimum(0)) { gg = reduceGridGeometrySide(gg, Side.LEFT); } return gg; } private GridGeometry2D reduceGridGeometrySide(GridGeometry2D gg, Side side) { Side curr = side; for (int i = 0; i <= 4; i++) { GridEnvelope2D gridRange = gg.getGridRange2D(); Rectangle bounds = gridRange.getBounds(); GridEnvelope2D reducedRange = new GridEnvelope2D(gridRange); switch (curr) { case TOP: reducedRange.setBounds(bounds.x, bounds.y + 1, bounds.width, bounds.height - 1); break; case RIGHT: reducedRange.setBounds(bounds.x, bounds.y, bounds.width - 1, bounds.height); break; case BOTTOM: reducedRange.setBounds(bounds.x, bounds.y, bounds.width, bounds.height - 1); break; case LEFT: reducedRange.setBounds(bounds.x + 1, bounds.y, bounds.width - 1, bounds.height); break; default: throw new RuntimeException("Unexpected side " + side); } GridGeometry2D reducedGeometry = new GridGeometry2D(reducedRange, gg.getGridToCRS(), gg.getCoordinateReferenceSystem()); // see if we actually reduced the grid geometry on the side we needed it to Envelope reducedEnvelope = reducedGeometry.getEnvelope(); switch (side) { case TOP: if (reducedEnvelope.getMaximum(1) <= validArea.getMaximum(1)) { return reducedGeometry; } break; case RIGHT: if (reducedEnvelope.getMaximum(0) <= validArea.getMaximum(0)) { return reducedGeometry; } break; case BOTTOM: if (reducedEnvelope.getMinimum(1) >= validArea.getMinimum(1)) { return reducedGeometry; } break; case LEFT: if (reducedEnvelope.getMinimum(0) >= validArea.getMinimum(0)) { return reducedGeometry; } break; default: throw new RuntimeException("Unexpected side " + side); } // we did not... oh well, the grid to world might contain a rotation, try the other // sides curr = curr.next(); } // if we got here, we could not perform the reduction, might not be fatal, so let's just log LOGGER.warning("Could not reduce the grid geometry inside the valid area bounds: " + validArea + "\nGrid geometry is" + gg); return gg; } /** * Builds a cut envelope for the Crop operation. Since the crop internals will add back the * pixels we just removed due to numerical issues, we keep the envelope a bit on the safe side * * @param reduced * @return */ public GeneralEnvelope getCutEnvelope(GridGeometry2D reduced) { GeneralEnvelope result; MathTransform2D mt = reduced.getGridToCRS2D(); if (mt instanceof AffineTransform2D) { AffineTransform2D at = (AffineTransform2D) mt; double scaleX = Math.abs(at.getScaleX()); double scaleY = Math.abs(at.getScaleY()); double step = ((scaleX + scaleY) / 2.) / 10.; Envelope2D envelope = reduced.getEnvelope2D(); result = new GeneralEnvelope(envelope.getCoordinateReferenceSystem()); result.setEnvelope(envelope.getMinX() + step, envelope.getMinY() + step, envelope.getMaxX() - step, envelope.getMaxY() - step); } else { // general transform, keep it as is result = new GeneralEnvelope(reduced.getEnvelope()); } return result; } }