/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2014, 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;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.coverage.grid.GridEnvelope2D;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.data.DataSourceException;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.referencing.operation.LinearTransform;
import org.geotools.referencing.operation.builder.GridToEnvelopeMapper;
import org.geotools.referencing.operation.matrix.XAffineTransform;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.util.Utilities;
import org.geotools.util.logging.Logging;
import org.opengis.geometry.Envelope;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.NoninvertibleTransformException;
import org.opengis.referencing.operation.TransformException;
/**
* Class that supports readers in computing the proper reading resolution for a given grid geometry
*/
public class ReadResolutionCalculator {
static final Logger LOGGER = Logging.getLogger(ReadResolutionCalculator.class);
private ReferencedEnvelope requestedBBox;
private Rectangle requestedRasterArea;
private AffineTransform requestedGridToWorld;
private double[] fullResolution;
private boolean accurateResolution;
private MathTransform destinationToSourceTransform;
public ReadResolutionCalculator(GridGeometry2D requestedGridGeometry,
CoordinateReferenceSystem nativeCrs, double[] fullResolution) throws FactoryException {
Utilities.ensureNonNull("gridGeometry", requestedGridGeometry);
this.requestedBBox = new ReferencedEnvelope(
(Envelope) requestedGridGeometry.getEnvelope2D());
this.requestedRasterArea = requestedGridGeometry.getGridRange2D().getBounds();
this.requestedGridToWorld = (AffineTransform) requestedGridGeometry.getGridToCRS2D();
this.fullResolution = fullResolution;
// the reader might not know (e.g., wms cascading reader) in this case we
// pick the classic computation results, it's better than nothing
if (fullResolution == null) {
this.fullResolution = computeClassicResolution();
}
CoordinateReferenceSystem requestedCRS = requestedGridGeometry
.getCoordinateReferenceSystem();
if (!CRS.equalsIgnoreMetadata(nativeCrs, requestedCRS)) {
this.destinationToSourceTransform = CRS.findMathTransform(requestedCRS, nativeCrs);
}
}
/**
* Computes the requested resolution which is going to be used for selecting overviews and or
* deciding decimation factors on the target coverage.
*
* <p>
* In case the requested envelope is in the same {@link CoordinateReferenceSystem} of the
* coverage we compute the resolution using the requested {@link MathTransform}. Notice that it
* must be a {@link LinearTransform} or else we fail.
*
* <p>
* In case the requested envelope is not in the same {@link CoordinateReferenceSystem} of the
* coverage we
*
* @throws DataSourceException in case something bad happens during reprojections and/or
* intersections.
*/
public double[] computeRequestedResolution(ReferencedEnvelope readBounds) {
try {
// let's try to get the resolution from the requested gridToWorld
if (requestedGridToWorld instanceof LinearTransform) {
//
// the crs of the request and the one of the coverage are NOT the
// same and the conversion is not , we can get the resolution from envelope + raster
// directly
//
if (destinationToSourceTransform != null
&& !destinationToSourceTransform.isIdentity()) {
if (accurateResolution) {
return computeAccurateResolution(readBounds);
} else {
return computeClassicResolution();
}
} else {
// the crs of the request and the one of the coverage are the
// same, we can get the resolution from the grid to world
return new double[] { XAffineTransform.getScaleX0(requestedGridToWorld),
XAffineTransform.getScaleY0(requestedGridToWorld) };
}
} else {
// should not happen
throw new UnsupportedOperationException(Errors.format(
ErrorKeys.UNSUPPORTED_OPERATION_$1, requestedGridToWorld.toString()));
}
} catch (Throwable e) {
if (LOGGER.isLoggable(Level.INFO))
LOGGER.log(Level.INFO, "Unable to compute requested resolution", e);
}
//
// use the coverage resolution since we cannot compute the requested one
//
LOGGER.log(Level.WARNING,
"Unable to compute requested resolution, the reader will pick the native one");
return fullResolution;
}
/**
* Classic way of computing the requested resolution
*
* @return
*/
private double[] computeClassicResolution() {
final GridToEnvelopeMapper geMapper = new GridToEnvelopeMapper(new GridEnvelope2D(
requestedRasterArea), requestedBBox);
final AffineTransform tempTransform = geMapper.createAffineTransform();
return new double[] { XAffineTransform.getScaleX0(tempTransform),
XAffineTransform.getScaleY0(tempTransform) };
}
/**
* Compute the resolutions through a more accurate logic: Compute the resolution in 9 points,
* the corners of the requested area and the middle points and take the better one. This will
* provide better results for cases where there is a lot more deformation on a subregion
* (top/bottom/sides) of the requested bbox with respect to others.
*
* @return
* @throws TransformException
* @throws NoninvertibleTransformException
* @throws FactoryException
*/
private double[] computeAccurateResolution(ReferencedEnvelope readBBox)
throws TransformException, NoninvertibleTransformException, FactoryException {
if (!CRS.equalsIgnoreMetadata(readBBox.getCoordinateReferenceSystem(),
requestedBBox.getCoordinateReferenceSystem())) {
readBBox = readBBox.transform(requestedBBox.getCoordinateReferenceSystem(), true);
}
double resX = XAffineTransform.getScaleX0(requestedGridToWorld);
double resY = XAffineTransform.getScaleY0(requestedGridToWorld);
GeneralEnvelope cropBboxTarget = CRS.transform(readBBox,
requestedBBox.getCoordinateReferenceSystem());
double[] points = new double[36];
for (int i = 0; i < 3; i++) {
double x;
if (i == 0) {
x = cropBboxTarget.getMinimum(0) + resX / 2;
} else if (i == 1) {
x = cropBboxTarget.getMedian(0);
} else {
x = cropBboxTarget.getMaximum(0) - resX / 2;
}
for (int j = 0; j < 3; j++) {
double y;
if (j == 0) {
y = cropBboxTarget.getMinimum(1) + resY / 2;
} else if (j == 1) {
y = cropBboxTarget.getMedian(1);
} else {
y = cropBboxTarget.getMaximum(1) - resY / 2;
}
int k = (i * 3 + j) * 4;
points[k] = x - resX / 2;
points[k + 1] = y - resY / 2;
points[k + 2] = x + resX / 2;
points[k + 3] = y + resY / 2;
}
}
destinationToSourceTransform.transform(points, 0, points, 0, 18);
double minDistance = Double.MAX_VALUE;
for (int i = 0; i < 36 && minDistance > 0; i += 4) {
double dx = points[i + 2] - points[i];
double dy = points[i + 3] - points[i + 1];
double d = Math.sqrt(dx * dx + dy * dy);
if (d < minDistance) {
minDistance = d;
}
}
// reprojection can turn a segment into a zero lenght one, in that case, fall back on
// the full resolution in that case
return new double[] { Math.max(minDistance, fullResolution[0]),
Math.max(minDistance, fullResolution[1]) };
}
public boolean isAccurateResolution() {
return accurateResolution;
}
public void setAccurateResolution(boolean accurateResolution) {
this.accurateResolution = accurateResolution;
}
}