Author: desruisseaux Date: Wed Feb 11 21:13:41 2015 New Revision: 1659071 URL: http://svn.apache.org/r1659071 Log: Partial port of DefaultMathTransformFactory. Abstract for now, will become a concrete class after the port has been completed.
Added: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java (with props) Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultOperationMethod.java sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.properties sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages_fr.properties Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java?rev=1659071&r1=1659070&r2=1659071&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java [UTF-8] Wed Feb 11 21:13:41 2015 @@ -31,7 +31,7 @@ import static org.apache.sis.internal.me * * @author Martin Desruisseaux (Geomatys) * @since 0.4 - * @version 0.5 + * @version 0.6 * @module */ public final class Formulas extends Static { @@ -43,7 +43,7 @@ public final class Formulas extends Stat * @see #ANGULAR_TOLERANCE * @see org.apache.sis.internal.util.Numerics#COMPARISON_THRESHOLD */ - public static final double LINEAR_TOLERANCE = 1.0; + public static final double LINEAR_TOLERANCE = 0.01; /** * Default tolerance threshold for comparing ordinate values in a geographic CRS, Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java?rev=1659071&r1=1659070&r2=1659071&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/ReferencingUtilities.java [UTF-8] Wed Feb 11 21:13:41 2015 @@ -24,6 +24,7 @@ import org.opengis.annotation.UML; import org.opengis.annotation.Specification; import org.opengis.referencing.cs.*; import org.opengis.referencing.crs.*; +import org.opengis.referencing.datum.Ellipsoid; import org.opengis.referencing.datum.PrimeMeridian; import org.apache.sis.util.Static; import org.apache.sis.util.Utilities; @@ -49,7 +50,7 @@ import static org.apache.sis.internal.ut * * @author Martin Desruisseaux (IRD, Geomatys) * @since 0.5 - * @version 0.5 + * @version 0.6 * @module */ public final class ReferencingUtilities extends Static { @@ -167,6 +168,27 @@ public final class ReferencingUtilities } /** + * Returns the ellipsoid used by the specified coordinate reference system, providing that + * the two first dimensions use an instance of {@link GeographicCRS}. Otherwise (i.e. if the + * two first dimensions are not geographic), returns {@code null}. + * + * @param crs The coordinate reference system for which to get the ellipsoid. + * @return The ellipsoid in the given CRS, or {@code null} if none. + * + * @since 0.6 + */ + public static Ellipsoid getEllipsoidOfGeographicCRS(CoordinateReferenceSystem crs) { + while (!(crs instanceof GeographicCRS)) { + if (crs instanceof CompoundCRS) { + crs = ((CompoundCRS) crs).getComponents().get(0); + } else { + return null; + } + } + return ((GeographicCRS) crs).getDatum().getEllipsoid(); + } + + /** * Derives a geographic CRS with (<var>longitude</var>, <var>latitude</var>) axis order in decimal degrees. * If no such CRS can be obtained or created, returns {@code null}. * Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java?rev=1659071&r1=1659070&r2=1659071&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java [UTF-8] Wed Feb 11 21:13:41 2015 @@ -377,7 +377,8 @@ public class DefaultParameterValue<T> ex */ @Override public double doubleValue(final Unit<?> unit) throws IllegalArgumentException, IllegalStateException { - return getConverterTo(unit).convert(doubleValue()); + final double value = doubleValue(); // Invoke first in case it throws an exception. + return getConverterTo(unit).convert(value); } /** Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultOperationMethod.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultOperationMethod.java?rev=1659071&r1=1659070&r2=1659071&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultOperationMethod.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultOperationMethod.java [UTF-8] Wed Feb 11 21:13:41 2015 @@ -395,6 +395,8 @@ public class DefaultOperationMethod exte * which is the most conservative return value. * * @return Interface implemented by all coordinate operations that use this method. + * + * @see org.apache.sis.referencing.operation.transform.DefaultMathTransformFactory#getAvailableMethods(Class) */ public Class<? extends SingleOperation> getOperationType() { return SingleOperation.class; @@ -409,6 +411,9 @@ public class DefaultOperationMethod exte * this property is mandatory according ISO 19111, but optional in Apache SIS.</div> * * @return The formula used by this method, or {@code null} if unknown. + * + * @see DefaultFormula + * @see org.apache.sis.referencing.operation.transform.MathTransformProvider */ @Override public Formula getFormula() { @@ -420,6 +425,8 @@ public class DefaultOperationMethod exte * May be null if unknown, as in an <cite>Affine Transform</cite>. * * @return The dimension of source CRS, or {@code null} if unknown. + * + * @see org.apache.sis.referencing.operation.transform.AbstractMathTransform#getSourceDimensions() */ @Override public Integer getSourceDimensions() { @@ -431,6 +438,8 @@ public class DefaultOperationMethod exte * May be null if unknown, as in an <cite>Affine Transform</cite>. * * @return The dimension of target CRS, or {@code null} if unknown. + * + * @see org.apache.sis.referencing.operation.transform.AbstractMathTransform#getTargetDimensions() */ @Override public Integer getTargetDimensions() { Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java?rev=1659071&r1=1659070&r2=1659071&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java [UTF-8] Wed Feb 11 21:13:41 2015 @@ -130,6 +130,8 @@ public abstract class AbstractMathTransf * Gets the dimension of input points. * * @return The dimension of input points. + * + * @see org.apache.sis.referencing.operation.DefaultOperationMethod#getSourceDimensions() */ @Override public abstract int getSourceDimensions(); @@ -138,6 +140,8 @@ public abstract class AbstractMathTransf * Gets the dimension of output points. * * @return The dimension of output points. + * + * @see org.apache.sis.referencing.operation.DefaultOperationMethod#getTargetDimensions() */ @Override public abstract int getTargetDimensions(); Added: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java?rev=1659071&view=auto ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java (added) +++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java [UTF-8] Wed Feb 11 21:13:41 2015 @@ -0,0 +1,603 @@ +/* + * 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.IdentityHashMap; +import java.util.Locale; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.logging.Level; +import javax.measure.quantity.Length; +import javax.measure.unit.SI; +import javax.measure.unit.Unit; +import org.opengis.metadata.Identifier; +import org.opengis.parameter.ParameterValue; +import org.opengis.parameter.ParameterValueGroup; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.cs.CoordinateSystem; +import org.opengis.referencing.datum.Ellipsoid; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.MathTransformFactory; +import org.opengis.referencing.operation.OperationMethod; +import org.opengis.referencing.operation.SingleOperation; +import org.opengis.util.FactoryException; +import org.opengis.util.NoSuchIdentifierException; +import org.apache.sis.internal.referencing.Formulas; +import org.apache.sis.internal.referencing.ReferencingUtilities; +import org.apache.sis.referencing.IdentifiedObjects; +import org.apache.sis.referencing.operation.DefaultOperationMethod; +import org.apache.sis.util.ArgumentChecks; +import org.apache.sis.util.CharSequences; +import org.apache.sis.util.Deprecable; +import org.apache.sis.util.collection.WeakHashSet; +import org.apache.sis.util.iso.AbstractFactory; +import org.apache.sis.util.iso.DefaultNameSpace; +import org.apache.sis.util.logging.Logging; +import org.apache.sis.util.resources.Errors; +import org.apache.sis.util.resources.Messages; + + +/** + * Low level factory for creating {@linkplain AbstractMathTransform math transforms}. + * High level GIS applications usually do not need to use this factory directly. + * They can use the static convenience methods in the {@link org.apache.sis.referencing.CRS} + * or {@link MathTransforms} classes instead. + * + * {@section Math transforms discovery} + * Unless {@linkplain #DefaultMathTransformFactory(Iterable) specified explicitely at construction time}, + * {@code OperationMethod} implementations shall be listed in the following file: + * + * {@preformat text + * META-INF/services/org.opengis.referencing.operation.OperationMethod + * } + * + * {@code DefaultMathTransformFactory} parses the above-cited files in all JAR files in order to find all available + * operation methods. By default, only operation methods that implement the {@link MathTransformProvider} interface + * can be used by the {@code create(…)} methods in this class. + * + * {@section Thread safety} + * This class is safe for multi-thread usage if all referenced {@code OperationMethod} instances are thread-safe. + * There is typically only one {@code MathTransformFactory} instance for the whole application. + * + * @author Martin Desruisseaux (Geomatys, IRD) + * @since 0.6 + * @version 0.6 + * @module + */ +public abstract class DefaultMathTransformFactory extends AbstractFactory implements MathTransformFactory { + /** + * The separator character between an identifier and its namespace in the argument given to + * {@link #getOperationMethod(String)}. For example this is the separator in {@code "EPSG:9807"}. + * + * This is defined as a constant for now, but we may make it configurable in a future version. + */ + private static final char IDENTIFIER_SEPARATOR = DefaultNameSpace.DEFAULT_SEPARATOR; + + /** + * Minimal precision of ellipsoid semi-major and semi-minor axis lengths, in metres. + * If the length difference between the axis of two ellipsoids is greater than this threshold, + * we will report a mismatch. This is used for logging purpose only and do not have any impact + * on the {@code MathTransform} objects to be created by this factory. + */ + private static final double ELLIPSOID_PRECISION = Formulas.LINEAR_TOLERANCE; + + /** + * All methods specified at construction time or found on the classpath. + * If the iterable is an instance of {@link ServiceLoader}, then it will + * be reloaded when {@link #reload()} is invoked. + * + * <p>All uses of this field shall be synchronized on {@code methods}.</p> + */ + private final Iterable<? extends OperationMethod> methods; + + /** + * The methods by name, cached for faster lookup and for avoiding some + * synchronizations on {@link #methods} and {@link #pool}. + */ + private final ConcurrentMap<String, OperationMethod> methodsByName; + + /** + * The methods by type. All uses of this map shall be synchronized on {@code methodsByType}. + * + * <div class="note"><b>Note:</b> + * we do not use a concurrent map here because the number of entries is expected to be very small + * (about 2 entries), which make concurrent algorithms hardly efficient. Furthermore this map is + * not used often. + * </div> + */ + private final Map<Class<?>, OperationMethodSet> methodsByType; + + /** + * The last method used by a {@code create(…)} method. + */ + private final ThreadLocal<OperationMethod> lastMethod; + + /** + * The math transforms created so far. This pool is used in order + * to return instances of existing math transforms when possible. + */ + private final WeakHashSet<MathTransform> pool; + + /** + * Creates a new factory which will discover operation methods with a {@link ServiceLoader}. + * {@code OperationMethod} implementations shall be listed in the following file: + * + * {@preformat text + * META-INF/services/org.opengis.referencing.operation.OperationMethod + * } + * + * {@code DefaultMathTransformFactory} parses the above-cited files in all JAR files in order to find all available + * operation methods. By default, only operation methods that implement the {@link MathTransformProvider} interface + * can be used by the {@code create(…)} methods in this class. + */ + public DefaultMathTransformFactory() { + this(ServiceLoader.load(OperationMethod.class)); + } + + /** + * Creates a new factory which will use the given operation methods. The given iterable is stored by reference — + * its content is <strong>not</strong> copied — in order to allow deferred {@code OperationMethod} constructions. + * Note that by default, only operation methods that implement the {@link MathTransformProvider} interface can be + * used by the {@code create(…)} methods in this class. + * + * <p><b>Requirements:</b></p> + * <ul> + * <li>The given iterable should not contain duplicated elements.</li> + * <li>The given iterable shall be stable: all elements returned by the first iteration must also be + * returned by any subsequent iterations, unless {@link #reload()} has been invoked.</li> + * <li>{@code OperationMethod} instances should also implement {@link MathTransformProvider}.</li> + * <li>All {@code OperationMethod} instances shall be thread-safe.</li> + * <li>The {@code Iterable} itself does not need to be thread-safe since all usages will be synchronized as below: + * + * {@preformat java + * synchronized (methods) { + * for (OperationMethod method : methods) { + * // Use the method here. + * } + * } + * } + * </li> + * </ul> + * + * @param methods The operation methods to use, stored by reference (not copied). + */ + public DefaultMathTransformFactory(final Iterable<? extends OperationMethod> methods) { + ArgumentChecks.ensureNonNull("methods", methods); + this.methods = methods; + methodsByName = new ConcurrentHashMap<>(); + methodsByType = new IdentityHashMap<>(); + lastMethod = new ThreadLocal<>(); + pool = new WeakHashSet<>(MathTransform.class); + } + + /** + * Returns a set of available methods for coordinate operations of the given type. + * The {@code type} argument can be used for filtering the kind of operations described by the returned + * {@code OperationMethod}s. The argument is usually (but not restricted to) one of the following types: + * + * <ul> + * <li>{@link org.opengis.referencing.operation.Transformation} + * for coordinate operations described by empirically derived parameters.</li> + * <li>{@link org.opengis.referencing.operation.Conversion} + * for coordinate operations described by definitions.</li> + * <li>{@link org.opengis.referencing.operation.Projection} + * for conversions from geodetic latitudes and longitudes to plane (map) coordinates.</li> + * <li>{@link SingleOperation} for all coordinate operations.</li> + * </ul> + * + * This method may conservatively return more {@code OperationMethod} elements than requested + * if it does not support filtering by the given type. + * + * @param type <code>{@linkplain SingleOperation}.class</code> for fetching all operation methods, + * <code>{@linkplain Projection}.class</code> for fetching only map projection methods, <i>etc</i>. + * @return Methods available in this factory for coordinate operations of the given type. + * + * @see #getDefaultParameters(String) + * @see #createParameterizedTransform(ParameterValueGroup) + * @see DefaultOperationMethod#getOperationType() + */ + @Override + public Set<OperationMethod> getAvailableMethods(final Class<? extends SingleOperation> type) { + OperationMethodSet set; + synchronized (methodsByType) { + set = methodsByType.get(type); + } + if (set == null) { + /* + * Implementation note: we are better to avoid holding a lock on 'methods' and 'methodsByType' + * in same time because the 'methods' iterator could be a user's implementation which callback + * this factory. + */ + synchronized (methods) { + set = new OperationMethodSet(type, methods); + } + final OperationMethodSet previous; + synchronized (methodsByType) { + previous = methodsByType.putIfAbsent(type, set); + } + if (previous != null) { + set = previous; + } + } + return set; + } + + /** + * Returns the operation method for the specified name or identifier. The given argument shall be either + * a method {@linkplain DefaultOperationMethod#getName() name} (e.g. <cite>"Transverse Mercator"</cite>) + * or one of its {@linkplain DefaultOperationMethod#getIdentifiers() identifiers} (e.g. {@code "EPSG:9807"}). + * + * <p>The search is case-insensitive. Comparisons against method names can be + * {@linkplain DefaultOperationMethod#isHeuristicMatchForName(String) heuristic}.</p> + * + * <p>If more than one method match the given identifier, then the first (according iteration order) + * non-{@linkplain Deprecable#isDeprecated() deprecated} matching method is returned. If all matching + * methods are deprecated, the first one is returned.</p> + * + * @param identifier The name or identifier of the operation method to search. + * @return The operation method for the given name or identifier. + * @throws NoSuchIdentifierException if there is no operation method registered for the specified identifier. + */ + public OperationMethod getOperationMethod(String identifier) throws NoSuchIdentifierException { + identifier = CharSequences.trimWhitespaces(identifier); + ArgumentChecks.ensureNonEmpty("identifier", identifier); + OperationMethod method = methodsByName.get(identifier); + if (method == null) { + for (final OperationMethod m : methods) { + if (matches(m, identifier)) { + /* + * Stop the iteration at the first non-deprecated method. + * If we find only deprecated methods, take the first one. + */ + final boolean isDeprecated = (m instanceof Deprecable) && ((Deprecable) m).isDeprecated(); + if (!isDeprecated || method == null) { + method = m; + if (!isDeprecated) { + break; + } + } + } + } + if (method == null) { + throw new NoSuchIdentifierException(Errors.format(Errors.Keys.NoSuchOperationMethod_1, method), identifier); + } + /* + * Remember the method we just found, for faster check next time. + */ + final OperationMethod previous = methodsByName.putIfAbsent(identifier.intern(), method); + if (previous != null) { + method = previous; + } + } + return method; + } + + /** + * Returns {@code true} if the name or an identifier of the given method matches the given {@code identifier}. + * + * This method is private and static for now, but we may consider to make it a protected member in a future + * SIS version in order to give users a chance to override the matching criterion. We don't do that yet + * because we would like to have at least one use case before doing so. + * + * @param method The method to test for a match. + * @param identifier The name or identifier of the operation method to search. + * @return {@code true} if the given method is a match for the given identifier. + */ + private static boolean matches(final OperationMethod method, final String identifier) { + if (IdentifiedObjects.isHeuristicMatchForName(method, identifier)) { + return true; + } + for (int s = identifier.indexOf(IDENTIFIER_SEPARATOR); s >= 0; + s = identifier.indexOf(IDENTIFIER_SEPARATOR, s)) + { + final String codespace = identifier.substring(0, s).trim(); + final String code = identifier.substring(++s).trim(); + for (final Identifier id : method.getIdentifiers()) { + if (codespace.equalsIgnoreCase(id.getCodeSpace()) && code.equalsIgnoreCase(id.getCode())) { + return true; + } + } + } + return false; + } + + /** + * Returns the default parameter values for a math transform using the given method. + * The method argument is the name of any operation method returned by the {@link #getAvailableMethods(Class)} method. + * A typical example is + * "<a href="http://www.remotesensing.org/geotiff/proj_list/transverse_mercator.html">Transverse Mercator</a>"). + * + * <p>This method creates new parameter instances at every call. The returned object is intended to be modified + * by the user before to be passed to <code>{@linkplain #createParameterizedTransform(ParameterValueGroup) + * createParameterizedTransform}(parameters)</code>.</p> + * + * @param method The name or identifier of the operation method to search. + * @return A new group of parameter values for the {@code OperationMethod} identified by the given name. + * @throws NoSuchIdentifierException if there is no method registered for the given name or identifier. + * + * @see #getAvailableMethods(Class) + * @see #createParameterizedTransform(ParameterValueGroup) + * @see org.apache.sis.referencing.operation.transform.AbstractMathTransform#getParameterValues() + */ + @Override + public ParameterValueGroup getDefaultParameters(final String method) throws NoSuchIdentifierException { + return getOperationMethod(method).getParameters().createValue(); + } + + /** + * Returns the value of the given parameter in the given unit, or {@code NaN} if the parameter is not set. + * + * <p><b>NOTE:</b> Do not merge this method with {@code ensureSet(…)}. We keep those two methods + * separated in order to give to {@code createBaseToDerived(…)} a "all or nothing" behavior.</p> + */ + private static double getValue(final ParameterValue<?> parameter, final Unit<Length> unit) { + return (parameter.getValue() != null) ? parameter.doubleValue(unit) : Double.NaN; + } + + /** + * Ensures that a value is set in the given parameter. + * + * <ul> + * <li>If the parameter has no value, then it is set to the given value.<li> + * <li>If the parameter already has a value, then the parameter is left unchanged + * but its value is compared to the given one for consistency.</li> + * </ul> + * + * @param parameter The parameter which must have a value. + * @param actual The current parameter value, or {@code NaN} if none. + * @param expected The expected parameter value, derived from the ellipsoid. + * @param unit The unit of {@code value}. + * @param tolerance Maximal difference (in unit of {@code unit}) for considering the two values as equivalent. + * @return {@code true} if there is a mismatch between the actual value and the expected one. + */ + private static boolean ensureSet(final ParameterValue<?> parameter, final double actual, + final double expected, final Unit<?> unit, final double tolerance) + { + if (Math.abs(actual - expected) <= tolerance) { + return false; + } + if (Double.isNaN(actual)) { + parameter.setValue(expected, unit); + } + return true; + } + + /** + * Creates a {@linkplain #createParameterizedTransform(ParameterValueGroup) parameterized transform} + * from a base CRS to a derived CS. This convenience method {@linkplain #createConcatenatedTransform + * concatenates} the parameterized transform with any other transform required for performing units + * changes and ordinates swapping. + * + * In addition, this method infers the {@code "semi_major"} and {@code "semi_minor"} parameters values + * from the {@linkplain org.apache.sis.referencing.datum.DefaultEllipsoid ellipsoid} associated to the + * {@code baseCRS}, if those parameters are not explicitly given and if they are applicable (typically + * for cartographic projections). + * + * <p>The {@code OperationMethod} instance used by this method can be obtained by a call to + * {@link #getLastMethodUsed()}.</p> + * + * @param baseCRS The source coordinate reference system. + * @param parameters The parameter values for the transform. + * @param derivedCS The target coordinate system. + * @return The parameterized transform. + * @throws NoSuchIdentifierException if there is no transform registered for the method. + * @throws FactoryException if the object creation failed. This exception is thrown + * if some required parameter has not been supplied, or has illegal value. + */ + @Override + public MathTransform createBaseToDerived(final CoordinateReferenceSystem baseCRS, + final ParameterValueGroup parameters, final CoordinateSystem derivedCS) + throws NoSuchIdentifierException, FactoryException + { + /* + * If the user's parameters do not contain semi-major and semi-minor axis lengths, infer + * them from the ellipsoid. We have to do that because those parameters are often omitted, + * since the standard place where to provide this information is in the ellipsoid object. + */ + RuntimeException failure = null; + final Ellipsoid ellipsoid = ReferencingUtilities.getEllipsoidOfGeographicCRS(baseCRS); + if (ellipsoid != null) { + ParameterValue<?> mismatchedParam = null; + double mismatchedValue = 0; + try { + final ParameterValue<?> semiMajor = parameters.parameter("semi_major"); + final ParameterValue<?> semiMinor = parameters.parameter("semi_minor"); + final Unit<Length> axisUnit = ellipsoid.getAxisUnit(); + /* + * The two calls to getOptional(…) shall succeed before we write anything, in order to have a + * "all or nothing" behavior as much as possible. Note that Ellipsoid.getSemi**Axis() have no + * reason to fail, so we don't take precaution for them. + */ + final double a = getValue(semiMajor, axisUnit); + final double b = getValue(semiMinor, axisUnit); + final double tol = SI.METRE.getConverterTo(axisUnit).convert(ELLIPSOID_PRECISION); + if (ensureSet(semiMajor, a, ellipsoid.getSemiMajorAxis(), axisUnit, tol)) { + mismatchedParam = semiMajor; + mismatchedValue = a; + } + if (ensureSet(semiMinor, b, ellipsoid.getSemiMinorAxis(), axisUnit, tol)) { + mismatchedParam = semiMinor; + mismatchedValue = b; + } + } catch (IllegalArgumentException | IllegalStateException e) { + /* + * Parameter not found, or is not numeric, or unit of measurement is not linear. + * Those exceptions should never occur with map projections, but may happen for + * some other operations like Molodenski¹. + * + * Do not touch to the parameters. We will see if createParameterizedTransform(…) + * can do something about that. If it can not, createParameterizedTransform(…) is + * the right place to throw the exception. + * + * ¹ The actual Molodenski parameter names are "src_semi_major" and "src_semi_minor". + * But we do not try to set them because we have no way to set the corresponding + * "tgt_semi_major" and "tgt_semi_minor" parameters anyway. + */ + failure = e; + } + if (mismatchedParam != null) { + Logging.log(DefaultMathTransformFactory.class, "createBaseToDerived", + Messages.getResources((Locale) null).getLogRecord(Level.WARNING, + Messages.Keys.MismatchedEllipsoidAxisLength_3, ellipsoid.getName().getCode(), + mismatchedParam.getDescriptor().getName().getCode(), mismatchedValue)); + } + } + MathTransform baseToDerived; + try { + baseToDerived = createParameterizedTransform(parameters); + final OperationMethod method = lastMethod.get(); + baseToDerived = createBaseToDerived(baseCRS, baseToDerived, derivedCS); + lastMethod.set(method); + } catch (FactoryException e) { + if (failure != null) { + e.addSuppressed(failure); + } + throw e; + } + return baseToDerived; + } + + /** + * Creates a transform from a base CRS to a derived CS. This method expects a "raw" transform without + * unit conversion or axis swapping. Such "raw" transforms are typically map projections working on + * (<cite>longitude</cite>, <cite>latitude</cite>) axes in degrees and (<cite>x</cite>, <cite>y</cite>) + * axes in metres. This method inspects the coordinate systems and prepend or append the unit conversions + * and axis swapping automatically. + * + * @param baseCRS The source coordinate reference system. + * @param projection The "raw" <cite>base to derived</cite> transform. + * @param derivedCS the target coordinate system. + * @return The parameterized transform. + * @throws FactoryException if the object creation failed. This exception is thrown + * if some required parameter has not been supplied, or has illegal value. + */ + public abstract MathTransform createBaseToDerived(final CoordinateReferenceSystem baseCRS, + final MathTransform projection, final CoordinateSystem derivedCS) + throws FactoryException; + + /** + * Creates a transform by concatenating two existing transforms. + * A concatenated transform acts in the same way as applying two transforms, one after the other. + * + * <p>The dimension of the output space of the first transform must match the dimension of the input space + * in the second transform. In order to concatenate more than two transforms, use this method repeatedly.</p> + * + * @param transform1 The first transform to apply to points. + * @param transform2 The second transform to apply to points. + * @return The concatenated transform. + * @throws FactoryException if the object creation failed. + */ + @Override + public MathTransform createConcatenatedTransform(final MathTransform transform1, + final MathTransform transform2) + throws FactoryException + { + MathTransform tr; + try { + tr = MathTransforms.concatenate(transform1, transform2); + } catch (IllegalArgumentException exception) { + throw new FactoryException(exception); + } + tr = pool.unique(tr); + return tr; + } + + /** + * Creates a transform which passes through a subset of ordinates to another transform. + * This allows transforms to operate on a subset of ordinates. + * + * <div class="note"><b>Example:</b> + * Giving (<var>latitude</var>, <var>longitude</var>, <var>height</var>) coordinates, + * a pass through transform can convert the height values from meters to feet without + * affecting the (<var>latitude</var>, <var>longitude</var>) values.</div> + * + * The resulting transform will have the following dimensions: + * + * {@preformat java + * Source: firstAffectedOrdinate + subTransform.getSourceDimensions() + numTrailingOrdinates + * Target: firstAffectedOrdinate + subTransform.getTargetDimensions() + numTrailingOrdinates + * } + * + * @param firstAffectedOrdinate The lowest index of the affected ordinates. + * @param subTransform Transform to use for affected ordinates. + * @param numTrailingOrdinates Number of trailing ordinates to pass through. Affected ordinates will range + * from {@code firstAffectedOrdinate} inclusive to {@code dimTarget-numTrailingOrdinates} exclusive. + * @return A pass through transform. + * @throws FactoryException if the object creation failed. + */ + @Override + public MathTransform createPassThroughTransform(final int firstAffectedOrdinate, + final MathTransform subTransform, + final int numTrailingOrdinates) + throws FactoryException + { + MathTransform tr; + try { + tr = PassThroughTransform.create(firstAffectedOrdinate, subTransform, numTrailingOrdinates); + } catch (IllegalArgumentException exception) { + throw new FactoryException(exception); + } + tr = pool.unique(tr); + return tr; + } + + /** + * Returns the operation method used for the latest call to + * {@link #createParameterizedTransform(ParameterValueGroup)} in the currently running thread. + * Returns {@code null} if not applicable. + * + * @see #createParameterizedTransform(ParameterValueGroup) + */ + @Override + public OperationMethod getLastMethodUsed() { + return lastMethod.get(); + } + + /** + * Notifies this factory that the elements provided by the {@code Iterable<OperationMethod>} may have changed. + * This method performs the following steps: + * + * <ul> + * <li>Clears all caches.</li> + * <li>If the {@code Iterable} given at construction time is an instance of {@link ServiceLoader}, + * invokes its {@code reload()} method.</li> + * </ul> + * + * This method is useful to sophisticated applications which dynamically make new plug-ins available at runtime, + * for example following changes of the application classpath. + * + * @see #DefaultMathTransformFactory(Iterable) + * @see ServiceLoader#reload() + */ + public void reload() { + synchronized (methods) { + methodsByName.clear(); + if (methods instanceof ServiceLoader<?>) { + ((ServiceLoader<?>) methods).reload(); + } + synchronized (methodsByType) { + for (final OperationMethodSet c : methodsByType.values()) { + c.reset(); + } + } + pool.clear(); + } + } +} Propchange: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java ------------------------------------------------------------------------------ svn:mime-type = text/plain;charset=UTF-8 Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java?rev=1659071&r1=1659070&r2=1659071&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java [UTF-8] Wed Feb 11 21:13:41 2015 @@ -585,6 +585,11 @@ public final class Errors extends Indexe public static final short NoSuchAuthorityCode_3 = 137; /** + * No operation method found for name of identifier “{0}”. + */ + public static final short NoSuchOperationMethod_1 = 179; + + /** * No unit of measurement has been specified. */ public static final short NoUnit = 72; Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties?rev=1659071&r1=1659070&r2=1659071&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties [ISO-8859-1] (original) +++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties [ISO-8859-1] Wed Feb 11 21:13:41 2015 @@ -150,6 +150,7 @@ NotASkewSymmetricMatrix = Matr NotAUnicodeIdentifier_1 = Text \u201c{0}\u201d is not a Unicode identifier. NotComparableClass_1 = Class \u2018{0}\u2019 is not a comparable. NoSuchAuthorityCode_3 = No code \u201c{2}\u201d from authority \u201c{0}\u201d found for object of type \u2018{1}\u2019. +NoSuchOperationMethod_1 = No operation method found for name of identifier \u201c{0}\u201d. NoUnit = No unit of measurement has been specified. NullArgument_1 = Argument \u2018{0}\u2019 shall not be null. NullCollectionElement_1 = \u2018{0}\u2019 collection does not accept null elements. Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties?rev=1659071&r1=1659070&r2=1659071&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties [ISO-8859-1] (original) +++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties [ISO-8859-1] Wed Feb 11 21:13:41 2015 @@ -140,6 +140,7 @@ NotASkewSymmetricMatrix = La m NotAUnicodeIdentifier_1 = Le texte \u00ab\u202f{0}\u202f\u00bb n\u2019est pas un identifiant Unicode. NotComparableClass_1 = La classe \u2018{0}\u2019 n\u2019est pas comparable. NoSuchAuthorityCode_3 = Aucun code \u00ab\u202f{2}\u202f\u00bb de l\u2019autorit\u00e9 \u00ab\u202f{0}\u202f\u00bb n\u2019a \u00e9t\u00e9 trouv\u00e9 pour un objet de type \u2018{1}\u2019. +NoSuchOperationMethod_1 = Aucune m\u00e9thode n\u2019a \u00e9t\u00e9 trouv\u00e9e pour le nom ou l\u2019identifiant \u00ab\u202f{0}\u202f\u00bb. NoUnit = Aucune unit\u00e9 de mesure n\u2019a \u00e9t\u00e9 sp\u00e9cifi\u00e9e. NullArgument_1 = L\u2019argument \u2018{0}\u2019 ne doit pas \u00eatre nul. NullCollectionElement_1 = La collection \u2018{0}\u2019 n\u2019accepte pas les valeurs nulles. Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java?rev=1659071&r1=1659070&r2=1659071&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java [UTF-8] Wed Feb 11 21:13:41 2015 @@ -92,6 +92,12 @@ public final class Messages extends Inde public static final short LocalesDiscarded = 3; /** + * The “{1}” parameter could have been omitted. But it has been given a value of {2} which does + * not match the definition of the “{0}” ellipsoid. + */ + public static final short MismatchedEllipsoidAxisLength_3 = 9; + + /** * Property “{0}” is hidden by “{1}”. */ public static final short PropertyHiddenBy_2 = 4; Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.properties URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.properties?rev=1659071&r1=1659070&r2=1659071&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.properties [ISO-8859-1] (original) +++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.properties [ISO-8859-1] Wed Feb 11 21:13:41 2015 @@ -22,4 +22,5 @@ IgnoredPropertiesAfterFirst_1 = Ignore IgnoredPropertyAssociatedTo_1 = Ignored property associated to \u2018{0}\u2019. PropertyHiddenBy_2 = Property \u201c{0}\u201d is hidden by \u201c{1}\u201d. LocalesDiscarded = Text were discarded for some locales. +MismatchedEllipsoidAxisLength_3 = The \u201c{1}\u201d parameter could have been omitted. But it has been given a value of {2} which does not match the definition of the \u201c{0}\u201d ellipsoid. UnparsableValueStoredAsText_2 = Can not parse \u201c{1}\u201d as an instance of \u2018{0}\u2019. The value is stored as plain text instead, but will be ignored by some processing. Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages_fr.properties URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages_fr.properties?rev=1659071&r1=1659070&r2=1659071&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages_fr.properties [ISO-8859-1] (original) +++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages_fr.properties [ISO-8859-1] Wed Feb 11 21:13:41 2015 @@ -22,4 +22,5 @@ IgnoredPropertiesAfterFirst_1 = Des pr IgnoredPropertyAssociatedTo_1 = Une propri\u00e9t\u00e9 associ\u00e9e \u00e0 \u2018{0}\u2019 a \u00e9t\u00e9 ignor\u00e9e. PropertyHiddenBy_2 = La propri\u00e9t\u00e9 \u00ab\u202f{0}\u202f\u00bb est masqu\u00e9e par \u00ab\u202f{1}\u202f\u00bb. LocalesDiscarded = Des textes ont \u00e9t\u00e9 ignor\u00e9s pour certaines langues. +MismatchedEllipsoidAxisLength_3 = Le param\u00e8tre \u00ab\u202f{1}\u202f\u00bb aurait pu \u00eatre omis. Mais il lui a \u00e9t\u00e9 donn\u00e9 la {2} qui ne correspond pas \u00e0 la d\u00e9finition de l'ellipso\u00efde \u00ab\u202f{0}\u202f\u00bb. UnparsableValueStoredAsText_2 = La valeur \u00ab\u202f{1}\u202f\u00bb ne peut pas \u00eatre interpr\u00e9t\u00e9e comme une instance de \u2018{0}\u2019. Elle est donc m\u00e9moris\u00e9e sous sa forme textuelle, mais sera ignor\u00e9e par certains traitements.