| /* |
| * 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.internal.referencing.provider; |
| |
| import java.util.Map; |
| import java.util.Collections; |
| import javax.xml.bind.annotation.XmlTransient; |
| import javax.measure.Unit; |
| import org.opengis.util.FactoryException; |
| import org.opengis.parameter.ParameterValueGroup; |
| import org.opengis.parameter.ParameterDescriptor; |
| import org.opengis.parameter.ParameterDescriptorGroup; |
| import org.opengis.parameter.ParameterNotFoundException; |
| import org.opengis.parameter.InvalidParameterValueException; |
| import org.opengis.referencing.operation.MathTransform; |
| import org.opengis.referencing.operation.MathTransformFactory; |
| import org.apache.sis.metadata.iso.citation.Citations; |
| import org.apache.sis.parameter.ParameterBuilder; |
| import org.apache.sis.parameter.Parameters; |
| import org.apache.sis.referencing.datum.DefaultEllipsoid; |
| import org.apache.sis.referencing.operation.transform.MolodenskyTransform; |
| import org.apache.sis.internal.referencing.NilReferencingObject; |
| import org.apache.sis.internal.referencing.Formulas; |
| import org.apache.sis.internal.system.Loggers; |
| import org.apache.sis.internal.util.Constants; |
| import org.apache.sis.measure.Units; |
| import org.apache.sis.util.resources.Errors; |
| import org.apache.sis.util.logging.Logging; |
| import org.apache.sis.util.Debug; |
| |
| |
| /** |
| * The provider for "<cite>Molodensky transformation</cite>" (EPSG:9604). |
| * This provider constructs transforms between two geographic reference systems without passing though a geocentric one. |
| * This class nevertheless extends {@link GeocentricAffineBetweenGeographic} because it is an approximation of |
| * {@link GeocentricTranslation3D}. |
| * |
| * <p>The translation terms (<var>dx</var>, <var>dy</var> and <var>dz</var>) are common to all authorities. |
| * But remaining parameters are specified in different ways depending on the authority:</p> |
| * |
| * <ul> |
| * <li>EPSG defines <cite>"Semi-major axis length difference"</cite> |
| * and <cite>"Flattening difference"</cite> parameters.</li> |
| * <li>OGC rather defines "{@code src_semi_major}", "{@code src_semi_minor}", |
| * "{@code tgt_semi_major}", "{@code tgt_semi_minor}" and "{@code dim}" parameters.</li> |
| * </ul> |
| * |
| * @author Rueben Schulz (UBC) |
| * @author Martin Desruisseaux (IRD, Geomatys) |
| * @version 1.0 |
| * @since 0.7 |
| * @module |
| */ |
| @XmlTransient |
| public final class Molodensky extends GeocentricAffineBetweenGeographic { |
| /** |
| * Serial number for inter-operability with different versions. |
| */ |
| private static final long serialVersionUID = 8126525068450868912L; |
| |
| /** |
| * The operation parameter descriptor for the <cite>Semi-major axis length difference</cite> |
| * optional parameter value. This parameter is defined by the EPSG database and can be used |
| * in replacement of {@link #TGT_SEMI_MAJOR}. |
| * Units are {@linkplain Units#METRE metres}. |
| * |
| * <!-- Generated by ParameterNameTableGenerator --> |
| * <table class="sis"> |
| * <caption>Parameter names</caption> |
| * <tr><td> EPSG: </td><td> Semi-major axis length difference </td></tr> |
| * </table> |
| * <b>Notes:</b> |
| * <ul> |
| * <li>No default value</li> |
| * </ul> |
| */ |
| public static final ParameterDescriptor<Double> AXIS_LENGTH_DIFFERENCE; |
| |
| /** |
| * The operation parameter descriptor for the <cite>Flattening difference</cite> optional parameter value. |
| * This parameter is defined by the EPSG database and can be used in replacement of {@link #TGT_SEMI_MINOR}. |
| * Valid values range from -1 to +1, {@linkplain Units#UNITY dimensionless}. |
| * |
| * <!-- Generated by ParameterNameTableGenerator --> |
| * <table class="sis"> |
| * <caption>Parameter names</caption> |
| * <tr><td> EPSG: </td><td> Flattening difference </td></tr> |
| * </table> |
| * <b>Notes:</b> |
| * <ul> |
| * <li>Value domain: [-1.0 … 1.0]</li> |
| * <li>No default value</li> |
| * </ul> |
| */ |
| public static final ParameterDescriptor<Double> FLATTENING_DIFFERENCE; |
| |
| /** |
| * The group of all parameters expected by this coordinate operation. |
| */ |
| public static final ParameterDescriptorGroup PARAMETERS; |
| static { |
| final ParameterBuilder builder = builder(); |
| AXIS_LENGTH_DIFFERENCE = builder.addIdentifier("8654").addName("Semi-major axis length difference").create(Double.NaN, Units.METRE); |
| FLATTENING_DIFFERENCE = builder.addIdentifier("8655").addName("Flattening difference").createBounded(-1, +1, Double.NaN, Units.UNITY); |
| PARAMETERS = builder.setRequired(true) |
| .addIdentifier("9604") |
| .addName("Molodensky") |
| .addName(Citations.OGC, "Molodenski") |
| .createGroup(DIMENSION, // OGC only |
| SRC_SEMI_MAJOR, SRC_SEMI_MINOR, // OGC only |
| TGT_SEMI_MAJOR, TGT_SEMI_MINOR, // OGC only - redundant with differences |
| TX, // OGC and EPSG |
| TY, // OGC and EPSG |
| TZ, // OGC and EPSG |
| AXIS_LENGTH_DIFFERENCE, // EPSG only |
| FLATTENING_DIFFERENCE); // EPSG only |
| } |
| |
| /** |
| * Creates a descriptor for the internal parameters of {@link MolodenskyTransform}. |
| * This is similar to the standard parameters except that the redundant target axes |
| * lengths are omitted. |
| * |
| * @return internal parameter descriptor. |
| */ |
| @Debug |
| public static ParameterDescriptorGroup internal() { |
| final ParameterBuilder builder = builder().setCodeSpace(Citations.SIS, Constants.SIS); |
| ParameterDescriptor<Boolean> abridged = builder.addName("abridged").create(Boolean.class, null); |
| return builder.addName("Molodensky (radians domain)") |
| .createGroup(DIMENSION, |
| SRC_SEMI_MAJOR, |
| SRC_SEMI_MINOR, |
| AXIS_LENGTH_DIFFERENCE, |
| FLATTENING_DIFFERENCE, |
| TX, |
| TY, |
| TZ, |
| abridged); |
| } |
| |
| /** |
| * Constructs a new provider. |
| */ |
| public Molodensky() { |
| this(3, 3, new Molodensky[4]); |
| redimensioned[0] = new Molodensky(2, 2, redimensioned); |
| redimensioned[1] = new Molodensky(2, 3, redimensioned); |
| redimensioned[2] = new Molodensky(3, 2, redimensioned); |
| redimensioned[3] = this; |
| } |
| |
| /** |
| * Constructs a provider for the given dimensions. |
| * |
| * @param sourceDimensions number of dimensions in the source CRS of this operation method. |
| * @param targetDimensions number of dimensions in the target CRS of this operation method. |
| * @param redimensioned providers for all combinations between 2D and 3D cases. |
| */ |
| private Molodensky(int sourceDimensions, int targetDimensions, GeodeticOperation[] redimensioned) { |
| super(sourceDimensions, targetDimensions, PARAMETERS, redimensioned); |
| } |
| |
| /** |
| * While Molodensky method is an approximation of geocentric translation, this is not exactly that. |
| */ |
| @Override |
| int getType() { |
| return OTHER; |
| } |
| |
| /** |
| * Creates a Molodensky transform from the specified group of parameter values. |
| * |
| * @param factory the factory to use for creating concatenated transforms. |
| * @param values the group of parameter values. |
| * @return the created Molodensky transform. |
| * @throws FactoryException if a transform can not be created. |
| */ |
| @Override |
| public MathTransform createMathTransform(final MathTransformFactory factory, final ParameterValueGroup values) |
| throws FactoryException |
| { |
| return createMathTransform(factory, Parameters.castOrWrap(values), |
| getSourceDimensions(), getTargetDimensions(), false); |
| } |
| |
| /** |
| * Creates a (potentially abridged) Molodensky transform from the specified group of parameter values. |
| * The specified number of dimensions are <em>default</em> values; they may be overridden by user parameters. |
| * |
| * @param factory the factory to use for creating concatenated transforms. |
| * @param values the group of parameter values specified by the users. |
| * @param sourceDimensions number of source dimensions (2 or 3) of the operation method. |
| * @param targetDimensions number of target dimensions (2 or 3) of the operation method. |
| * @param isAbridged {@code true} for the abridged form. |
| * @return the created (abridged) Molodensky transform. |
| * @throws FactoryException if a transform can not be created. |
| */ |
| static MathTransform createMathTransform(final MathTransformFactory factory, final Parameters values, |
| int sourceDimensions, int targetDimensions, final boolean isAbridged) throws FactoryException |
| { |
| final Integer dim = values.getValue(DIMENSION); |
| if (dim != null) { |
| final int n = dim; // Unboxing. |
| if (n != 2 && n != 3) { |
| throw new InvalidParameterValueException(Errors.format( |
| Errors.Keys.IllegalArgumentValue_2, "dim", dim), "dim", dim); |
| } |
| sourceDimensions = targetDimensions = n; |
| } |
| /* |
| * Following method calls implicitly convert parameter values to metres. |
| * We do not try to match ellipsoid axis units because: |
| * |
| * 1) It complicates the code. |
| * 2) We have no guarantees that ellipsoid unit match the coordinate system unit. |
| * 3) OGC 01-009 explicitly said that angles are in degrees and heights in metres. |
| * 4) The above is consistent with what we do for map projections. |
| */ |
| double sa = values.doubleValue( SRC_SEMI_MAJOR); |
| double sb = values.doubleValue( SRC_SEMI_MINOR); |
| double ta = optional (values, TGT_SEMI_MAJOR); |
| double tb = optional (values, TGT_SEMI_MINOR); |
| double Δa = conditional(values, AXIS_LENGTH_DIFFERENCE, ta); |
| double Δf = conditional(values, FLATTENING_DIFFERENCE, tb); |
| if (Double.isNaN(ta)) { |
| ta = sa + Δa; |
| } |
| if (Double.isNaN(tb)) { |
| tb = ta*(sb/sa - Δf); |
| } |
| final Map<String,?> name = Collections.singletonMap(DefaultEllipsoid.NAME_KEY, NilReferencingObject.UNNAMED); |
| final Ellipsoid source = new Ellipsoid(name, sa, sb, Δa, Δf); |
| final Ellipsoid target = new Ellipsoid(name, ta, tb, -Δa, -Δf); |
| source.other = target; |
| target.other = source; |
| source.computeDifferences(values); |
| return MolodenskyTransform.createGeodeticTransformation(factory, |
| source, sourceDimensions >= 3, |
| target, targetDimensions >= 3, |
| values.doubleValue(TX), |
| values.doubleValue(TY), |
| values.doubleValue(TZ), |
| isAbridged); |
| } |
| |
| /** |
| * Returns the value of the given parameter, or NaN if undefined. |
| */ |
| private static double optional(final Parameters values, final ParameterDescriptor<Double> parameter) { |
| final Double value = values.getValue(parameter); |
| return (value != null) ? value : Double.NaN; |
| } |
| |
| /** |
| * Returns the value of the given parameter as a mandatory parameter if {@code condition} is NaN, |
| * or an optional parameter otherwise. |
| */ |
| private static double conditional(Parameters values, ParameterDescriptor<Double> parameter, double condition) { |
| return Double.isNaN(condition) ? values.doubleValue(parameter) : optional(values, parameter); |
| } |
| |
| /** |
| * A temporary ellipsoid used only for passing arguments to the {@link MolodenskyTransform} constructor. |
| * The intent is to use the Δa and Δf values explicitly specified in the EPSG parameters if available, |
| * or to compute them only if no Δa or Δf values where specified. |
| */ |
| @SuppressWarnings("serial") |
| private static final class Ellipsoid extends DefaultEllipsoid { |
| /** The EPSG parameter values, or NaN if unspecified. */ |
| private double Δa, Δf; |
| |
| /** The ellipsoid for which Δa and Δf are valid. */ |
| Ellipsoid other; |
| |
| /** Creates a new temporary ellipsoid with explicitly provided Δa and Δf values. */ |
| Ellipsoid(Map<String,?> name, double a, double b, double Δa, double Δf) { |
| super(name, a, b, Formulas.getInverseFlattening(a, b), false, Units.METRE); |
| this.Δa = Δa; |
| this.Δf = Δf; |
| } |
| |
| /** |
| * Computes Δa and Δf now if not already done and tries to store the result in the given parameters. |
| * The parameters are set in order to complete them when the user specified the OGC parameters (axis |
| * lengths) and not the EPSG ones (axis and flattening differences). |
| */ |
| void computeDifferences(final Parameters values) { |
| if (Double.isNaN(Δa)) { |
| Δa = super.semiMajorAxisDifference(other); |
| setIfPresent(values, AXIS_LENGTH_DIFFERENCE, Δa, getAxisUnit()); |
| } |
| if (Double.isNaN(Δf)) { |
| Δf = super.flatteningDifference(other); |
| setIfPresent(values, FLATTENING_DIFFERENCE, Δf, Units.UNITY); |
| } |
| } |
| |
| /** |
| * Tries to set the given parameter value. This method should be invoked only when completing parameters |
| * without explicit values. This approach completes the work done in {@code DefaultMathTransformFactory}, |
| * which already completed the {@code src_semi_major}, {@code src_semi_minor}, {@code tgt_semi_major} and |
| * {@code tgt_semi_minor} parameters. |
| * |
| * @param values the group in which to set the parameter values. |
| * @param parameter descriptor of the parameter to set. |
| * @param value the new value. |
| * @param unit unit of measurement for the new value. |
| */ |
| private static void setIfPresent(final Parameters values, final ParameterDescriptor<Double> parameter, |
| final double value, final Unit<?> unit) |
| { |
| try { |
| values.getOrCreate(parameter).setValue(value, unit); |
| } catch (ParameterNotFoundException | InvalidParameterValueException e) { |
| // Nonn-fatal since this attempt was only for information purpose. |
| Logging.recoverableException(Logging.getLogger(Loggers.COORDINATE_OPERATION), |
| Molodensky.class, "createMathTransform", e); |
| } |
| } |
| |
| /** Returns Δa as specified in the parameters if possible, or compute it otherwise. */ |
| @Override public double semiMajorAxisDifference(final org.opengis.referencing.datum.Ellipsoid target) { |
| return (target == other && !Double.isNaN(Δa)) ? Δa : super.semiMajorAxisDifference(target); |
| } |
| |
| /** Returns Δf as specified in the parameters if possible, or compute it otherwise. */ |
| @Override public double flatteningDifference(final org.opengis.referencing.datum.Ellipsoid target) { |
| return (target == other && !Double.isNaN(Δf)) ? Δf : super.flatteningDifference(target); |
| } |
| } |
| } |