blob: a215ab871876abf9b7787e63ba6ced159d5ba2c2 [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.transform;
import java.util.OptionalInt;
import org.opengis.util.FactoryException;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.parameter.ParameterNotFoundException;
import org.opengis.parameter.InvalidParameterNameException;
import org.opengis.parameter.InvalidParameterValueException;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransformFactory;
/**
* An object capable to create {@code MathTransform} instances from given parameter values.
* This interface is the Apache SIS mechanism by which
* {@linkplain org.apache.sis.referencing.operation.DefaultFormula formula} are concretized as Java code.
* A math transform provider ignores the source and target <abbr>CRS</abbr> and works with coordinates in
* predefined axis order and units — typically (east, north, up) in degrees or meters — although some
* variations are allowed in the number of dimensions (typically the "up" dimension being optional).
* Adjustments for <abbr>CRS</abbr> axis order, units and exact number of dimensions are caller's responsibility.
*
* <p>This interface is generally not used directly. The recommended way to get a {@link MathTransform}
* is to {@linkplain org.apache.sis.referencing.CRS#findOperation find the coordinate operation}
* (generally from a pair of <var>source</var> and <var>target</var> CRS), then to invoke
* {@link org.opengis.referencing.operation.CoordinateOperation#getMathTransform()}.
* Alternatively, one can also use a {@linkplain DefaultMathTransformFactory math transform factory}.</p>
*
* <p>Implementations of this interface usually extend {@link org.apache.sis.referencing.operation.DefaultOperationMethod},
* but this is not mandatory. This interface can also be used alone since {@link MathTransform} instances can be created
* for other purpose than coordinate operations.</p>
*
*
* <h2>How to add custom coordinate operations to Apache SIS</h2>
* {@link DefaultMathTransformFactory} can discover automatically new coordinate operations
* (including map projections) by scanning the module path. To define a custom coordinate operation,
* one needs to define a <strong>thread-safe</strong> class implementing <strong>both</strong> this
* {@code MathTransformProvider} interface and the {@link org.opengis.referencing.operation.OperationMethod} one.
* While not mandatory, we suggest to extend {@link org.apache.sis.referencing.operation.DefaultOperationMethod}.
* Example:
*
* {@snippet lang="java" :
* public class MyOperationProvider extends DefaultOperationMethod implements MathTransformProvider {
* private static final ParameterDescriptor<Foo> FOO;
* private static final ParameterDescriptor<Bar> BAR;
* private static final ParameterDescriptorGroup PARAMETERS;
* static {
* final var builder = new ParameterBuilder();
* FOO = builder.addName("Foo").create(Foo.class, null);
* BAR = builder.addName("Bar").create(Bar.class, null);
* PARAMETERS = builder.addName("My operation").createGroup(FOO, BAR);
* }
*
* public MyOperationProvider() {
* super(Map.of(NAME_KEY, PARAMETERS.getName()), PARAMETERS);
* }
*
* @Override
* public MathTransform createMathTransform(Context context) {
* var pg = Parameters.castOrWrap(context.getCompletedParameters();
* Foo foo = pg.getMandatoryValue(FOO);
* Bar bar = pg.getMandatoryValue(BAR);
* return new MyOperation(foo, bar);
* }
* }
* }
*
* In the common case where the provider needs numerical parameter values in a specific units of measurement,
* the following pattern can be used:
*
* {@snippet lang="java" :
* double semiMajor = values.parameter("semi_major").doubleValue(Units.METRE);
* double semiMinor = values.parameter("semi_minor").doubleValue(Units.METRE);
* }
*
* Then the class name of that implementation shall be declared in {@code module-info.java}
* as a provider of the {@code org.opengis.referencing.operation.OperationMethod} service.
*
* @author Martin Desruisseaux (Geomatys, IRD)
* @version 1.5
*
* @see org.apache.sis.referencing.operation.DefaultOperationMethod
* @see DefaultMathTransformFactory
* @see AbstractMathTransform
*
* @since 0.6
*/
@FunctionalInterface
public interface MathTransformProvider {
/**
* Creates a math transform from the specified group of parameter values.
* Invoking this method is equivalent to invoking {@link #createMathTransform(Context)}
* with the given factory and parameters wrapped in an instance of {@code Context}.
*
* @param factory the factory to use if this constructor needs to create other math transforms.
* @param parameters the parameter values that define the transform to create.
* @return the math transform created from the given parameters.
* @throws InvalidParameterNameException if the given parameter group contains an unknown parameter.
* @throws ParameterNotFoundException if a required parameter was not found.
* @throws InvalidParameterValueException if a parameter has an invalid value.
* @throws FactoryException if the math transform cannot be created for some other reason
* (for example a required file was not found).
*/
default MathTransform createMathTransform(final MathTransformFactory factory, final ParameterValueGroup parameters)
throws InvalidParameterNameException, ParameterNotFoundException,
InvalidParameterValueException, FactoryException
{
return createMathTransform(new MathTransformProvider.Context() {
@Override public MathTransformFactory getFactory() {
return (factory != null) ? factory : DefaultMathTransformFactory.provider();
}
@Override public ParameterValueGroup getCompletedParameters() {
return parameters;
}
});
}
/**
* The parameter values that define the transform to create, together with its context.
* The context includes the desired number of source and target dimensions,
* and the factory to use if the provider needs to create math transform steps.
*
* <h2>Purpose of the dimension properties</h2>
* If the operation method accepts different number of dimensions (for example, operations
* that can optionally use or compute an ellipsoidal height as the third dimension),
* then the provider can use the desired number of dimensions for selecting which variant
* (2D versus 3D) of the operation method to use.
*
* <h2>Purpose of the factory property</h2>
* Some math transforms may actually be implemented as a chain of operation steps, for example a
* {@linkplain DefaultMathTransformFactory#createConcatenatedTransform(MathTransform, MathTransform)
* concatenation} of {@linkplain DefaultMathTransformFactory#createAffineTransform affine transforms}
* with other kinds of transforms. In such cases, providers can use the given factory for creating
* and concatenating the affine steps.
*
* @author Martin Desruisseaux (Geomatys)
* @version 1.5
* @since 1.5
*/
public interface Context {
/**
* The factory to use if the provider needs to create other math transforms as operation steps.
* This is often the factory which is invoking the {@link #createMathTransform(Context)} method,
* but not necessarily.
*
* <p>The default implementation returns the {@link DefaultMathTransformFactory}.</p>
*
* @return the factory to use if the provider needs to create other math transforms.
*/
default MathTransformFactory getFactory() {
return DefaultMathTransformFactory.provider();
}
/**
* Returns the desired number of source dimensions.
* This value can be the determined from the source coordinate reference system, but not necessarily.
* It can also be the number of target dimensions of the previous step in a concatenated transform.
*
* <p>The number of source dimensions may be unknown (absent).
* In such case, the default number of dimensions is method-specific.
* Some operation methods may search for a {@link org.opengis.parameter.ParameterValue} named {@code "dim"}.
* Other operation methods will fallback on a hard-coded number of dimensions, typically 2 or 3.</p>
*
* @return desired number of source dimensions.
*/
default OptionalInt getSourceDimensions() {
return OptionalInt.empty();
}
/**
* Returns the desired number of target dimensions.
* This value can be the determined from the target coordinate reference system, but not necessarily.
* It can also be the number of source dimensions of the next step in a concatenated transform.
*
* <p>The number of target dimensions may be unknown (absent).
* In such case, the default number of dimensions is method-specific.
* Some operation methods may search for a {@link org.opengis.parameter.ParameterValue} named {@code "dim"}.
* Other operation methods will fallback on a hard-coded number of dimensions, typically 2 or 3.</p>
*
* @return desired number of target dimensions.
*/
default OptionalInt getTargetDimensions() {
return OptionalInt.empty();
}
/**
* Returns the parameter values that fully define the transform to create.
* The parameters returned by this method shall include parameters that are usually inferred from
* the context (source and target <abbr>CRS</abbr>) rather that explicitly provided by the users.
* Examples of inferred parameters are {@code "dim"},
* {@code "semi_major"}, {@code "semi_minor"},
* {@code "src_semi_major"}, {@code "src_semi_minor"},
* {@code "tgt_semi_major"}, {@code "tgt_semi_minor"} and/or
* {@code "inverse_flattening"}, depending on the operation method.
*
* <p>An exception to above rule is the source and target number of dimensions,
* which can be specified either by the {@code "dim"} parameter or by the
* {@link #getSourceDimensions()} and {@link #getTargetDimensions()} methods.
* The reason for this departure is that the number of dimensions is often not a formal
* parameter of an operation method, but can nevertheless be used for inferring variants.
* For example the <q>Geographic3D offsets</q> (EPSG:9660) method does not have a "dimension" argument
* because, as its name implies, that operation method is intended for the three-dimensional case only.
* However, if the number of dimensions is nevertheless 2, the provider may be able to opportunistically
* redirect to the <q>Geographic2D offsets</q> (EPSG:9619) operation method, because those two methods
* are actually implemented by the same code (an affine transform) in Apache SIS.</p>
*
* @return the parameter values that fully define the transform to create.
*/
ParameterValueGroup getCompletedParameters();
}
/**
* Creates a math transform from a group of parameter values and its context.
* The context includes the factory to use and the desired number of source and target dimensions.
* The given number of dimensions is only a hint. Providers can use different numbers of dimensions
* (often hard-coded in the formulas) than the ones specified in the {@code context}.
* Callers should check the actual number of dimensions of the returned transform.
*
* @param context the parameter values that define the transform to create, together with its context.
* @return the math transform created from the given parameters.
* @throws InvalidParameterNameException if the parameter group contains an unknown parameter.
* @throws ParameterNotFoundException if a required parameter was not found.
* @throws InvalidParameterValueException if a parameter has an invalid value.
* @throws FactoryException if the math transform cannot be created for some other reason
* (for example a required file was not found).
*
* @since 1.5
*/
MathTransform createMathTransform(Context context)
throws InvalidParameterNameException, ParameterNotFoundException,
InvalidParameterValueException, FactoryException;
}