This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit 49ceb864506fdf6cb48235375195951b53712f0a Author: Martin Desruisseaux <[email protected]> AuthorDate: Wed Apr 27 19:06:55 2022 +0200 Replace the way variants of map projections are represented, using enumeration instead of a single integer. This change makes possible to attach more information to the enumeration value. This is a step for making easier to add support for variants such as "Mercator Auxiliary Sphere". --- .../referencing/provider/AbstractMercator.java | 12 +- .../internal/referencing/provider/Mercator1SP.java | 9 +- .../internal/referencing/provider/Mercator2SP.java | 9 +- .../provider/MercatorAuxiliarySphere.java | 80 +++++++++++++ .../referencing/provider/MillerCylindrical.java | 2 +- .../referencing/provider/PseudoMercator.java | 2 +- .../operation/projection/AlbersEqualArea.java | 2 +- .../operation/projection/AzimuthalEquidistant.java | 2 +- .../operation/projection/CassiniSoldner.java | 79 ++++++++----- .../operation/projection/CylindricalEqualArea.java | 55 ++++++--- .../operation/projection/Initializer.java | 42 ++++--- .../projection/LambertConicConformal.java | 91 +++++++++------ .../referencing/operation/projection/Mercator.java | 124 +++++++++++++++------ .../projection/ModifiedAzimuthalEquidistant.java | 2 +- .../operation/projection/Mollweide.java | 29 ++++- .../operation/projection/NormalizedProjection.java | 50 ++++----- .../operation/projection/ObliqueMercator.java | 80 ++++++++----- .../operation/projection/ObliqueStereographic.java | 2 +- .../operation/projection/Orthographic.java | 2 +- .../operation/projection/PolarStereographic.java | 79 ++++++++----- .../operation/projection/Polyconic.java | 2 +- .../operation/projection/ProjectionVariant.java | 58 ++++++++++ .../operation/projection/SatelliteTracking.java | 2 +- .../operation/projection/Sinusoidal.java | 2 +- .../operation/projection/TransverseMercator.java | 36 ++++-- .../operation/projection/ZonedGridSystem.java | 2 +- ...g.opengis.referencing.operation.OperationMethod | 1 + .../referencing/provider/ProvidersTest.java | 1 + .../operation/projection/InitializerTest.java | 2 +- .../sis/referencing/operation/projection/NoOp.java | 4 +- 30 files changed, 614 insertions(+), 249 deletions(-) diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AbstractMercator.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AbstractMercator.java index c792f94e74..417dfb6b39 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AbstractMercator.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AbstractMercator.java @@ -31,7 +31,7 @@ import org.apache.sis.referencing.operation.projection.NormalizedProjection; * Base class of providers for all Mercator projections, and for Mercator-like projections. * * @author Martin Desruisseaux (IRD, Geomatys) - * @version 0.6 + * @version 1.2 * @since 0.6 * @module */ @@ -78,15 +78,19 @@ class AbstractMercator extends MapProjection { /** * Returns the given descriptor as an array, excluding the two first elements which are assumed - * to be the axis lengths.This method assumes that all elements in the given list are instances + * to be the axis lengths. This method assumes that all elements in the given list are instances * of {@link ParameterDescriptor}. * + * @param descriptors the descriptors to return as an array. + * @param expansion number of additional elements in the returned array. + * @return the given descriptors without the two first elements. * @throws ArrayStoreException if a {@code descriptors} element is not an instance of {@link ParameterDescriptor}. */ @SuppressWarnings("SuspiciousToArrayCall") - static ParameterDescriptor<?>[] toArray(List<GeneralParameterDescriptor> descriptors) { + static ParameterDescriptor<?>[] toArray(List<GeneralParameterDescriptor> descriptors, int expansion) { descriptors = descriptors.subList(2, descriptors.size()); - return descriptors.toArray(new ParameterDescriptor<?>[descriptors.size()]); // Intentional array subtype. + // Intentionallly use an array subtype in call to `toArray(…)`. + return descriptors.toArray(new ParameterDescriptor<?>[descriptors.size() + expansion]); } /** diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator1SP.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator1SP.java index d84fd0e9c4..7d165e8394 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator1SP.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator1SP.java @@ -30,7 +30,7 @@ import org.apache.sis.metadata.iso.citation.Citations; * * @author Martin Desruisseaux (IRD, Geomatys) * @author Rueben Schulz (UBC) - * @version 0.6 + * @version 1.2 * * @see <a href="http://geotiff.maptools.org/proj_list/mercator_1sp.html">GeoTIFF parameters for Mercator 1SP</a> * @@ -44,6 +44,11 @@ public final class Mercator1SP extends AbstractMercator { */ private static final long serialVersionUID = -5886510621481710072L; + /** + * The EPSG identifier, to be preferred to the name when available. + */ + public static final String IDENTIFIER = "9804"; + /** * The operation parameter descriptor for the <cite>Latitude of natural origin</cite> (φ₀) parameter value. * In theory, this parameter should not be used and its value should be 0 in all cases. @@ -126,7 +131,7 @@ public final class Mercator1SP extends AbstractMercator { .addName(Citations.PROJ4, "k")); PARAMETERS = builder - .addIdentifier( "9804") // The ellipsoidal case + .addIdentifier( IDENTIFIER) // The ellipsoidal case .addName( "Mercator (variant A)") // Starting from EPSG version 7.6 .addName( "Mercator (1SP)") // Prior to EPSG version 7.6 .addName(Citations.OGC, "Mercator_1SP") diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator2SP.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator2SP.java index 552953843a..7b64cae079 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator2SP.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mercator2SP.java @@ -31,7 +31,7 @@ import org.apache.sis.metadata.iso.citation.Citations; * * @author Martin Desruisseaux (IRD, Geomatys) * @author Rueben Schulz (UBC) - * @version 0.6 + * @version 1.2 * * @see <a href="http://geotiff.maptools.org/proj_list/mercator_2sp.html">GeoTIFF parameters for Mercator 2SP</a> * @@ -45,6 +45,11 @@ public final class Mercator2SP extends AbstractMercator { */ private static final long serialVersionUID = 6356028352681135786L; + /** + * The EPSG identifier, to be preferred to the name when available. + */ + public static final String IDENTIFIER = "9805"; + /* * ACCESS POLICY: Only formal EPSG parameters shall be public. * Parameters that we add ourselves should be package-privated. @@ -126,7 +131,7 @@ public final class Mercator2SP extends AbstractMercator { .setRemarks(remarks).setDeprecated(true)); PARAMETERS = builder - .addIdentifier( "9805") + .addIdentifier( IDENTIFIER) .addName( "Mercator (variant B)") // Starting from EPSG version 7.6 .addName( "Mercator (2SP)") // Prior to EPSG version 7.6 .addName(Citations.OGC, "Mercator_2SP") diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MercatorAuxiliarySphere.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MercatorAuxiliarySphere.java new file mode 100644 index 0000000000..0dd127e112 --- /dev/null +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MercatorAuxiliarySphere.java @@ -0,0 +1,80 @@ +/* + * 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 javax.xml.bind.annotation.XmlTransient; +import org.opengis.parameter.ParameterDescriptor; +import org.opengis.parameter.ParameterDescriptorGroup; +import org.apache.sis.parameter.ParameterBuilder; +import org.apache.sis.metadata.iso.citation.Citations; + + +/** + * The provider for <cite>"Mercator Auxiliary Sphere"</cite> projection (defined by ESRI). + * This is often equivalent to {@link PseudoMercator}. + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.2 + * @since 1.2 + * @module + */ +@XmlTransient +public final class MercatorAuxiliarySphere extends AbstractMercator { + /** + * For cross-version compatibility. + */ + private static final long serialVersionUID = -8000127893422503988L; + + /** + * The operation parameter descriptor for the <cite>Auxiliary sphere type</cite> parameter value. + * Valid values are: + * + * <ul> + * <li>0 = use semi-major axis or radius of the geographic coordinate system.</li> + * <li>1 = use semi-minor axis or radius.</li> + * <li>2 = calculate and use authalic radius.</li> + * <li>3 = use authalic radius and convert geodetic latitudes to authalic latitudes.</li> + * </ul> + * + * <!-- Generated by ParameterNameTableGenerator --> + * <table class="sis"> + * <caption>Parameter names</caption> + * <tr><td> ESRI: </td><td> Auxiliary_Sphere_Type </td></tr> + * </table> + */ + public static final ParameterDescriptor<Integer> AUXILIARY_SPHERE_TYPE; + + /** + * The group of all parameters expected by this coordinate operation. + */ + private static final ParameterDescriptorGroup PARAMETERS; + static { + final ParameterBuilder builder = builder().setCodeSpace(Citations.ESRI, "ESRI"); + AUXILIARY_SPHERE_TYPE = builder.addName("Auxiliary_Sphere_Type").createBounded(0, 3, 0); + + final ParameterDescriptor<?>[] descriptors = toArray(MercatorSpherical.PARAMETERS.descriptors(), 1); + descriptors[descriptors.length - 1] = AUXILIARY_SPHERE_TYPE; + PARAMETERS = builder.addName("Mercator_Auxiliary_Sphere").createGroupForMapProjection(descriptors); + } + + /** + * Constructs a new provider. + */ + public MercatorAuxiliarySphere() { + super(PARAMETERS); + } +} diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MillerCylindrical.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MillerCylindrical.java index 5531117b39..3d42617b1a 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MillerCylindrical.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MillerCylindrical.java @@ -71,7 +71,7 @@ public final class MillerCylindrical extends AbstractMercator { .addIdentifier(Citations.GEOTIFF, "20") .addName (Citations.PROJ4, "mill") .addIdentifier(Citations.MAP_INFO, "11") - .createGroupForMapProjection(toArray(MercatorSpherical.PARAMETERS.descriptors())); + .createGroupForMapProjection(toArray(MercatorSpherical.PARAMETERS.descriptors(), 0)); } /** diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PseudoMercator.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PseudoMercator.java index 46fc169070..56fe1330c7 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PseudoMercator.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PseudoMercator.java @@ -50,7 +50,7 @@ public final class PseudoMercator extends AbstractMercator { PARAMETERS = builder() .addIdentifier(IDENTIFIER) .addName("Popular Visualisation Pseudo Mercator") - .createGroupForMapProjection(toArray(MercatorSpherical.PARAMETERS.descriptors())); + .createGroupForMapProjection(toArray(MercatorSpherical.PARAMETERS.descriptors(), 0)); } /** diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AlbersEqualArea.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AlbersEqualArea.java index 03e0673d9f..fcfd6f0866 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AlbersEqualArea.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AlbersEqualArea.java @@ -111,7 +111,7 @@ public class AlbersEqualArea extends EqualAreaProjection { roles.put(ParameterRole.FALSE_EASTING, EASTING_AT_FALSE_ORIGIN); roles.put(ParameterRole.FALSE_NORTHING, NORTHING_AT_FALSE_ORIGIN); roles.put(ParameterRole.CENTRAL_MERIDIAN, LONGITUDE_OF_FALSE_ORIGIN); - return new Initializer(method, parameters, roles, STANDARD_VARIANT); + return new Initializer(method, parameters, roles, null); } /** diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AzimuthalEquidistant.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AzimuthalEquidistant.java index 397621ab27..e698d8c489 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AzimuthalEquidistant.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AzimuthalEquidistant.java @@ -95,7 +95,7 @@ public class AzimuthalEquidistant extends NormalizedProjection { roles.put(ParameterRole.CENTRAL_MERIDIAN, LONGITUDE_OF_ORIGIN); roles.put(ParameterRole.FALSE_EASTING, FALSE_EASTING); roles.put(ParameterRole.FALSE_NORTHING, FALSE_NORTHING); - return new Initializer(method, parameters, roles, STANDARD_VARIANT); + return new Initializer(method, parameters, roles, null); } /** diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CassiniSoldner.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CassiniSoldner.java index 7c8b88c3a9..143fe0a8e5 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CassiniSoldner.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CassiniSoldner.java @@ -17,6 +17,7 @@ package org.apache.sis.referencing.operation.projection; import java.util.EnumMap; +import java.util.regex.Pattern; import org.opengis.util.FactoryException; import org.opengis.parameter.ParameterDescriptor; import org.opengis.referencing.operation.MathTransform; @@ -49,7 +50,7 @@ import static org.apache.sis.internal.referencing.provider.CassiniSoldner.*; * * @author Martin Desruisseaux (Geomatys) * @author Rémi Maréchal (Geomatys) - * @version 1.1 + * @version 1.2 * @since 1.1 * @module */ @@ -57,7 +58,7 @@ public class CassiniSoldner extends MeridianArcBased { /** * For cross-version compatibility. */ - private static final long serialVersionUID = 8306467537772990906L; + private static final long serialVersionUID = 616536461409425434L; /** * The hyperbolic variants of this projection. {@link #VANUA} is the special case @@ -66,7 +67,37 @@ public class CassiniSoldner extends MeridianArcBased { * * @see #variant */ - private static final byte HYPERBOLIC = 1, VANUA = 2; + private enum Variant implements ProjectionVariant { + /** + * The <cite>"Hyperbolic Cassini-Soldner"</cite> variant, which is non-invertible. + */ + HYPERBOLIC(Pattern.compile(".*\\bHyperbolic\\b.*", Pattern.CASE_INSENSITIVE), HyperbolicCassiniSoldner.IDENTIFIER), + + /** + * The special case of <cite>"Vanua Levu Grid"</cite> at φ₀=16°15′S. + * This is the only hyperbolic variant for which inverse projection is supported. + * This special case is detected by checking the value of the latitude of origin. + */ + VANUA(HYPERBOLIC.operationName, HyperbolicCassiniSoldner.IDENTIFIER); + + /** Name pattern for this variant. */ private final Pattern operationName; + /** EPSG identifier for this variant. */ private final String identifier; + /** Creates a new enumeration value. */ + private Variant(final Pattern operationName, final String identifier) { + this.operationName = operationName; + this.identifier = identifier; + } + + /** The expected name pattern of an operation method for this variant. */ + @Override public Pattern getOperationNamePattern() { + return operationName; + } + + /** EPSG identifier of an operation method for this variant. */ + @Override public String getIdentifier() { + return identifier; + } + } /** * The latitude of {@link #VANUA} variant (16°15′S) in radians. @@ -77,14 +108,14 @@ public class CassiniSoldner extends MeridianArcBased { * The type of Cassini-Soldner projection. Possible values are: * * <ul> - * <li>{@link #STANDARD_VARIANT} if this projection is the standard variant.</li> - * <li>{@link #HYPERBOLIC} if this projection is the "Hyperbolic Cassini-Soldner" case.</li> - * <li>{@link #VANUA} if this projection is the "Hyperbolic Cassini-Soldner" case at φ₀=16°15′S.</li> + * <li>{@code null} if this projection is the standard variant.</li> + * <li>{@link Variant#HYPERBOLIC} if this projection is the "Hyperbolic Cassini-Soldner" case.</li> + * <li>{@link Variant#VANUA} if this projection is the "Hyperbolic Cassini-Soldner" case at φ₀=16°15′S.</li> * </ul> * * Other cases may be added in the future. */ - private final byte variant; + private final Variant variant; /** * Meridional distance <var>M</var> from equator to latitude of origin φ₀. @@ -94,17 +125,6 @@ public class CassiniSoldner extends MeridianArcBased { */ private final double M0; - /** - * Returns the variant of the projection based on the name and identifier of the given operation method. - * See {@link #variant} for the list of possible values. - */ - private static byte getVariant(final OperationMethod method) { - if (identMatch(method, "(?i).*\\bHyperbolic\\b.*", HyperbolicCassiniSoldner.IDENTIFIER)) { - return HYPERBOLIC; - } - return STANDARD_VARIANT; - } - /** * Creates a Cassini-Soldner projection from the given parameters. * The {@code method} argument can be the description of one of the following: @@ -127,12 +147,13 @@ public class CassiniSoldner extends MeridianArcBased { */ @Workaround(library="JDK", version="1.7") private static Initializer initializer(final OperationMethod method, final Parameters parameters) { + final Variant variant = variant(method, new Variant[] {Variant.HYPERBOLIC}, null); final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new EnumMap<>(ParameterRole.class); roles.put(ParameterRole.CENTRAL_MERIDIAN, LONGITUDE_OF_ORIGIN); roles.put(ParameterRole.SCALE_FACTOR, SCALE_FACTOR); roles.put(ParameterRole.FALSE_EASTING, FALSE_EASTING); roles.put(ParameterRole.FALSE_NORTHING, FALSE_NORTHING); - return new Initializer(method, parameters, roles, getVariant(method)); + return new Initializer(method, parameters, roles, variant); } /** @@ -142,14 +163,14 @@ public class CassiniSoldner extends MeridianArcBased { super(initializer); final double φ0 = toRadians(initializer.getAndStore(LATITUDE_OF_ORIGIN)); M0 = distance(φ0, sin(φ0), cos(φ0)); - if (initializer.variant == STANDARD_VARIANT) { + if (initializer.variant == null) { final MatrixSIS denormalize = getContextualParameters().getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION); denormalize.convertBefore(1, null, -distance(φ0, sin(φ0), cos(φ0))); } else if (abs(φ0 - VANUA_LATITUDE) <= ANGULAR_TOLERANCE) { - variant = VANUA; + variant = Variant.VANUA; return; } - variant = initializer.variant; + variant = (Variant) initializer.variant; } /** @@ -167,7 +188,7 @@ public class CassiniSoldner extends MeridianArcBased { */ @Override final String[] getInternalParameterNames() { - if (variant != STANDARD_VARIANT) { + if (variant != null) { return new String[] {"M₀"}; } else { return super.getInternalParameterNames(); @@ -180,7 +201,7 @@ public class CassiniSoldner extends MeridianArcBased { */ @Override final double[] getInternalParameterValues() { - if (variant != STANDARD_VARIANT) { + if (variant != null) { return new double[] {M0}; } else { return super.getInternalParameterValues(); @@ -201,7 +222,7 @@ public class CassiniSoldner extends MeridianArcBased { @Override public MathTransform createMapProjection(final MathTransformFactory factory) throws FactoryException { CassiniSoldner kernel = this; - if (eccentricity == 0 && variant == STANDARD_VARIANT) { + if (eccentricity == 0 && variant == null) { kernel = new Spherical(this); } return context.completeTransform(factory, kernel); @@ -256,7 +277,7 @@ public class CassiniSoldner extends MeridianArcBased { if (dstPts != null) { dstPts[dstOff ] = (A - T*A3/6 + Q*T*(A3*A2) / 120) / rν; dstPts[dstOff+1] = distance(φ, sinφ, cosφ) + tanφ*A2*(0.5 + S/4) / rν; - if (variant != STANDARD_VARIANT) { + if (variant != null) { /* * Offset: X³ / (6ρν) where ρ = (1 – ℯ²)⋅ν³ * = X³ / (6⋅(1 – ℯ²)⋅ν⁴) @@ -268,7 +289,7 @@ public class CassiniSoldner extends MeridianArcBased { if (!derivate) { return null; } - if (variant != STANDARD_VARIANT) { + if (variant != null) { throw new ProjectionException(Resources.format(Resources.Keys.CanNotComputeDerivative)); } /* @@ -306,8 +327,8 @@ public class CassiniSoldner extends MeridianArcBased { { double x = srcPts[srcOff ]; double y = srcPts[srcOff+1]; - if (variant != STANDARD_VARIANT) { - if (variant != VANUA) { + if (variant != null) { + if (variant != Variant.VANUA) { throw new ProjectionException(Errors.format(Errors.Keys.UnsupportedArgumentValue_1, "φ₀≠16°15′S")); } /* diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CylindricalEqualArea.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CylindricalEqualArea.java index 81bcd0bb2c..5d5c9ef337 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CylindricalEqualArea.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/CylindricalEqualArea.java @@ -17,6 +17,7 @@ package org.apache.sis.referencing.operation.projection; import java.util.EnumMap; +import java.util.regex.Pattern; import org.opengis.parameter.ParameterDescriptor; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.MathTransformFactory; @@ -58,7 +59,7 @@ import static org.apache.sis.internal.referencing.provider.LambertCylindricalEqu * However this projection may be useful for computing areas. * * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.2 * @since 0.8 * @module */ @@ -66,34 +67,55 @@ public class CylindricalEqualArea extends EqualAreaProjection { /** * For cross-version compatibility. */ - private static final long serialVersionUID = 8840395516658904421L; + private static final long serialVersionUID = 5659955047326708663L; /** - * Returns the variant of the projection based on the name and identifier of the given operation method. + * The variants of the projection based on the name and identifier of the given operation method. * See {@link #variant} for the list of possible values. */ - private static byte getVariant(final OperationMethod method) { - if (identMatch(method, "(?i).*\\bSpherical\\b.*", LambertCylindricalEqualAreaSpherical.IDENTIFIER)) { - return Initializer.AUTHALIC_RADIUS; + private enum Variant implements ProjectionVariant { + /** + * The "Lambert Cylindrical Equal Area (Spherical)" case. + */ + SPHERICAL(".*\\bSpherical\\b.*", LambertCylindricalEqualAreaSpherical.IDENTIFIER); + + /** Name pattern for this variant. */ private final Pattern operationName; + /** EPSG identifier for this variant. */ private final String identifier; + /** Creates a new enumeration value. */ + private Variant(final String operationName, final String identifier) { + this.operationName = Pattern.compile(operationName, Pattern.CASE_INSENSITIVE); + this.identifier = identifier; + } + + /** The expected name pattern of an operation method for this variant. */ + @Override public Pattern getOperationNamePattern() { + return operationName; + } + + /** EPSG identifier of an operation method for this variant. */ + @Override public String getIdentifier() { + return identifier; + } + + /** Requests the use of authalic radius. */ + @Override public boolean useAuthalicRadius() { + return true; } - return STANDARD_VARIANT; } /** * The type of Cylindrical Equal Area projection. Possible values are: * * <ul> - * <li>{@link #STANDARD_VARIANT} if this projection is a default variant.</li> - * <li>{@link Initializer#AUTHALIC_RADIUS} if this projection is the "Lambert Cylindrical Equal Area (Spherical)" - * case, in which case the semi-major and semi-minor axis lengths should be replaced by the authalic radius + * <li>{@code null} if this projection is a default variant.</li> + * <li>{@link Variant#SPHERICAL} if this projection is the "Lambert Cylindrical Equal Area (Spherical)" case, + * in which case the semi-major and semi-minor axis lengths should be replaced by the authalic radius * (this replacement is performed by the {@link Initializer} constructor).</li> * </ul> * * Other cases may be added in the future. - * - * @see #getVariant(OperationMethod) */ - private final byte variant; + private final Variant variant; /** * Creates a Cylindrical Equal Area projection from the given parameters. @@ -112,6 +134,7 @@ public class CylindricalEqualArea extends EqualAreaProjection { @SuppressWarnings("fallthrough") @Workaround(library="JDK", version="1.7") private static Initializer initializer(final OperationMethod method, final Parameters parameters) { + final Variant variant = variant(method, Variant.values(), null); final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new EnumMap<>(ParameterRole.class); /* * "Longitude of origin" and "scale factor" are intentionally omitted from this map because they will @@ -120,7 +143,7 @@ public class CylindricalEqualArea extends EqualAreaProjection { roles.put(ParameterRole.SCALE_FACTOR, SCALE_FACTOR); roles.put(ParameterRole.FALSE_EASTING, FALSE_EASTING); roles.put(ParameterRole.FALSE_NORTHING, FALSE_NORTHING); - return new Initializer(method, parameters, roles, getVariant(method)); + return new Initializer(method, parameters, roles, variant); } /** @@ -130,7 +153,7 @@ public class CylindricalEqualArea extends EqualAreaProjection { @Workaround(library="JDK", version="1.7") private CylindricalEqualArea(final Initializer initializer) { super(initializer); - variant = initializer.variant; + variant = (Variant) initializer.variant; final MatrixSIS denormalize = context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION); /* * The longitude of origin is normally subtracted in the 'normalize' matrix. But in the particular of case @@ -194,7 +217,7 @@ public class CylindricalEqualArea extends EqualAreaProjection { @Override public MathTransform createMapProjection(final MathTransformFactory factory) throws FactoryException { CylindricalEqualArea kernel = this; - if (variant == Initializer.AUTHALIC_RADIUS || eccentricity == 0) { + if (variant == Variant.SPHERICAL || eccentricity == 0) { kernel = new Spherical(this); } return context.completeTransform(factory, kernel); diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java index 9f23271199..63699a5546 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Initializer.java @@ -56,7 +56,7 @@ import static org.apache.sis.util.ArgumentChecks.ensureNonNull; * * @author Martin Desruisseaux (Geomatys) * @author Rémi Maréchal (Geomatys) - * @version 1.1 + * @version 1.2 * @since 0.6 * @module */ @@ -93,21 +93,9 @@ final class Initializer { private final byte signum_λ0; /** - * Map projection variant. - * Values from 0 to 127 inclusive are convenience values at the discretion of {@link NormalizedProjection} subclasses. - * Values from 128 to 255 inclusive are values handled in a special way by {@link Initializer} constructor. + * Map projection variant, or {@code null} if none. */ - final byte variant; - - /** - * A {@link #variant} value telling the constructor to computing the authalic radius instead of using - * the semi-major and semi-minor axis lengths directly. - * - * <p>Note that this value is not necessarily equivalent to the {@code SPHERICAL} value defined in some - * map projection, because EPSG guidance notes recommend different approaches for spherical implementations. - * For example the Mercator projection will use the radius of conformal sphere instead of the authalic radius.</p> - */ - static final byte AUTHALIC_RADIUS = (byte) 128; + final ProjectionVariant variant; /** * Creates a new initializer. The parameters are described in @@ -117,13 +105,11 @@ final class Initializer { * @param parameters the parameters of the projection to be created. * @param roles parameters to look for <cite>central meridian</cite>, <cite>scale factor</cite>, * <cite>false easting</cite>, <cite>false northing</cite> and other values. - * @param variant convenience field left at the discretion of {@link NormalizedProjection} subclasses. - * Values equal or greater than 128 are special values recognized by this constructor - * (see {@link #AUTHALIC_RADIUS}). + * @param variant the map projection variant, or {@code null} if none. */ Initializer(final OperationMethod method, final Parameters parameters, - final Map<ParameterRole, ? extends ParameterDescriptor<? extends Number>> roles, - final byte variant) + final Map<ParameterRole, ? extends ParameterDescriptor<? extends Number>> roles, + final ProjectionVariant variant) { ensureNonNull("method", method); ensureNonNull("parameters", parameters); @@ -153,7 +139,7 @@ final class Initializer { eccentricitySquared = new DoubleDouble(); DoubleDouble k = DoubleDouble.createAndGuessError(a); // The value by which to multiply all results of normalized projection. if (a != b) { - if (variant == AUTHALIC_RADIUS) { + if (variant != null && variant.useAuthalicRadius()) { k.value = Formulas.getAuthalicRadius(a, b); k.error = 0; } else { @@ -271,7 +257,7 @@ final class Initializer { /** * Same as {@link #getAndStore(ParameterDescriptor)}, but returns the given default value if the parameter - * is not specified. This method shall be used only for parameters having a default value more complex than + * is not specified. This method shall be used only for parameters having a default value more complex than * what we can represent in {@link ParameterDescriptor#getDefaultValue()}. */ final double getAndStore(final ParameterDescriptor<Double> descriptor, final double defaultValue) { @@ -284,6 +270,18 @@ final class Initializer { return value; } + /** + * Same as {@link #getAndStore(ParameterDescriptor, double)} but working on integer values. + */ + final int getAndStore(final ParameterDescriptor<Integer> descriptor, final int defaultValue) { + final Integer value = parameters.getValue(descriptor); + if (value == null) { + return defaultValue; + } + context.getOrCreate(descriptor).setValue(value); + return value; + } + /** * Returns {@code b/a} where {@code a} is the semi-major axis length and {@code b} the semi-minor axis length. * We retrieve this value from the eccentricity with {@code b/a = sqrt(1-ℯ²)}. diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java index 269ceeae53..d5d311c3e7 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConicConformal.java @@ -17,6 +17,7 @@ package org.apache.sis.referencing.operation.projection; import java.util.EnumMap; +import java.util.regex.Pattern; import org.opengis.util.FactoryException; import org.opengis.parameter.ParameterDescriptor; import org.opengis.referencing.operation.MathTransform; @@ -61,7 +62,7 @@ import static org.apache.sis.internal.referencing.Formulas.fastHypot; * @author André Gosselin (MPO) * @author Rueben Schulz (UBC) * @author Rémi Maréchal (Geomatys) - * @version 1.1 + * @version 1.2 * @since 0.6 * @module */ @@ -69,34 +70,53 @@ public class LambertConicConformal extends ConformalProjection { /** * For cross-version compatibility. */ - private static final long serialVersionUID = -8971522854919443706L; + private static final long serialVersionUID = -8786460743797422415L; /** - * Codes for variants of Lambert Conical Conformal projection. Those variants modify the way the projections are - * constructed (e.g. in the way parameters are interpreted), but formulas are basically the same after construction. + * Variants of Lambert Conical Conformal projection. Those variants modify the way the projections are constructed + * (e.g. in the way parameters are interpreted), but formulas are basically the same after construction. * Those variants are not exactly the same than variants 1SP and 2SP used by EPSG, but they are closely related. * * <p>We do not provide such codes in public API because they duplicate the functionality of * {@link OperationMethod} instances. We use them only for constructors convenience.</p> - * - * <p><b>CONVENTION:</b> Codes for SP1 case must be odd, and codes for SP2 case must be even. - * - * @see #getVariant(OperationMethod) */ - private static final byte SP1 = 1, WEST = 3, // Must be odd - SP2 = 2, BELGIUM = 4, MICHIGAN = 6; // Must be even + private enum Variant implements ProjectionVariant { + // Declaration order matter. Patterns are matched in that order. - /** - * Returns the type of the projection based on the name and identifier of the given operation method. - * If this method can not identify the type, then the parameters should be considered as a 2SP case. - */ - private static byte getVariant(final OperationMethod method) { - if (identMatch(method, "(?i).*\\bBelgium\\b.*", LambertConformalBelgium .IDENTIFIER)) return BELGIUM; - if (identMatch(method, "(?i).*\\bMichigan\\b.*", LambertConformalMichigan.IDENTIFIER)) return MICHIGAN; - if (identMatch(method, "(?i).*\\bWest\\b.*", LambertConformalWest .IDENTIFIER)) return WEST; - if (identMatch(method, "(?i).*\\b2SP\\b.*", LambertConformal2SP .IDENTIFIER)) return SP2; - if (identMatch(method, "(?i).*\\b1SP\\b.*", LambertConformal1SP .IDENTIFIER)) return SP1; - return STANDARD_VARIANT; // Unidentified case, to be considered as 2SP. + /** The <cite>"Lambert Conic Conformal (2SP Belgium)"</cite> projection. */ + BELGIUM(".*\\bBelgium\\b.*", LambertConformalBelgium.IDENTIFIER, false), + + /** The <cite>"Lambert Conic Conformal (2SP Michigan)"</cite> projection. */ + MICHIGAN(".*\\bMichigan\\b.*", LambertConformalMichigan.IDENTIFIER, false), + + /** The <cite>"Lambert Conic Conformal (West Orientated)"</cite> projection. */ + WEST(".*\\bWest\\b.*", LambertConformalWest.IDENTIFIER, true), + + /** The <cite>"Lambert Conic Conformal (1SP)"</cite> projection. */ + ONE_PARALLEL(".*\\b1SP\\b.*", LambertConformal1SP.IDENTIFIER, true), + + /** The <cite>"Lambert Conic Conformal (2SP)"</cite> projection. */ + TWO_PARALLELS(".*\\b2SP\\b.*", LambertConformal2SP.IDENTIFIER, false); + + /** Name pattern for this variant. */ private final Pattern operationName; + /** EPSG identifier for this variant. */ private final String identifier; + /** Number of standard parallels. */ final boolean is1SP; + /** Creates a new enumeration value. */ + private Variant(final String operationName, final String identifier, final boolean is1SP) { + this.operationName = Pattern.compile(operationName, Pattern.CASE_INSENSITIVE); + this.identifier = identifier; + this.is1SP = is1SP; + } + + /** The expected name pattern of an operation method for this variant. */ + @Override public Pattern getOperationNamePattern() { + return operationName; + } + + /** EPSG identifier of an operation method for this variant. */ + @Override public String getIdentifier() { + return identifier; + } } /** @@ -176,7 +196,7 @@ public class LambertConicConformal extends ConformalProjection { @SuppressWarnings("fallthrough") @Workaround(library="JDK", version="1.7") private static Initializer initializer(final OperationMethod method, final Parameters parameters) { - final byte variant = getVariant(method); + final Variant variant = variant(method, Variant.values(), Variant.TWO_PARALLELS); final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new EnumMap<>(ParameterRole.class); /* * "Scale factor" is not formally a "Lambert Conformal (2SP)" argument, but we accept it @@ -193,7 +213,7 @@ public class LambertConicConformal extends ConformalProjection { eastingDirection = ParameterRole.FALSE_WESTING; // Fallthrough } - case SP1: { + case ONE_PARALLEL: { roles.put(eastingDirection, LambertConformal1SP.FALSE_EASTING); roles.put(ParameterRole.FALSE_NORTHING, LambertConformal1SP.FALSE_NORTHING); roles.put(ParameterRole.CENTRAL_MERIDIAN, LambertConformal1SP.LONGITUDE_OF_ORIGIN); @@ -203,9 +223,8 @@ public class LambertConicConformal extends ConformalProjection { scaleFactor = LambertConformalMichigan.SCALE_FACTOR; // Ellipsoid scaling factor (EPSG:1038) // Fallthrough } - case STANDARD_VARIANT: case BELGIUM: - case SP2: { + case TWO_PARALLELS: { roles.put(eastingDirection, LambertConformal2SP.EASTING_AT_FALSE_ORIGIN); roles.put(ParameterRole.FALSE_NORTHING, LambertConformal2SP.NORTHING_AT_FALSE_ORIGIN); roles.put(ParameterRole.CENTRAL_MERIDIAN, LambertConformal2SP.LONGITUDE_OF_FALSE_ORIGIN); @@ -224,8 +243,10 @@ public class LambertConicConformal extends ConformalProjection { @Workaround(library="JDK", version="1.7") private LambertConicConformal(final Initializer initializer) { super(initializer); - double φ0 = initializer.getAndStore(((initializer.variant & 1) != 0) ? // Odd 'type' are SP1, even 'type' are SP2. - LambertConformal1SP.LATITUDE_OF_ORIGIN : LambertConformal2SP.LATITUDE_OF_FALSE_ORIGIN); + final Variant variant = (Variant) initializer.variant; + double φ0 = initializer.getAndStore(variant.is1SP + ? LambertConformal1SP.LATITUDE_OF_ORIGIN + : LambertConformal2SP.LATITUDE_OF_FALSE_ORIGIN); /* * Standard parallels (SP) are defined only for the 2SP case, but we look for them unconditionally * in case the user gave us non-standard parameters. For the 1SP case, or for the 2SP case left to @@ -249,8 +270,8 @@ public class LambertConicConformal extends ConformalProjection { * * t = tan(π/4 – φ/2) / [(1 – ℯ⋅sinφ)/(1 + ℯ⋅sinφ)] ^ (ℯ/2) * - * while our 'expΨ' function is defined like above, but with tan(π/4 + φ/2) instead of tan(π/4 - φ/2). - * Those two expressions are the reciprocal of each other if we reverse the sign of φ (see 'expΨ' for + * while our `expΨ` function is defined like above, but with tan(π/4 + φ/2) instead of tan(π/4 - φ/2). + * Those two expressions are the reciprocal of each other if we reverse the sign of φ (see `expΨ` for * trigonometric identities), but their accuracies are not equivalent: the hemisphere having values * closer to zero is favorized. The EPSG formulas favorize the North hemisphere. * @@ -258,8 +279,8 @@ public class LambertConicConformal extends ConformalProjection { * values in order to match the EPSG formulas, but we will do that only if the map projection is * for the North hemisphere. * - * TEST: whether 'isNorth' is true of false does not change the formulas "correctness": it is only - * a small accuracy improvement. One can safely force this boolean value to 'true' or 'false' for + * TEST: whether `isNorth` is true of false does not change the formulas "correctness": it is only + * a small accuracy improvement. One can safely force this boolean value to `true` or `false` for * testing purpose. */ final boolean isNorth = isPositive(φ0); @@ -285,7 +306,7 @@ public class LambertConicConformal extends ConformalProjection { * for reducing the amount of calls to the logarithmic function. Note that this equation * tends toward 0/0 if φ₁ ≈ φ₂, which force us to do a special check for the SP1 case. */ - if (abs(φ1 - φ2) >= ANGULAR_TOLERANCE) { // Should be 'true' for 2SP case. + if (abs(φ1 - φ2) >= ANGULAR_TOLERANCE) { // Should be `true` for 2SP case. final double sinφ2 = sin(φ2); final double m2 = initializer.scaleAtφ(sinφ2, cos(φ2)); final double t2 = expΨ(φ2, eccentricity*sinφ2); @@ -310,7 +331,7 @@ public class LambertConicConformal extends ConformalProjection { * Compute the radius of the parallel of latitude of the false origin. * This is related to the "ρ₀" term in Snyder. From EPG guide: * - * r = a⋅F⋅tⁿ where (in our case) a=1 and t is our 'expΨ' function. + * r = a⋅F⋅tⁿ where (in our case) a=1 and t is our `expΨ` function. * * EPSG uses this term in the computation of y = FN + rF – r⋅cos(θ). */ @@ -326,7 +347,7 @@ public class LambertConicConformal extends ConformalProjection { * Normalization: * - Subtract central meridian to longitudes (done by the super-class constructor). * - Convert longitudes and latitudes from degrees to radians (done by the super-class constructor) - * - Multiply longitude by 'n'. + * - Multiply longitude by `n`. * - In the Belgium case only, subtract BELGE_A to the scaled longitude. * * Denormalization @@ -346,7 +367,7 @@ public class LambertConicConformal extends ConformalProjection { } final MatrixSIS normalize = context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION); final MatrixSIS denormalize = context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION); - normalize .convertAfter(0, sλ, (initializer.variant == BELGIUM) ? belgeA() : null); + normalize .convertAfter(0, sλ, (variant == Variant.BELGIUM) ? belgeA() : null); normalize .convertAfter(1, sφ, null); denormalize.convertBefore(0, F, null); F.negate(); denormalize.convertBefore(1, F, rF); diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java index a0e3f9dfb0..88627c0719 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java @@ -17,6 +17,7 @@ package org.apache.sis.referencing.operation.projection; import java.util.EnumMap; +import java.util.regex.Pattern; import org.opengis.util.FactoryException; import org.opengis.parameter.ParameterDescriptor; import org.opengis.referencing.operation.Matrix; @@ -27,14 +28,17 @@ import org.opengis.referencing.operation.TransformException; import org.apache.sis.internal.referencing.provider.Mercator1SP; import org.apache.sis.internal.referencing.provider.Mercator2SP; import org.apache.sis.internal.referencing.provider.MercatorSpherical; +import org.apache.sis.internal.referencing.provider.MercatorAuxiliarySphere; import org.apache.sis.internal.referencing.provider.RegionalMercator; import org.apache.sis.internal.referencing.provider.PseudoMercator; +import org.apache.sis.internal.referencing.Formulas; import org.apache.sis.internal.util.DoubleDouble; import org.apache.sis.referencing.operation.matrix.Matrix2; import org.apache.sis.referencing.operation.matrix.MatrixSIS; import org.apache.sis.referencing.operation.transform.MathTransforms; import org.apache.sis.referencing.operation.transform.ContextualParameters; import org.apache.sis.parameter.Parameters; +import org.apache.sis.util.resources.Errors; import org.apache.sis.util.Workaround; import static java.lang.Math.*; @@ -73,7 +77,7 @@ import static org.apache.sis.math.MathFunctions.isPositive; * @author Rueben Schulz (UBC) * @author Simon Reynard (Geomatys) * @author Rémi Maréchal (Geomatys) - * @version 0.8 + * @version 1.2 * * @see TransverseMercator * @see ObliqueMercator @@ -85,51 +89,75 @@ public class Mercator extends ConformalProjection { /** * For cross-version compatibility. */ - private static final long serialVersionUID = 2564172914329253286L; + private static final long serialVersionUID = 8732555724521630563L; /** - * Codes for variants of Mercator projection. Those variants modify the way the projections are constructed + * Variants of Mercator projection. Those variants modify the way the projections are constructed * (e.g. in the way parameters are interpreted), but formulas are basically the same after construction. * Those variants are not exactly the same than variants A, B and C used by EPSG, but they are related. * * <p>We do not provide such codes in public API because they duplicate the functionality of * {@link OperationMethod} instances. We use them only for constructors convenience.</p> - * - * <p><b>CONVENTION:</b> <strong>Spherical cases must be odd, all other cases must be even.</strong> - * This allow us to perform quick checks for all spherical cases using {@code if ((type & SPHERICAL) != 0)}.</p> - * - * @see #variant - * @see #getVariant(OperationMethod) */ - private static final byte SPHERICAL = 1, PSEUDO = 3, // Must be odd and SPHERICAL must be 1. - REGIONAL = 2, MILLER = 4; // Must be even. + private enum Variant implements ProjectionVariant { + // Declaration order matter. Patterns are matched in that order. - /** - * Returns the variant of the projection based on the name and identifier of the given operation method. - */ - private static byte getVariant(final OperationMethod method) { - if (identMatch(method, "(?i).*\\bvariant\\s*C\\b.*", RegionalMercator .IDENTIFIER)) return REGIONAL; - if (identMatch(method, "(?i).*\\bSpherical\\b.*", MercatorSpherical.IDENTIFIER)) return SPHERICAL; - if (identMatch(method, "(?i).*\\bPseudo.*", PseudoMercator .IDENTIFIER)) return PSEUDO; - if (identMatch(method, "(?i).*\\bMiller.*", null)) return MILLER; - return STANDARD_VARIANT; + /** The <cite>"Mercator (variant A)"</cite> projection (one standard parallel). */ + ONE_PARALLEL(".*\\bvariant\\s*A\\b.*", Mercator1SP.IDENTIFIER, false), + + /** The <cite>"Mercator (variant B)"</cite> projection (two standard parallels). */ + TWO_PARALLELS(".*\\bvariant\\s*B\\b.*", Mercator2SP.IDENTIFIER, false), + + /** The <cite>"Mercator (variant C)"</cite> projection. */ + REGIONAL(".*\\bvariant\\s*C\\b.*", RegionalMercator.IDENTIFIER, false), + + /** The <cite>"Mercator (Spherical)"</cite> projection. */ + SPHERICAL(".*\\bSpherical\\b.*", MercatorSpherical.IDENTIFIER, true), + + /** The <cite>"Popular Visualisation Pseudo Mercator"</cite> projection. */ + PSEUDO(".*\\bPseudo.*", PseudoMercator.IDENTIFIER, true), + + /** The <cite>"Mercator Auxiliary Sphere"</cite> projection. */ + AUXILIARY(".*\\bAuxiliary\\s*Sphere\\b.*", null, true), + + /** Miller projection. */ + MILLER(".*\\bMiller.*", null, false); + + /** Name pattern for this variant. */ private final Pattern operationName; + /** EPSG identifier for this variant. */ private final String identifier; + /** Whether spherical case is used. */ final boolean spherical; + /** Creates a new enumeration value. */ + private Variant(final String operationName, final String identifier, final boolean spherical) { + this.operationName = Pattern.compile(operationName, Pattern.CASE_INSENSITIVE); + this.identifier = identifier; + this.spherical = spherical; + } + + /** The expected name pattern of an operation method for this variant. */ + @Override public Pattern getOperationNamePattern() { + return operationName; + } + + /** EPSG identifier of an operation method for this variant. */ + @Override public String getIdentifier() { + return identifier; + } } /** * The type of Mercator projection. Possible values are: * <ul> - * <li>{@link #STANDARD_VARIANT} if this projection is a Mercator variant A or B.</li> - * <li>{@link #REGIONAL} if this projection is the "Mercator (variant C)" case.</li> - * <li>{@link #SPHERICAL} if this projection is the "Mercator (Spherical)" case.</li> - * <li>{@link #PSEUDO} if this projection is the "Pseudo Mercator" case.</li> - * <li>{@link #MILLER} if this projection is the "Miller Cylindrical" case.</li> + * <li>{@link Variant#DEFAULT} if this projection is a Mercator variant A or B.</li> + * <li>{@link Variant#REGIONAL} if this projection is the "Mercator (variant C)" case.</li> + * <li>{@link Variant#SPHERICAL} if this projection is the "Mercator (Spherical)" case.</li> + * <li>{@link Variant#PSEUDO} if this projection is the "Pseudo Mercator" case.</li> + * <li>{@link Variant#MILLER} if this projection is the "Miller Cylindrical" case.</li> + * <li>{@link Variant#AUXILIARY} if this projection is the "Mercator Auxiliary Sphere" case.</li> * </ul> * * Other cases may be added in the future. - * - * @see #getVariant(OperationMethod) */ - private final byte variant; + private final Variant variant; /** * Creates a Mercator projection from the given parameters. @@ -140,6 +168,7 @@ public class Mercator extends ConformalProjection { * <li><cite>"Mercator (variant B)"</cite>, also known as <cite>"Mercator (2SP)"</cite>.</li> * <li><cite>"Mercator (variant C)"</cite>.</li> * <li><cite>"Mercator (Spherical)"</cite>.</li> + * <li><cite>"Mercator Auxiliary Sphere"</cite>.</li> * <li><cite>"Popular Visualisation Pseudo Mercator"</cite>.</li> * <li><cite>"Miller Cylindrical"</cite>.</li> * </ul> @@ -158,7 +187,7 @@ public class Mercator extends ConformalProjection { @SuppressWarnings("fallthrough") @Workaround(library="JDK", version="1.7") private static Initializer initializer(final OperationMethod method, final Parameters parameters) { - final byte variant = getVariant(method); + final Variant variant = variant(method, Variant.values(), Variant.TWO_PARALLELS); final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new EnumMap<>(ParameterRole.class); /* * "Longitude of origin" is a parameter of all Mercator projections, but is intentionally omitted from @@ -208,7 +237,7 @@ public class Mercator extends ConformalProjection { @Workaround(library="JDK", version="1.7") private Mercator(final Initializer initializer) { super(initializer); - this.variant = initializer.variant; + variant = (Variant) initializer.variant; /* * The "Longitude of natural origin" parameter is found in all Mercator projections and is mandatory. * Since this is usually the Greenwich meridian, the default value is 0°. We keep the value in degrees @@ -227,7 +256,7 @@ public class Mercator extends ConformalProjection { * "Latitude of origin" can not have a non-zero value, if it still have non-zero value we will process as * for "Latitude of false origin". */ - final double φ0 = toRadians(initializer.getAndStore((variant == REGIONAL) + final double φ0 = toRadians(initializer.getAndStore((variant == Variant.REGIONAL) ? RegionalMercator.LATITUDE_OF_FALSE_ORIGIN : Mercator1SP.LATITUDE_OF_ORIGIN)); /* * In theory, the "Latitude of 1st standard parallel" and the "Scale factor at natural origin" parameters @@ -244,7 +273,7 @@ public class Mercator extends ConformalProjection { * * However in the particular case of Mercator projection, we will apply the longitude rotation in the * denormalization matrix instead. This is possible only for this particular projection because the - * 'transform(…)' methods pass the longitude values unchanged. By keeping the normalization affine as + * `transform(…)` methods pass the longitude values unchanged. By keeping the normalization affine as * simple as possible, we increase the chances of efficient concatenation of an inverse with a forward * projection. */ @@ -255,7 +284,7 @@ public class Mercator extends ConformalProjection { if (λ0 != 0) { /* * Use double-double arithmetic here for consistency with the work done in the normalization matrix. - * The intent is to have exact value at 'double' precision when computing Matrix.invert(). Note that + * The intent is to have exact value at `double` precision when computing Matrix.invert(). Note that * there is no such goal for other parameters computed from sine or consine functions. */ final DoubleDouble offset = DoubleDouble.createDegreesToRadians(); @@ -265,9 +294,34 @@ public class Mercator extends ConformalProjection { if (φ0 != 0) { denormalize.convertBefore(1, null, new DoubleDouble(-log(expΨ(φ0, eccentricity * sin(φ0))))); } - if (variant == MILLER) { + /* + * Variants of the Mercator projection which can be handled by scale factors. + * In the "Mercator Auxiliary Sphere" case, sphere types are: + * + * 0 = use semimajor axis or radius of the geographic coordinate system. + * 1 = use semiminor axis or radius. + * 2 = calculate and use authalic radius. + * 3 = use authalic radius and convert geodetic latitudes to authalic latitudes. + * The conversion is not handled by this class and must be done by the caller. + */ + if (variant == Variant.MILLER) { normalize .convertBefore(1, 0.80, null); denormalize.convertBefore(1, 1.25, null); + } else if (variant == Variant.AUXILIARY) { + DoubleDouble ratio = null; + final int type = initializer.getAndStore(MercatorAuxiliarySphere.AUXILIARY_SPHERE_TYPE, 0); + switch (type) { + case 0: break; // Same as "Popular Visualisation Pseudo Mercator". + case 1: ratio = initializer.axisLengthRatio(); break; + case 2: + case 3: ratio = new DoubleDouble(Formulas.getAuthalicRadius(1, initializer.axisLengthRatio().value)); break; + default: { + throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, + MercatorAuxiliarySphere.AUXILIARY_SPHERE_TYPE.getName().getCode(), type)); + } + } + denormalize.convertAfter(0, ratio, null); + denormalize.convertAfter(1, ratio, null); } /* * At this point we are done, but we add here a little bit a maniac precision hunting. @@ -322,7 +376,7 @@ public class Mercator extends ConformalProjection { @Override public MathTransform createMapProjection(final MathTransformFactory factory) throws FactoryException { Mercator kernel = this; - if ((variant & SPHERICAL) != 0 || eccentricity == 0) { + if (variant.spherical || eccentricity == 0) { kernel = new Spherical(this); } return context.completeTransform(factory, kernel); diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistant.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistant.java index 4bcb6ab8b4..1d7d59499d 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistant.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistant.java @@ -94,7 +94,7 @@ public class ModifiedAzimuthalEquidistant extends AzimuthalEquidistant { roles.put(ParameterRole.CENTRAL_MERIDIAN, LONGITUDE_OF_ORIGIN); roles.put(ParameterRole.FALSE_EASTING, FALSE_EASTING); roles.put(ParameterRole.FALSE_NORTHING, FALSE_NORTHING); - return new Initializer(method, parameters, roles, STANDARD_VARIANT); + return new Initializer(method, parameters, roles, null); } /** diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mollweide.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mollweide.java index 872a4b6b27..9e8d7b2108 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mollweide.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mollweide.java @@ -17,6 +17,7 @@ package org.apache.sis.referencing.operation.projection; import java.util.EnumMap; +import java.util.regex.Pattern; import org.opengis.parameter.ParameterDescriptor; import org.opengis.referencing.operation.Matrix; import org.opengis.referencing.operation.OperationMethod; @@ -49,7 +50,7 @@ import static org.apache.sis.internal.referencing.provider.Mollweide.*; * * @author Johann Sorel (Geomatys) * @author Martin Desruisseaux (Geomatys) - * @version 1.0 + * @version 1.2 * @since 1.0 * @module */ @@ -59,6 +60,30 @@ public class Mollweide extends NormalizedProjection { */ private static final long serialVersionUID = 712275000459795291L; + /** + * Allowed projection variants. Current implementation supports only spherical formulas. + * We do not yet use this enumeration for detecting variants from the operation name. + */ + private enum Variant implements ProjectionVariant { + /** The spherical case. */ + SPHERICAL; + + /** The expected name pattern of an operation method for this variant. */ + @Override public Pattern getOperationNamePattern() { + return null; + } + + /** EPSG identifier of an operation method for this variant. */ + @Override public String getIdentifier() { + return null; + } + + /** Requests the use of authalic radius. */ + @Override public boolean useAuthalicRadius() { + return true; + } + } + /** * Work around for RFE #4093999 in Sun's bug database * ("Relax constraint on placement of this()/super() call in constructors"). @@ -69,7 +94,7 @@ public class Mollweide extends NormalizedProjection { roles.put(ParameterRole.CENTRAL_MERIDIAN, CENTRAL_MERIDIAN); roles.put(ParameterRole.FALSE_EASTING, FALSE_EASTING); roles.put(ParameterRole.FALSE_NORTHING, FALSE_NORTHING); - return new Initializer(method, parameters, roles, Initializer.AUTHALIC_RADIUS); + return new Initializer(method, parameters, roles, Variant.SPHERICAL); } /** diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java index 4822743e7f..9682cd4207 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; import java.util.HashMap; import java.util.Objects; +import java.util.regex.Pattern; import java.io.Serializable; import java.lang.reflect.Modifier; import org.opengis.metadata.Identifier; @@ -175,12 +176,6 @@ public abstract class NormalizedProjection extends AbstractMathTransform2D imple */ static final int MAXIMUM_ITERATIONS = Formulas.MAXIMUM_ITERATIONS; - /** - * In map projection implementations that can have some variants, the constant for identifying - * the most standard form of the projection. - */ - static final byte STANDARD_VARIANT = 0; - /** * The internal parameter descriptors. Keys are implementation classes. Values are parameter descriptor groups * containing at least a parameter for the {@link #eccentricity} value, and optionally other internal parameter @@ -426,7 +421,7 @@ public abstract class NormalizedProjection extends AbstractMathTransform2D imple protected NormalizedProjection(final OperationMethod method, final Parameters parameters, final Map<ParameterRole, ? extends ParameterDescriptor<? extends Number>> roles) { - this(new Initializer(method, parameters, roles, STANDARD_VARIANT)); + this(new Initializer(method, parameters, roles, null)); } /** @@ -455,30 +450,33 @@ public abstract class NormalizedProjection extends AbstractMathTransform2D imple } /** - * Returns {@code true} if the projection specified by the given method has the given keyword or identifier. - * If non-null, the given identifier is presumed in the EPSG namespace and has precedence over the keyword. - * - * <div class="note"><b>Implementation note:</b> - * Since callers usually give a constant string for the {@code regex} argument, it would be more efficient to - * compile the {@link java.util.regex.Pattern} once for all. However the regular expression is used only as a - * fallback if the descriptor does not contain EPSG identifier, which should be rare. Usually, the regular - * expression will never be compiled.</div> + * Returns the variant of the map projection described by the given operation method. + * Identifiers are tested first because they have precedence over operation names. * - * @param method the user-specified projection method. - * @param regex the regular expression to use when using the operation name as the criterion. - * @param identifier the identifier to compare against the operation method name. - * @return {@code true} if the name of the given operation method contains the given keyword - * or has an EPSG identifier equals to the given identifier. + * @param method the user-specified projection method. + * @param variants possible variants for the map projection. + * @param defaultValue value to return if no match is found. + * @return the variant of the given operation method, or {@code defaultValue} if none. */ - static boolean identMatch(final OperationMethod method, final String regex, final String identifier) { - if (identifier != null) { - for (final Identifier id : method.getIdentifiers()) { - if (Constants.EPSG.equals(id.getCodeSpace())) { - return identifier.equals(id.getCode()); + static <V extends ProjectionVariant> V variant(final OperationMethod method, final V[] variants, final V defaultValue) { + for (final V variant : variants) { + final String identifier = variant.getIdentifier(); + if (identifier != null) { + for (final Identifier id : method.getIdentifiers()) { + if (Constants.EPSG.equals(id.getCodeSpace()) && identifier.equals(id.getCode())) { + return variant; + } } } } - return method.getName().getCode().replace('_',' ').matches(regex); + final String name = method.getName().getCode().replace('_',' '); + for (final V variant : variants) { + final Pattern regex = variant.getOperationNamePattern(); + if (regex.matcher(name).matches()) { + return variant; + } + } + return defaultValue; } /** diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueMercator.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueMercator.java index a32206d5db..528763f215 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueMercator.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueMercator.java @@ -17,6 +17,7 @@ package org.apache.sis.referencing.operation.projection; import java.util.EnumMap; +import java.util.regex.Pattern; import org.opengis.parameter.ParameterDescriptor; import org.opengis.parameter.InvalidParameterValueException; import org.opengis.referencing.operation.OperationMethod; @@ -69,31 +70,58 @@ public class ObliqueMercator extends ConformalProjection { /** * For compatibility with different versions during deserialization. */ - private static final long serialVersionUID = -5289761492678147674L; + private static final long serialVersionUID = -2194259651400234956L; /** - * Bitmask for projections having easting/northing values defined at projection center - * instead of at coordinate system natural origin. - * This bit is unset for <cite>Hotine Oblique Mercator (variant A)</cite> (EPSG:9812) - * and is set for <cite>Hotine Oblique Mercator (variant B)</cite> (EPSG:9815). + * Variants of Oblique Mercator projection. */ - private static final byte CENTER = 1; + private enum Variant implements ProjectionVariant { + /** The <cite>Hotine Oblique Mercator (variant A)</cite> projection. */ + DEFAULT(".*\\bvariant\\s*A\\b.*", IDENTIFIER_A, false, false), - /** - * Bitmask for projections having their central line defined by two points instead of an azimuth angle. - * The two points variants are used by ESRI. - */ - private static final byte TWO_POINTS = 2; + /** The <cite>Hotine Oblique Mercator (variant B)</cite> projection. */ + CENTER(".*\\bvariant\\s*B\\b.*", IDENTIFIER, false, true), - /** - * Returns the type of the projection based on the name and identifier of the given operation method. - */ - private static byte getVariant(final OperationMethod method) { - if (identMatch(method, "(?i).*\\bvariant\\s*A\\b.*", IDENTIFIER_A)) return 0; - if (identMatch(method, "(?i).*\\bvariant\\s*B\\b.*", IDENTIFIER )) return CENTER; - if (identMatch(method, "(?i).*\\bTwo[_\\s]Point[_\\s]Natural\\b.*", null)) return TWO_POINTS; - if (identMatch(method, "(?i).*\\bTwo[_\\s]Point[_\\s]Center\\b.*", null)) return TWO_POINTS | CENTER; - return STANDARD_VARIANT; // Unidentified case, to be considered as variant A. + /** The "<cite>Oblique Mercator</cite>" projection specified by two points on the central line. */ + TWO_POINTS(".*\\bTwo\\s*Point\\s*Natural\\b.*", null, true, false), + + /** The "<cite>Oblique Mercator</cite>" projection specified by two points on the central line. */ + TWO_POINTS_CENTER(".*\\bTwo\\s*Point\\s*Center\\b.*", null, true, true); + + /** Name pattern for this variant. */ private final Pattern operationName; + /** EPSG identifier for this variant. */ private final String identifier; + + /** + * Whether the projection has its central line defined by two points instead of an azimuth angle. + * The two points variants are used by ESRI. + */ + final boolean twoPoints; + + /** + * Whether easting/northing values are defined at projection + * center instead of at coordinate system natural origin. + * This is {@code false} for <cite>Hotine Oblique Mercator (variant A)</cite> + * and {@code true} for <cite>Hotine Oblique Mercator (variant B)</cite>. + */ + final boolean center; + + /** Creates a new enumeration value. */ + private Variant(final String operationName, final String identifier, final boolean twoPoints, final boolean center) { + this.operationName = Pattern.compile(operationName, Pattern.CASE_INSENSITIVE); + this.identifier = identifier; + this.twoPoints = twoPoints; + this.center = center; + } + + /** The expected name pattern of an operation method for this variant. */ + @Override public Pattern getOperationNamePattern() { + return operationName; + } + + /** EPSG identifier of an operation method for this variant. */ + @Override public String getIdentifier() { + return identifier; + } } /** @@ -133,13 +161,12 @@ public class ObliqueMercator extends ConformalProjection { */ @Workaround(library="JDK", version="1.7") private static Initializer initializer(final OperationMethod method, final Parameters parameters) { - final byte variant = getVariant(method); - final boolean isCenter = (variant & CENTER) != 0; + final Variant variant = variant(method, Variant.values(), Variant.DEFAULT); final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new EnumMap<>(ParameterRole.class); // ParameterRole.CENTRAL_MERIDIAN intentionally excluded. It will be handled in the constructor instead. roles.put(ParameterRole.SCALE_FACTOR, SCALE_FACTOR); - roles.put(ParameterRole.FALSE_EASTING, isCenter ? EASTING_AT_CENTRE : FALSE_EASTING); - roles.put(ParameterRole.FALSE_NORTHING, isCenter ? NORTHING_AT_CENTRE : FALSE_NORTHING); + roles.put(ParameterRole.FALSE_EASTING, variant.center ? EASTING_AT_CENTRE : FALSE_EASTING); + roles.put(ParameterRole.FALSE_NORTHING, variant.center ? NORTHING_AT_CENTRE : FALSE_NORTHING); return new Initializer(method, parameters, roles, variant); } @@ -149,6 +176,7 @@ public class ObliqueMercator extends ConformalProjection { */ private ObliqueMercator(final Initializer initializer) { super(initializer); + final Variant variant = (Variant) initializer.variant; final double λc = toRadians(initializer.getAndStore(LONGITUDE_OF_CENTRE)); final double φc = toRadians(initializer.getAndStore(LATITUDE_OF_CENTRE)); final double sinφ = sin(φc); @@ -180,7 +208,7 @@ public class ObliqueMercator extends ConformalProjection { * Only the azimuth case is described in EPSG guidance notes. */ double αc, γc, γ0, λ0; - if ((initializer.variant & TWO_POINTS) == 0) { + if (!variant.twoPoints) { αc = initializer.getAndStore(AZIMUTH); // Convert to radians later. γc = toRadians(initializer.getAndStore(RECTIFIED_GRID_ANGLE, αc)); αc = toRadians(αc); @@ -259,7 +287,7 @@ public class ObliqueMercator extends ConformalProjection { * = A⋅(λc – λ₀) if αc = 90°. */ final double ArB = A / B; - if ((initializer.variant & CENTER) != 0) { + if (variant.center) { final double uc; if (abs(abs(αc) - PI/2) < ANGULAR_TOLERANCE) { uc = A * (λc - λ0); diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueStereographic.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueStereographic.java index 8fff3f55fc..9a6cf818fd 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueStereographic.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ObliqueStereographic.java @@ -129,7 +129,7 @@ public class ObliqueStereographic extends NormalizedProjection { roles.put(ParameterRole.SCALE_FACTOR, SCALE_FACTOR); roles.put(ParameterRole.FALSE_EASTING, FALSE_EASTING); roles.put(ParameterRole.FALSE_NORTHING, FALSE_NORTHING); - return new Initializer(method, parameters, roles, STANDARD_VARIANT); + return new Initializer(method, parameters, roles, null); } /** diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Orthographic.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Orthographic.java index 304b25ed89..4dbb366589 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Orthographic.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Orthographic.java @@ -77,7 +77,7 @@ public class Orthographic extends NormalizedProjection { roles.put(ParameterRole.CENTRAL_MERIDIAN, LONGITUDE_OF_ORIGIN); roles.put(ParameterRole.FALSE_EASTING, FALSE_EASTING); roles.put(ParameterRole.FALSE_NORTHING, FALSE_NORTHING); - return new Initializer(method, parameters, roles, STANDARD_VARIANT); + return new Initializer(method, parameters, roles, null); } /** diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java index f1206f8c2b..6861708782 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java @@ -17,6 +17,7 @@ package org.apache.sis.referencing.operation.projection; import java.util.EnumMap; +import java.util.regex.Pattern; import org.opengis.util.FactoryException; import org.opengis.parameter.ParameterDescriptor; import org.opengis.referencing.operation.MathTransform; @@ -56,7 +57,7 @@ import static org.apache.sis.internal.referencing.Formulas.fastHypot; * @author Martin Desruisseaux (MPO, IRD, Geomatys) * @author Rueben Schulz (UBC) * @author Rémi Maréchal (Geomatys) - * @version 0.6 + * @version 1.2 * * @see ObliqueStereographic * @@ -70,28 +71,48 @@ public class PolarStereographic extends ConformalProjection { private static final long serialVersionUID = -6635298308431138524L; /** - * Codes for variants of Polar Stereographic projection. Those variants modify the way the projections are - * constructed (e.g. in the way parameters are interpreted), but formulas are basically the same after construction. + * Variants of Polar Stereographic projection. Those variants modify the way the projections are constructed + * (e.g. in the way parameters are interpreted), but formulas are basically the same after construction. * Those variants are not exactly the same than variants A, B and C used by EPSG, but they are closely related. * * <p>We do not provide such codes in public API because they duplicate the functionality of * {@link OperationMethod} instances. We use them only for constructors convenience.</p> * - * @see #getVariant(OperationMethod) + * <p>The default case is {@link #B}.</p> */ - private static final byte A = 1, B = 2, C = 3, NORTH = 4, SOUTH = 5; + private enum Variant implements ProjectionVariant { + /** The <cite>"Polar Stereographic (Variant A)"</cite> projection. */ + A(".*\\bvariant\\s*A\\b.*", PolarStereographicA.IDENTIFIER), - /** - * Returns the type of the projection based on the name and identifier of the given operation method. - * If this method can not identify the type, then the parameters should be considered as a 2SP case. - */ - private static byte getVariant(final OperationMethod method) { - if (identMatch(method, "(?i).*\\bvariant\\s*A\\b.*", PolarStereographicA.IDENTIFIER)) return A; - if (identMatch(method, "(?i).*\\bvariant\\s*B\\b.*", PolarStereographicB.IDENTIFIER)) return B; - if (identMatch(method, "(?i).*\\bvariant\\s*C\\b.*", PolarStereographicC.IDENTIFIER)) return C; - if (identMatch(method, "(?i).*\\bNorth\\b.*", null)) return NORTH; - if (identMatch(method, "(?i).*\\bSouth\\b.*", null)) return SOUTH; - return STANDARD_VARIANT; // Unidentified case, to be considered as variant B. + /** The <cite>"Polar Stereographic (Variant B)"</cite> projection. */ + B(".*\\bvariant\\s*B\\b.*", PolarStereographicB.IDENTIFIER), + + /** The <cite>"Polar Stereographic (Variant C)"</cite> projection. */ + C(".*\\bvariant\\s*C\\b.*", PolarStereographicC.IDENTIFIER), + + /** <cite>"Stereographic North Pole"</cite> projection (ESRI). */ + NORTH(".*\\bNorth\\b.*", null), + + /** <cite>"Stereographic South Pole"</cite> projection (ESRI). */ + SOUTH(".*\\bSouth\\b.*", null); + + /** Name pattern for this variant. */ private final Pattern operationName; + /** EPSG identifier for this variant. */ private final String identifier; + /** Creates a new enumeration value. */ + private Variant(final String operationName, final String identifier) { + this.operationName = Pattern.compile(operationName, Pattern.CASE_INSENSITIVE); + this.identifier = identifier; + } + + /** The expected name pattern of an operation method for this variant. */ + @Override public Pattern getOperationNamePattern() { + return operationName; + } + + /** EPSG identifier of an operation method for this variant. */ + @Override public String getIdentifier() { + return identifier; + } } /** @@ -99,9 +120,11 @@ public class PolarStereographic extends ConformalProjection { * The {@code method} argument can be the description of one of the following: * * <ul> - * <li><cite>"Polar Stereographic (Variant A)"</cite>.</li> - * <li><cite>"Polar Stereographic (Variant B)"</cite>.</li> - * <li><cite>"Polar Stereographic (Variant C)"</cite>.</li> + * <li><cite>"Polar Stereographic (Variant A)"</cite> (EPSG:9810).</li> + * <li><cite>"Polar Stereographic (Variant B)"</cite> (EPSG:9829).</li> + * <li><cite>"Polar Stereographic (Variant C)"</cite> (EPSG:9830).</li> + * <li><cite>"Stereographic North Pole"</cite> (ESRI).</li> + * <li><cite>"Stereographic South Pole"</cite> (ESRI).</li> * </ul> * * @param method description of the projection parameters. @@ -117,18 +140,18 @@ public class PolarStereographic extends ConformalProjection { */ @Workaround(library="JDK", version="1.7") private static Initializer initializer(final OperationMethod method, final Parameters parameters) { - final byte variant = getVariant(method); + final Variant variant = variant(method, Variant.values(), Variant.B); final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new EnumMap<>(ParameterRole.class); ParameterDescriptor<Double> falseEasting = PolarStereographicA.FALSE_EASTING; ParameterDescriptor<Double> falseNorthing = PolarStereographicA.FALSE_NORTHING; - if (variant == C) { + if (variant == Variant.C) { falseEasting = PolarStereographicC.EASTING_AT_FALSE_ORIGIN; falseNorthing = PolarStereographicC.NORTHING_AT_FALSE_ORIGIN; } roles.put(ParameterRole.FALSE_EASTING, falseEasting); roles.put(ParameterRole.FALSE_NORTHING, falseNorthing); roles.put(ParameterRole.SCALE_FACTOR, PolarStereographicA.SCALE_FACTOR); - roles.put(ParameterRole.CENTRAL_MERIDIAN, (variant == A) + roles.put(ParameterRole.CENTRAL_MERIDIAN, (variant == Variant.A) ? PolarStereographicA.LONGITUDE_OF_ORIGIN : PolarStereographicB.LONGITUDE_OF_ORIGIN); return new Initializer(method, parameters, roles, variant); @@ -141,7 +164,7 @@ public class PolarStereographic extends ConformalProjection { @Workaround(library="JDK", version="1.7") private PolarStereographic(final Initializer initializer) { super(initializer); - final byte variant = initializer.variant; + final Variant variant = (Variant) initializer.variant; /* * "Standard parallel" and "Latitude of origin" should be mutually exclusive, * but this is not a strict requirement for the constructor. @@ -158,19 +181,19 @@ public class PolarStereographic extends ConformalProjection { * └───────────────────────────────────┴────────────────────┴─────────────┘ */ double φ0; - if (variant == A) { + if (variant == Variant.A) { φ0 = initializer.getAndStore(PolarStereographicA.LATITUDE_OF_ORIGIN); // Mandatory } else { φ0 = initializer.getAndStore(PolarStereographicA.LATITUDE_OF_ORIGIN, // Optional (should not be present) - (variant == NORTH) ? Latitude.MAX_VALUE : - (variant == SOUTH) ? Latitude.MIN_VALUE : Double.NaN); + (variant == Variant.NORTH) ? Latitude.MAX_VALUE : + (variant == Variant.SOUTH) ? Latitude.MIN_VALUE : Double.NaN); } if (abs(abs(φ0) - Latitude.MAX_VALUE) > Formulas.ANGULAR_TOLERANCE) { // Can be only -90°, +90° or NaN throw new IllegalArgumentException(Resources.format(Resources.Keys.IllegalParameterValue_2, PolarStereographicA.LATITUDE_OF_ORIGIN.getName(), φ0)); } double φ1; - if (variant == B || variant == C || Double.isNaN(φ0)) { + if (variant == Variant.B || variant == Variant.C || Double.isNaN(φ0)) { φ1 = initializer.getAndStore(PolarStereographicB.STANDARD_PARALLEL); // Mandatory } else { φ1 = initializer.getAndStore(PolarStereographicB.STANDARD_PARALLEL, φ0); // Optional @@ -231,7 +254,7 @@ public class PolarStereographic extends ConformalProjection { final double sinφ1 = sin(φ1); final double mF = initializer.scaleAtφ(sinφ1, cos(φ1)); ρ = new DoubleDouble(mF / expΨ(φ1, eccentricity*sinφ1)); - ρF = (variant == C) ? new DoubleDouble(-mF) : null; + ρF = (variant == Variant.C) ? new DoubleDouble(-mF) : null; } /* * At this point, all parameters have been processed. Now process to their diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Polyconic.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Polyconic.java index 61c37d2f1f..b4bf84d41a 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Polyconic.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Polyconic.java @@ -101,7 +101,7 @@ public class Polyconic extends MeridianArcBased { roles.put(ParameterRole.CENTRAL_MERIDIAN, LONGITUDE_OF_ORIGIN); roles.put(ParameterRole.FALSE_EASTING, FALSE_EASTING); roles.put(ParameterRole.FALSE_NORTHING, FALSE_NORTHING); - return new Initializer(method, parameters, roles, STANDARD_VARIANT); + return new Initializer(method, parameters, roles, null); } /** diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ProjectionVariant.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ProjectionVariant.java new file mode 100644 index 0000000000..9fa591ca68 --- /dev/null +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ProjectionVariant.java @@ -0,0 +1,58 @@ +/* + * 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.projection; + +import java.util.regex.Pattern; + + +/** + * Variant of the map projection used. This interface is implemented by enumerations + * in {@link NormalizedProjection} sub-classes that support many variants. + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.2 + * @since 1.2 + * @module + */ +interface ProjectionVariant { + /** + * Returns the regular expression pattern to use for determining if the name of an operation method + * identifies this variant. + * + * @return the operation name pattern for this variant. + */ + Pattern getOperationNamePattern(); + + /** + * Returns the EPSG identifier to compare against the operation method. + * If non-null, the identifier is presumed in the EPSG namespace and has precedence over the pattern. + * + * @return EPSG identifier for this variant, or {@code null} if none. + */ + String getIdentifier(); + + /** + * Whether this variant is a spherical variant using authalic radius. + * This method can be overridden for handling authalic radius, but not conformance sphere radius. + * The latter is handled by {@link NormalizedProjection.ParameterRole#LATITUDE_OF_CONFORMAL_SPHERE_RADIUS}. + * + * @return whether this variant is a spherical variant using authalic radius. + */ + default boolean useAuthalicRadius() { + return false; + } +} diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/SatelliteTracking.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/SatelliteTracking.java index 046e8cd677..1a1b122053 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/SatelliteTracking.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/SatelliteTracking.java @@ -116,7 +116,7 @@ public class SatelliteTracking extends NormalizedProjection { final EnumMap<NormalizedProjection.ParameterRole, ParameterDescriptor<Double>> roles = new EnumMap<>(NormalizedProjection.ParameterRole.class); roles.put(NormalizedProjection.ParameterRole.CENTRAL_MERIDIAN, CENTRAL_MERIDIAN); roles.put(ParameterRole.LATITUDE_OF_CONFORMAL_SPHERE_RADIUS, LATITUDE_OF_ORIGIN); - return new Initializer(method, parameters, roles, STANDARD_VARIANT); + return new Initializer(method, parameters, roles, null); } /** diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Sinusoidal.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Sinusoidal.java index 684fd59870..38a12c3142 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Sinusoidal.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Sinusoidal.java @@ -61,7 +61,7 @@ public class Sinusoidal extends MeridianArcBased { roles.put(ParameterRole.CENTRAL_MERIDIAN, CENTRAL_MERIDIAN); roles.put(ParameterRole.FALSE_EASTING, FALSE_EASTING); roles.put(ParameterRole.FALSE_NORTHING, FALSE_NORTHING); - return new Initializer(method, parameters, roles, STANDARD_VARIANT); + return new Initializer(method, parameters, roles, null); } /** diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java index 25e946bb95..c1a07dc8da 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java @@ -17,6 +17,7 @@ package org.apache.sis.referencing.operation.projection; import java.util.EnumMap; +import java.util.regex.Pattern; import org.opengis.util.FactoryException; import org.opengis.parameter.ParameterDescriptor; import org.opengis.referencing.operation.MathTransform; @@ -65,7 +66,7 @@ import static org.apache.sis.internal.referencing.provider.TransverseMercator.*; * * @author Martin Desruisseaux (Geomatys) * @author Rémi Maréchal (Geomatys) - * @version 1.1 + * @version 1.2 * * @see Mercator * @see ObliqueMercator @@ -116,11 +117,30 @@ public class TransverseMercator extends NormalizedProjection { private static final boolean ALLOW_TRIGONOMETRIC_IDENTITIES = true; /** - * The "South orientated" variant of Transverse Mercator projection. - * Currently this is informative only (the south variant is handled - * with {@link ParameterRole} instead). + * Variants of the map projection. Currently this is informative only + * (the south variant is handled with {@link ParameterRole} instead). */ - private static final byte SOUTH_VARIANT = 1; + private enum Variant implements ProjectionVariant { + /** The "South orientated" variant of Transverse Mercator projection. */ + SOUTH_ORIENTATED(".*\\bSouth\\b.*", TransverseMercatorSouth.IDENTIFIER); + + /** Name pattern for this variant. */ private final Pattern operationName; + /** EPSG identifier for this variant. */ private final String identifier; + private Variant(final String operationName, final String identifier) { + this.operationName = Pattern.compile(operationName, Pattern.CASE_INSENSITIVE); + this.identifier = identifier; + } + + /** The expected name pattern of an operation method for this variant. */ + @Override public Pattern getOperationNamePattern() { + return operationName; + } + + /** EPSG identifier of an operation method for this variant. */ + @Override public String getIdentifier() { + return identifier; + } + } /** * Verifies if a trigonometric identity produced the expected value. This method is used in assertions only, @@ -181,11 +201,11 @@ public class TransverseMercator extends NormalizedProjection { */ @Workaround(library="JDK", version="1.7") private static Initializer initializer(final OperationMethod method, final Parameters parameters) { - final boolean isSouth = identMatch(method, "(?i).*\\bSouth\\b.*", TransverseMercatorSouth.IDENTIFIER); + final Variant variant = variant(method, Variant.values(), null); final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new EnumMap<>(ParameterRole.class); ParameterRole xOffset = ParameterRole.FALSE_EASTING; ParameterRole yOffset = ParameterRole.FALSE_NORTHING; - if (isSouth) { + if (variant == Variant.SOUTH_ORIENTATED) { xOffset = ParameterRole.FALSE_WESTING; yOffset = ParameterRole.FALSE_SOUTHING; } @@ -193,7 +213,7 @@ public class TransverseMercator extends NormalizedProjection { roles.put(ParameterRole.SCALE_FACTOR, SCALE_FACTOR); roles.put(xOffset, FALSE_EASTING); roles.put(yOffset, FALSE_NORTHING); - return new Initializer(method, parameters, roles, isSouth ? SOUTH_VARIANT : STANDARD_VARIANT); + return new Initializer(method, parameters, roles, variant); } /** diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ZonedGridSystem.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ZonedGridSystem.java index 778a53c8d8..b5b66a2a30 100644 --- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ZonedGridSystem.java +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ZonedGridSystem.java @@ -125,7 +125,7 @@ public class ZonedGridSystem extends AbstractMathTransform2D implements Serializ roles.put(NormalizedProjection.ParameterRole.SCALE_FACTOR, SCALE_FACTOR); roles.put(NormalizedProjection.ParameterRole.FALSE_EASTING, FALSE_EASTING); roles.put(NormalizedProjection.ParameterRole.FALSE_NORTHING, FALSE_NORTHING); - final Initializer initializer = new Initializer(method, parameters, roles, NormalizedProjection.STANDARD_VARIANT); + final Initializer initializer = new Initializer(method, parameters, roles, null); initialLongitude = initializer.getAndStore(INITIAL_LONGITUDE); zoneWidth = initializer.getAndStore(ZONE_WIDTH); final MatrixSIS normalize = initializer.context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION); diff --git a/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod b/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod index f5398c4f16..0e11fa01f6 100644 --- a/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod +++ b/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod @@ -32,6 +32,7 @@ org.apache.sis.internal.referencing.provider.Mercator1SP org.apache.sis.internal.referencing.provider.Mercator2SP org.apache.sis.internal.referencing.provider.MercatorSpherical org.apache.sis.internal.referencing.provider.PseudoMercator +org.apache.sis.internal.referencing.provider.MercatorAuxiliarySphere org.apache.sis.internal.referencing.provider.RegionalMercator org.apache.sis.internal.referencing.provider.MillerCylindrical org.apache.sis.internal.referencing.provider.LambertConformal1SP diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java index 18096e3c63..4810aa159a 100644 --- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java +++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java @@ -81,6 +81,7 @@ public final strictfp class ProvidersTest extends TestCase { Mercator2SP.class, MercatorSpherical.class, PseudoMercator.class, + MercatorAuxiliarySphere.class, RegionalMercator.class, MillerCylindrical.class, LambertConformal1SP.class, diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/InitializerTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/InitializerTest.java index a6e93d7cea..1e0cd789af 100644 --- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/InitializerTest.java +++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/InitializerTest.java @@ -77,7 +77,7 @@ public final strictfp class InitializerTest extends TestCase{ roles.put(NormalizedProjection.ParameterRole.SCALE_FACTOR, ObliqueStereographic.SCALE_FACTOR); roles.put(NormalizedProjection.ParameterRole.FALSE_EASTING, ObliqueStereographic.FALSE_EASTING); roles.put(NormalizedProjection.ParameterRole.FALSE_NORTHING, ObliqueStereographic.FALSE_NORTHING); - final Initializer initializer = new Initializer(op, (Parameters) p, roles, NormalizedProjection.STANDARD_VARIANT); + final Initializer initializer = new Initializer(op, (Parameters) p, roles, null); /* * The following lines give an example of how Apache SIS projection constructors * use the Initializer class. diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java index 978fe9e707..89cb0ee67d 100644 --- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java +++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/NoOp.java @@ -33,7 +33,7 @@ import org.apache.sis.util.Workaround; * This is used for testing methods other than {@code transform(…)} and {@code inverseTransform(…)}. * * @author Martin Desruisseaux (Geomatys) - * @version 0.6 + * @version 1.2 * @since 0.6 * @module */ @@ -74,7 +74,7 @@ final strictfp class NoOp extends ConformalProjection { super(new Initializer(new DefaultOperationMethod( Collections.singletonMap(DefaultOperationMethod.NAME_KEY, parameters.getDescriptor().getName()), DIMENSION, DIMENSION, - parameters.getDescriptor()), parameters, Collections.emptyMap(), STANDARD_VARIANT)); + parameters.getDescriptor()), parameters, Collections.emptyMap(), null)); } /**
