| /* |
| * 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; |
| } |
| } |