blob: 4aecf452bdc9925ee62d5445ae2606670d96086b [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.referencing.operation;
import java.util.List;
import org.opengis.referencing.crs.*;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.Matrix;
import org.opengis.referencing.operation.OperationNotFoundException;
import org.opengis.util.FactoryException;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.internal.system.Loggers;
import org.apache.sis.referencing.operation.matrix.Matrices;
// Branch-dependent imports
import org.apache.sis.referencing.crs.DefaultParametricCRS;
/**
* Information about the relationship between a source component and a target component
* in {@code CompoundCRS} instances.
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.7
* @since 0.7
* @module
*/
final class SubOperationInfo {
/**
* Types of target CRS, together with the type of CRS that may be used as the source for that target.
* For each array {@code COMPATIBLE_TYPES[i]}, the first element (i.e. {@code COMPATIBLE_TYPES[i][0]})
* is the target CRS and the whole array (including the first element) gives the valid source CRS type,
* if preference order.
*
* <div class="note"><b>Example:</b>
* if a target CRS is of type {@link VerticalCRS}, then the source CRS may be another {@code VerticalCRS}
* or a {@link GeodeticCRS}. The geodetic CRS is possible because it may be three-dimensional.</div>
*
* {@link ProjectedCRS} and {@link DerivedCRS} are not in this list because we rather use their base CRS
* as the criterion for determining their type.
*/
private static final Class<?>[][] COMPATIBLE_TYPES = {
{GeodeticCRS.class},
{VerticalCRS.class, GeodeticCRS.class},
{TemporalCRS.class},
{DefaultParametricCRS.class},
{EngineeringCRS.class},
{ImageCRS.class}
};
/**
* Returns the class of the given CRS after unwrapping derived and projected CRS.
* The returned type is for use with {@link #COMPATIBLE_TYPES}.
*/
private static Class<?> type(SingleCRS crs) {
while (crs instanceof GeneralDerivedCRS) {
crs = (SingleCRS) ((GeneralDerivedCRS) crs).getBaseCRS();
}
return crs.getClass();
}
/**
* The coordinate operation between a source component and a target component.
*/
final CoordinateOperation operation;
/**
* Returns the first dimension (inclusive) where the source component CRS begins in the source compound CRS.
*/
final int startAtDimension;
/**
* Returns the last dimension (exclusive) where the source component CRS ends in the source compound CRS.
*/
final int endAtDimension;
/**
* Creates a new instance containing the given information.
*/
private SubOperationInfo(final CoordinateOperation operation, final int startAtDimension, final int endAtDimension) {
this.operation = operation;
this.startAtDimension = startAtDimension;
this.endAtDimension = endAtDimension;
}
/**
* Searches in given list of source components for an operation capable to convert or transform coordinates
* to the given target CRS. If no such operation can be found, then this method returns {@code null}.
*
* @param caller the object which is inferring a coordinate operation.
* @param sourceIsUsed flags for keeping trace of which source has been used.
* @param sources all components of the source CRS.
* @param target one component of the target CRS.
* @return information about a coordinate operation from a source CRS to the given target CRS, or {@code null}.
* @throws FactoryException if an error occurred while grabbing a coordinate operation.
*/
static SubOperationInfo create(final CoordinateOperationFinder caller, final boolean[] sourceIsUsed,
final List<? extends SingleCRS> sources, final SingleCRS target) throws FactoryException
{
OperationNotFoundException failure = null;
final Class<?> targetType = type(target);
for (final Class<?>[] sourceTypes : COMPATIBLE_TYPES) {
if (sourceTypes[0].isAssignableFrom(targetType)) {
for (final Class<?> sourceType : sourceTypes) {
int startAtDimension;
int endAtDimension = 0;
for (int i=0; i<sourceIsUsed.length; i++) {
final SingleCRS source = sources.get(i);
startAtDimension = endAtDimension;
endAtDimension += source.getCoordinateSystem().getDimension();
if (!sourceIsUsed[i] && sourceType.isAssignableFrom(type(source))) {
final CoordinateOperation operation;
try {
operation = caller.createOperation(source, target);
} catch (OperationNotFoundException exception) {
if (failure == null) {
failure = exception;
} else {
failure.addSuppressed(exception);
}
continue;
}
/*
* Found an operation. Exclude the source component from the list because each source
* should be used at most once by SourceComponent. Note that the same source may still
* be used again in another context if that source is also an interpolation CRS.
*
* EXAMPLE: consider a coordinate operation from (GeodeticCRS₁, VerticalCRS₁) source
* to (GeodeticCRS₂, VerticalCRS₂) target. The source GeodeticCRS₁ should be mapped
* to exactly one target component (which is GeodeticCRS₂) and VerticalCRS₁ mapped
* to VerticalCRS₂. But the operation on vertical coordinates may need GeodeticCRS₁
* for doing its work, so GeodeticCRS₁ is needed twice. However when needed for the
* vertical coordinate operation, the GeodeticCRS₁ is used as an "interpolation CRS".
* Interpolation CRS are handled in other code paths; it is not the business of this
* SourceComponent class to care about them. From the point of view of this class,
* GeodeticCRS₁ is used only once.
*/
sourceIsUsed[i] = true;
if (failure != null) {
Logging.recoverableException(Logging.getLogger(Loggers.COORDINATE_OPERATION),
CoordinateOperationFinder.class, "decompose", failure);
}
return new SubOperationInfo(operation, startAtDimension, endAtDimension);
}
}
}
}
}
if (failure != null) {
throw failure;
}
return null;
}
/**
* Returns the dimension from which all remaining operations are identity.
*/
static int startOfIdentity(final SubOperationInfo[] selected) {
int n = selected.length;
while (n != 0) {
if (!selected[--n].operation.getMathTransform().isIdentity()) {
break;
}
}
return n;
}
/**
* Returns a matrix for an affine transform from all source coordinates to the coordinates of the
* source components selected for participating in the coordinate operation.
*
* @param sourceDimensions number of dimension of the source {@code CompoundCRS}.
* @param selectedDimensions number of source dimensions needed by the coordinate operations.
* @param selected all {@code SourceComponent} instances needed for the target {@code CompoundCRS}.
*/
static Matrix sourceToSelected(final int sourceDimensions, final int selectedDimensions, final SubOperationInfo[] selected) {
final Matrix select = Matrices.createZero(selectedDimensions + 1, sourceDimensions + 1);
select.setElement(selectedDimensions, sourceDimensions, 1);
int j = 0;
for (final SubOperationInfo component : selected) {
for (int i=component.startAtDimension; i<component.endAtDimension; i++) {
select.setElement(j++, i, 1);
}
}
return select;
}
}