001/*
002 *    GeoAPI - Java interfaces for OGC/ISO standards
003 *    Copyright © 2003-2023 Open Geospatial Consortium, Inc.
004 *    http://www.geoapi.org
005 *
006 *    Licensed under the Apache License, Version 2.0 (the "License");
007 *    you may not use this file except in compliance with the License.
008 *    You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *    Unless required by applicable law or agreed to in writing, software
013 *    distributed under the License is distributed on an "AS IS" BASIS,
014 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 *    See the License for the specific language governing permissions and
016 *    limitations under the License.
017 */
018package org.opengis.referencing.operation;
019
020import java.util.Map;
021import java.util.List;
022import java.util.Optional;
023import java.time.temporal.Temporal;
024import org.opengis.referencing.crs.CoordinateReferenceSystem;
025import org.opengis.annotation.UML;
026
027import static org.opengis.annotation.Obligation.*;
028import static org.opengis.annotation.Specification.*;
029
030
031/**
032 * An ordered sequence of two or more single coordinate operations.
033 * The sequence of operations is constrained by the requirement that the source coordinate reference system
034 * of step (<var>n</var>+1) must be the same as the target coordinate reference system of step (<var>n</var>).
035 * The source coordinate reference system of the first step and the target coordinate reference system of the
036 * last step are the source and target coordinate reference system associated with the concatenated operation.
037 * Instead of a forward operation, an inverse operation may be used for one or more of the operation steps
038 * mentioned above, if the inverse operation is uniquely defined by the forward operation.
039 *
040 * <p>The concatenated coordinate operation class is primarily intended to provide a mechanism that forces
041 * application software to use a preferred path to go from source to target coordinate reference system,
042 * if a direct transformation between the two is not available.</p>
043 *
044 * @author  OGC Topic 2 (for abstract model and documentation)
045 * @author  Martin Desruisseaux (IRD, Geomatys)
046 * @version 3.1
047 * @since   1.0
048 *
049 * @see CoordinateOperationFactory#createConcatenatedOperation(Map, CoordinateOperation[])
050 */
051@UML(identifier="ConcatenatedOperation", specification=ISO_19111)
052public interface ConcatenatedOperation extends CoordinateOperation {
053    /**
054     * Returns the sequence of operations that are steps in this concatenated operation.
055     * The sequence can contain {@link SingleOperation}s or {@link PassThroughOperation}s,
056     * but should not contain other {@code ConcatenatedOperation}s.
057     * The sequence shall contain at least two elements.
058     *
059     * <div class="warning"><b>Upcoming API change</b><br>
060     * This method is conformant to ISO 19111:2003. But the ISO 19111:2007 revision changed the element type
061     * from {@code SingleOperation} to {@link CoordinateOperation}. This change may be applied in GeoAPI 4.0.
062     * This is necessary for supporting usage of {@code PassThroughOperation} with {@link ConcatenatedOperation}.
063     * </div>
064     *
065     * @return the sequence of operations.
066     */
067    @UML(identifier="coordOperation", obligation=MANDATORY, specification=ISO_19111)
068    List<SingleOperation> getOperations();
069
070    /**
071     * Returns the <abbr>CRS</abbr> from which coordinates are changed.
072     * By default, this is the source <abbr>CRS</abbr> of the first operation.
073     */
074    @Override
075    @UML(identifier="sourceCRS", obligation=CONDITIONAL, specification=ISO_19111)
076    default CoordinateReferenceSystem getSourceCRS() {
077        return getOperations().get(0).getSourceCRS();
078    }
079
080    /**
081     * Returns the <abbr>CRS</abbr> to which coordinates are changed.
082     * By default, this is the target <abbr>CRS</abbr> of the last operation.
083     */
084    @Override
085    @UML(identifier="targetCRS", obligation=CONDITIONAL, specification=ISO_19111)
086    default CoordinateReferenceSystem getTargetCRS() {
087        var operations = getOperations();
088        return operations.get(operations.size() - 1).getTargetCRS();
089    }
090
091    /**
092     * Returns the <abbr>CRS</abbr> to be used for interpolations in a grid.
093     * By default, this is the interpolation <abbr>CRS</abbr> of the first
094     * operation step in which such <abbr>CRS</abbr> is defined
095     *
096     * @since 3.1
097     */
098    @Override
099    @UML(identifier="interpolationCRS", obligation=OPTIONAL, specification=ISO_19111)
100    default Optional<CoordinateReferenceSystem> getInterpolationCRS() {
101        for (CoordinateOperation step : getOperations()) {
102            Optional<CoordinateReferenceSystem> crs = step.getInterpolationCRS();
103            if (crs.isPresent()) return crs;
104        }
105        return Optional.empty();
106    }
107
108    /**
109     * Returns the date at which source coordinate tuples are valid.
110     * By default, this is the source epoch of the first operation step in which such epoch is defined.
111     *
112     * @since 3.1
113     */
114    @Override
115    @UML(identifier="sourceCoordinateEpoch", obligation=CONDITIONAL, specification=ISO_19111)
116    default Optional<Temporal> getSourceEpoch() {
117        for (CoordinateOperation step : getOperations()) {
118            Optional<Temporal> epoch = step.getSourceEpoch();
119            if (epoch.isPresent()) return epoch;
120        }
121        return Optional.empty();
122    }
123
124    /**
125     * Returns the date at which target coordinate tuples are valid.
126     * By default, this is the target epoch of the last operation step in which such epoch is defined.
127     *
128     * @since 3.1
129     */
130    @Override
131    @UML(identifier="targetCoordinateEpoch", obligation=CONDITIONAL, specification=ISO_19111)
132    default Optional<Temporal> getTargetEpoch() {
133        var operations = getOperations();
134        for (int i=operations.size(); --i >= 0;) {
135            CoordinateOperation step = operations.get(i);
136            Optional<Temporal> epoch = step.getTargetEpoch();
137            if (epoch.isPresent()) return epoch;
138        }
139        return Optional.empty();
140    }
141}