blob: 410e1402dba49879cc2ba384bb2862bddd14c535 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.openoffice;
import java.util.Arrays;
import org.opengis.util.FactoryException;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.geometry.Envelopes;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.geometry.GeneralDirectPosition;
import org.apache.sis.referencing.CRS;
import org.apache.sis.util.collection.Cache;
import org.apache.sis.referencing.privy.ReferencingUtilities;
import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
import org.apache.sis.referencing.operation.AbstractCoordinateOperation;
import org.apache.sis.storage.DataStoreException;
/**
* Applies a coordinate transformation between two CRS.
*
* @author Martin Desruisseaux (IRD, Geomatys)
*/
final class Transformer {
/**
* The geographic area of interest. All values are in degrees. Prime meridian is Greenwich.
* Datum does not need to be specified since area of interest is an approximated information.
*
* @see #hasAreaOfInterest()
*/
private double westBoundLongitude, eastBoundLongitude,
southBoundLatitude, northBoundLatitude;
/**
* The coordinate operation.
*/
private CoordinateOperation operation;
/**
* The first error that occurred while transforming points, or {@code null} if none.
*/
TransformException warning;
/**
* Creates a new transformer.
*/
Transformer(final ReferencingFunctions caller, final CoordinateReferenceSystem sourceCRS,
final String targetCRS, final double[][] points) throws FactoryException, DataStoreException
{
/*
* Computes the area of interest.
*/
final GeographicCRS domainCRS = ReferencingUtilities.toNormalizedGeographicCRS(sourceCRS, false, false);
if (domainCRS != null) {
final MathTransform toDomainOfValidity = CRS.findOperation(sourceCRS, domainCRS, null).getMathTransform();
final int dimension = toDomainOfValidity.getSourceDimensions();
final double[] domainCoord = new double[toDomainOfValidity.getTargetDimensions()];
if (domainCoord.length >= 2) {
westBoundLongitude = Double.POSITIVE_INFINITY;
southBoundLatitude = Double.POSITIVE_INFINITY;
eastBoundLongitude = Double.NEGATIVE_INFINITY;
northBoundLatitude = Double.NEGATIVE_INFINITY;
if (points != null) {
for (final double[] coord : points) {
if (coord != null && coord.length == dimension) {
try {
toDomainOfValidity.transform(coord, 0, domainCoord, 0, 1);
} catch (TransformException e) {
if (warning == null) {
warning = e;
}
continue;
}
final double x = domainCoord[0];
final double y = domainCoord[1];
if (x < westBoundLongitude) westBoundLongitude = x;
if (x > eastBoundLongitude) eastBoundLongitude = x;
if (y < southBoundLatitude) southBoundLatitude = y;
if (y > northBoundLatitude) northBoundLatitude = y;
}
}
}
}
}
/*
* Get the coordinate operation from the cache if possible, or compute it otherwise.
*/
final boolean hasAreaOfInterest = hasAreaOfInterest();
final CacheKey<CoordinateOperation> key = new CacheKey<>(CoordinateOperation.class, targetCRS, sourceCRS,
hasAreaOfInterest ? new double[] {westBoundLongitude, eastBoundLongitude,
southBoundLatitude, northBoundLatitude} : null);
operation = key.peek();
if (operation == null) {
final Cache.Handler<CoordinateOperation> handler = key.lock();
try {
operation = handler.peek();
if (operation == null) {
operation = CRS.findOperation(sourceCRS, caller.getCRS(targetCRS),
hasAreaOfInterest ? getAreaOfInterest() : null);
}
} finally {
handler.putAndUnlock(operation);
}
}
}
/**
* Returns {@code true} if the area of interest is non-empty.
*/
final boolean hasAreaOfInterest() {
return (westBoundLongitude < eastBoundLongitude) && (southBoundLatitude < northBoundLatitude);
}
/**
* Returns the area of interest. It is caller's responsibility to verify that
* {@link #hasAreaOfInterest()} returned {@code true} before to invoke this method.
*/
final GeographicBoundingBox getAreaOfInterest() {
return new DefaultGeographicBoundingBox(westBoundLongitude, eastBoundLongitude,
southBoundLatitude, northBoundLatitude);
}
/**
* Returns the accuracy in metres.
*/
final double getAccuracy() {
return AbstractCoordinateOperation.castOrCopy(operation).getLinearAccuracy();
}
/**
* Transforms the given points.
*/
final double[][] transform(final double[][] points) {
final MathTransform mt = operation.getMathTransform();
final GeneralDirectPosition sourcePt = new GeneralDirectPosition(mt.getSourceDimensions());
final GeneralDirectPosition targetPt = new GeneralDirectPosition(mt.getTargetDimensions());
final double[][] result = new double[points.length][];
for (int j=0; j<points.length; j++) {
final double[] coords = points[j];
if (coords != null) { // Paranoiac check.
for (int i=sourcePt.coordinates.length; --i>=0;) {
sourcePt.coordinates[i] = (i < coords.length) ? coords[i] : 0;
}
try {
result[j] = mt.transform(sourcePt, targetPt).getCoordinates();
} catch (TransformException exception) {
/*
* The coordinate operation failed for this particular point. But maybe it will
* succeed for another point. Set the values to NaN and continue the loop. Note:
* we will report the failure for logging purpose, but only the first one since
* all subsequent failures are likely to be the same one.
*/
final double[] pad = new double[mt.getTargetDimensions()];
Arrays.fill(pad, Double.NaN);
result[j] = pad;
if (warning == null) {
warning = exception;
}
}
}
}
return result;
}
/**
* Transforms the given envelope.
*/
final double[][] transformEnvelope(final double[][] points) throws TransformException {
final double[] min = new double[operation.getMathTransform().getSourceDimensions()];
final double[] max = new double[min.length];
Arrays.fill(min, Double.POSITIVE_INFINITY);
Arrays.fill(max, Double.NEGATIVE_INFINITY);
for (final double[] p : points) {
if (p != null) { // Paranoiac check.
for (int i=Math.min(min.length, p.length); --i >= 0;) {
final double v = p[i];
if (v < min[i]) min[i] = v;
if (v > max[i]) max[i] = v;
}
}
}
final GeneralEnvelope result = Envelopes.transform(operation, new GeneralEnvelope(min, max));
return new double[][] {
result.getLowerCorner().getCoordinates(),
result.getUpperCorner().getCoordinates()
};
}
}