sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch master updated: Implement "Modified Azimuthal Equidistant" projection method (without derivative for now). https://issues.apache.org/jira/browse/SIS-237
Date Fri, 20 Mar 2020 22:10:15 GMT
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/master by this push:
     new fd8015a  Implement "Modified Azimuthal Equidistant" projection method (without derivative for now). https://issues.apache.org/jira/browse/SIS-237
fd8015a is described below

commit fd8015a050d71db2d7cced23056e65e02a424f12
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Fri Mar 20 23:09:25 2020 +0100

    Implement "Modified Azimuthal Equidistant" projection method (without derivative for now).
    https://issues.apache.org/jira/browse/SIS-237
---
 .../apache/sis/internal/referencing/Formulas.java  |  21 +-
 .../provider/AzimuthalEquidistantSpherical.java    |  83 ++++++++
 .../provider/ModifiedAzimuthalEquidistant.java     | 167 +++++++++++++++
 .../operation/projection/AlbersEqualArea.java      |   4 +-
 .../operation/projection/AzimuthalEquidistant.java | 202 +++++++++++++++++++
 .../projection/ModifiedAzimuthalEquidistant.java   | 223 +++++++++++++++++++++
 .../operation/projection/ObliqueStereographic.java |   4 +-
 .../operation/projection/Polyconic.java            |   3 +-
 ...g.opengis.referencing.operation.OperationMethod |   2 +
 .../referencing/provider/ProvidersTest.java        |   2 +
 .../projection/AzimuthalEquidistantTest.java       | 162 +++++++++++++++
 .../ModifiedAzimuthalEquidistantTest.java          |  81 ++++++++
 .../sis/test/suite/ReferencingTestSuite.java       |   2 +
 13 files changed, 950 insertions(+), 6 deletions(-)

diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java
index 6cce1df..f0012a5 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/Formulas.java
@@ -33,7 +33,7 @@ import static org.apache.sis.internal.metadata.ReferencingServices.NAUTICAL_MILE
  * do not want to expose publicly those arbitrary values (or at least not in a too direct way).
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   0.4
  * @module
  */
@@ -161,6 +161,25 @@ public final class Formulas extends Static {
     }
 
     /**
+     * Returns the radius of the conformal sphere at a given latitude.
+     * The radius of conformal sphere is computed as below:
+     *
+     * <blockquote>Rc = √(1 – ℯ²) / (1 – ℯ²sin²φ)  where  ℯ² = 1 - (b/a)²</blockquote>
+     *
+     * This is a function of latitude and therefore not constant.
+     *
+     * @param  ellipsoid  the ellipsoid for which to compute the radius of conformal sphere.
+     * @param  φ          the latitude in radians where to compute the radius of conformal sphere.
+     * @return radius of the conformal sphere at latitude φ.
+     */
+    public static double radiusOfConformalSphere(final Ellipsoid ellipsoid, final double φ) {
+        final double sinφ = Math.sin(φ);
+        final double a = ellipsoid.getSemiMajorAxis();
+        final double r = ellipsoid.getSemiMinorAxis() / a;
+        return a * (r / (1 - (1 - r*r) * (sinφ*sinφ)));
+    }
+
+    /**
      * Computes the semi-minor axis length from the given semi-major axis and inverse flattening factor.
      *
      * @param  semiMajorAxis      the semi-major axis length.
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AzimuthalEquidistantSpherical.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AzimuthalEquidistantSpherical.java
new file mode 100644
index 0000000..ebaea11
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/AzimuthalEquidistantSpherical.java
@@ -0,0 +1,83 @@
+/*
+ * 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.ParameterDescriptorGroup;
+import org.opengis.referencing.operation.PlanarProjection;
+import org.apache.sis.parameter.Parameters;
+import org.apache.sis.referencing.operation.projection.NormalizedProjection;
+
+
+/**
+ * The provider for <cite>"Azimuthal Equidistant (Spherical)"</cite> projection.
+ * This projection method has no EPSG code.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ *
+ * @see <a href="http://geotiff.maptools.org/proj_list/azimuthal_equidistant.html">GeoTIFF parameters for Azimuthal Equidistant</a>
+ *
+ * @since 1.1
+ * @module
+ */
+@XmlTransient
+public final class AzimuthalEquidistantSpherical extends MapProjection {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -1152512250113874950L;
+
+    /**
+     * The group of all parameters expected by this coordinate operation.
+     */
+    static final ParameterDescriptorGroup PARAMETERS;
+    static {
+        PARAMETERS = builder().setCodeSpace(null, null)
+                .addName("Azimuthal Equidistant (Spherical)")
+                .createGroupForMapProjection(
+                        ModifiedAzimuthalEquidistant.LATITUDE_OF_ORIGIN,
+                        ModifiedAzimuthalEquidistant.LONGITUDE_OF_ORIGIN,
+                        ModifiedAzimuthalEquidistant.FALSE_EASTING,
+                        ModifiedAzimuthalEquidistant.FALSE_NORTHING);
+    }
+
+    /**
+     * Constructs a new provider.
+     */
+    public AzimuthalEquidistantSpherical() {
+        super(PARAMETERS);
+    }
+
+    /**
+     * Returns the operation type for this map projection.
+     */
+    @Override
+    public Class<PlanarProjection> getOperationType() {
+        return PlanarProjection.class;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return the map projection created from the given parameter values.
+     */
+    @Override
+    protected final NormalizedProjection createProjection(final Parameters parameters) {
+        return new org.apache.sis.referencing.operation.projection.AzimuthalEquidistant(this, parameters);
+    }
+}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ModifiedAzimuthalEquidistant.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ModifiedAzimuthalEquidistant.java
new file mode 100644
index 0000000..5c2a680
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ModifiedAzimuthalEquidistant.java
@@ -0,0 +1,167 @@
+/*
+ * 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.opengis.referencing.operation.PlanarProjection;
+import org.apache.sis.parameter.Parameters;
+import org.apache.sis.parameter.ParameterBuilder;
+import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.referencing.operation.projection.NormalizedProjection;
+
+
+/**
+ * The provider for <cite>"Modified Azimuthal Equidistant"</cite> projection (EPSG:9832).
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ *
+ * @see <a href="http://geotiff.maptools.org/proj_list/azimuthal_equidistant.html">GeoTIFF parameters for Azimuthal Equidistant</a>
+ *
+ * @since 1.1
+ * @module
+ */
+@XmlTransient
+public final class ModifiedAzimuthalEquidistant extends MapProjection {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -9025540136016917410L;
+
+    /**
+     * The operation parameter descriptor for the <cite>Latitude of natural origin</cite> (φ₀) parameter value.
+     * Valid values range is (-90 … 90)° and default value is 0°.
+     *
+     * <!-- Generated by ParameterNameTableGenerator -->
+     * <table class="sis">
+     *   <caption>Parameter names</caption>
+     *   <tr><td> EPSG:    </td><td> Latitude of natural origin </td></tr>
+     *   <tr><td> OGC:     </td><td> latitude_of_origin </td></tr>
+     *   <tr><td> GeoTIFF: </td><td> CenterLat </td></tr>
+     *   <tr><td> Proj4:   </td><td> lat_0 </td></tr>
+     * </table>
+     */
+    public static final ParameterDescriptor<Double> LATITUDE_OF_ORIGIN;
+
+    /**
+     * The operation parameter descriptor for the <cite>Longitude of natural origin</cite> (λ₀) parameter value.
+     * Valid values range is [-180 … 180]° and default value is 0°.
+     *
+     * <!-- Generated by ParameterNameTableGenerator -->
+     * <table class="sis">
+     *   <caption>Parameter names</caption>
+     *   <tr><td> EPSG:    </td><td> Longitude of natural origin </td></tr>
+     *   <tr><td> OGC:     </td><td> central_meridian </td></tr>
+     *   <tr><td> GeoTIFF: </td><td> CenterLong </td></tr>
+     *   <tr><td> Proj4:   </td><td> lon_0 </td></tr>
+     * </table>
+     */
+    public static final ParameterDescriptor<Double> LONGITUDE_OF_ORIGIN;
+
+    /**
+     * The operation parameter descriptor for the <cite>False easting</cite> (FE) parameter value.
+     * Valid values range is unrestricted and default value is 0 metre.
+     *
+     * <!-- Generated by ParameterNameTableGenerator -->
+     * <table class="sis">
+     *   <caption>Parameter names</caption>
+     *   <tr><td> EPSG:    </td><td> False easting </td></tr>
+     *   <tr><td> OGC:     </td><td> false_easting </td></tr>
+     *   <tr><td> GeoTIFF: </td><td> FalseEasting </td></tr>
+     *   <tr><td> Proj4:   </td><td> x_0 </td></tr>
+     * </table>
+     */
+    public static final ParameterDescriptor<Double> FALSE_EASTING;
+
+    /**
+     * The operation parameter descriptor for the <cite>False northing</cite> (FN) parameter value.
+     * Valid values range is unrestricted and default value is 0 metre.
+     *
+     * <!-- Generated by ParameterNameTableGenerator -->
+     * <table class="sis">
+     *   <caption>Parameter names</caption>
+     *   <tr><td> EPSG:    </td><td> False northing </td></tr>
+     *   <tr><td> OGC:     </td><td> false_northing </td></tr>
+     *   <tr><td> GeoTIFF: </td><td> FalseNorthing </td></tr>
+     *   <tr><td> Proj4:   </td><td> y_0 </td></tr>
+     * </table>
+     */
+    public static final ParameterDescriptor<Double> FALSE_NORTHING;
+
+    /**
+     * Returns a parameter with the same names and identifiers than the given parameter,
+     * except (OGC), ESRI and netCDF names which are omitted. We omit those names for now
+     * because we have not seen a reference about what those parameter names should be.
+     * The OGC names are kept despite that because it uses the same names for most projection.
+     * This may be revisited in future SIS versions.
+     *
+     * <p>The OGC and GeoTIFF names kept by this method are actually the names for
+     * <cite>Azimuthal Equidistant</cite> (not modified) projection.</p>
+     */
+    private static ParameterBuilder erase(final ParameterBuilder builder, ParameterDescriptor<?> template) {
+        return builder.addNamesAndIdentifiers(template)                         // Copy from this parameter…
+                      .rename(Citations.ESRI,   (CharSequence[]) null)          // … except for those names.
+                      .rename(Citations.NETCDF, (CharSequence[]) null);
+    }
+
+    /**
+     * The group of all parameters expected by this coordinate operation.
+     */
+    static final ParameterDescriptorGroup PARAMETERS;
+    static {
+        final ParameterBuilder builder = builder();
+        LATITUDE_OF_ORIGIN  = createLatitude (erase(builder, Orthographic.LATITUDE_OF_ORIGIN), true);
+        LONGITUDE_OF_ORIGIN = createLongitude(erase(builder, Orthographic.LONGITUDE_OF_ORIGIN));
+        FALSE_EASTING       = createShift    (erase(builder, Orthographic.FALSE_EASTING));
+        FALSE_NORTHING      = createShift    (erase(builder, Orthographic.FALSE_NORTHING));
+
+        PARAMETERS = builder.addIdentifier("9832")
+                .addName("Modified Azimuthal Equidistant")
+                .createGroupForMapProjection(
+                        LATITUDE_OF_ORIGIN,
+                        LONGITUDE_OF_ORIGIN,
+                        FALSE_EASTING,
+                        FALSE_NORTHING);
+    }
+
+    /**
+     * Constructs a new provider.
+     */
+    public ModifiedAzimuthalEquidistant() {
+        super(PARAMETERS);
+    }
+
+    /**
+     * Returns the operation type for this map projection.
+     */
+    @Override
+    public Class<PlanarProjection> getOperationType() {
+        return PlanarProjection.class;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return the map projection created from the given parameter values.
+     */
+    @Override
+    protected final NormalizedProjection createProjection(final Parameters parameters) {
+        return new org.apache.sis.referencing.operation.projection.ModifiedAzimuthalEquidistant(this, parameters);
+    }
+}
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 92f1538..c9aa589 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
@@ -255,8 +255,8 @@ public class AlbersEqualArea extends EqualAreaProjection {
         /*
          * End of map projection. Now compute the derivative.
          */
-        final double me = 1 - eccentricitySquared;
-        final double dρ_dφ = -0.5 * nm*dqm_dφ(sinφ, cos(φ)*me) / (me*ρ);
+        final double ome = 1 - eccentricitySquared;
+        final double dρ_dφ = -0.5 * nm*dqm_dφ(sinφ, cos(φ)*ome) / (ome*ρ);
         return new Matrix2(cosθ*ρ, dρ_dφ*sinθ,          // ∂x/∂λ, ∂x/∂φ
                           -sinθ*ρ, dρ_dφ*cosθ);         // ∂y/∂λ, ∂y/∂φ
     }
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
new file mode 100644
index 0000000..6bd1b4a
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/AzimuthalEquidistant.java
@@ -0,0 +1,202 @@
+/*
+ * 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.EnumMap;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.referencing.operation.OperationMethod;
+import org.opengis.parameter.ParameterDescriptor;
+import org.apache.sis.referencing.operation.matrix.Matrix2;
+import org.apache.sis.parameter.Parameters;
+import org.apache.sis.util.Workaround;
+
+import static java.lang.Math.*;
+import static org.apache.sis.internal.referencing.provider.ModifiedAzimuthalEquidistant.*;
+
+
+/**
+ * <cite>Azimuthal Equidistant (Spherical)</cite> projection.
+ * This projection method has no EPSG code.
+ * See the following references for an overview:
+ * <ul>
+ *   <li><a href="https://en.wikipedia.org/wiki/Azimuthal_equidistant_projection">Azimuthal equidistant projection</a></li>
+ *   <li><a href="https://mathworld.wolfram.com/AzimuthalEquidistantProjection.html">Azimuthal Equidistant Projection</a></li>
+ * </ul>
+ *
+ * Current implementation supports only the spherical case.
+ * For ellipsoidal formulas, the {@link ModifiedAzimuthalEquidistant} provides an approximation
+ * valid under 800 kilometres of the projection centre.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ *
+ * @see ModifiedAzimuthalEquidistant
+ *
+ * @since 1.1
+ * @module
+ */
+public class AzimuthalEquidistant extends NormalizedProjection {
+    /**
+     * For compatibility with different versions during deserialization.
+     */
+    private static final long serialVersionUID = -6969752149232210847L;
+
+    /**
+     * Sine and cosine of the latitude of origin φ₀.
+     */
+    final double sinφ0, cosφ0;
+
+    /**
+     * Work around for RFE #4093999 in Sun's bug database
+     * ("Relax constraint on placement of this()/super() call in constructors").
+     */
+    @Workaround(library="JDK", version="1.8")
+    private static Initializer initializer(final OperationMethod method, final Parameters parameters) {
+        final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new EnumMap<>(ParameterRole.class);
+        roles.put(ParameterRole.LATITUDE_OF_CONFORMAL_SPHERE_RADIUS, LATITUDE_OF_ORIGIN);
+        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, (byte) 0);
+    }
+
+    /**
+     * Creates a Azimuthal Equidistant projection from the given parameters.
+     * The {@code method} argument can be the description of one of the following:
+     *
+     * <ul>
+     *   <li><cite>"Azimuthal Equidistant (Spherical)"</cite>.</li>
+     * </ul>
+     *
+     * @param method      description of the projection parameters.
+     * @param parameters  the parameter values of the projection to create.
+     */
+    public AzimuthalEquidistant(final OperationMethod method, final Parameters parameters) {
+        this(initializer(method, parameters));
+    }
+
+    /**
+     * Creates a new normalized projection from the parameters computed by the given initializer.
+     *
+     * @param initializer  the initializer for computing map projection internal parameters.
+     */
+    AzimuthalEquidistant(final Initializer initializer) {
+        super(initializer);
+        final double φ0 = toRadians(initializer.getAndStore(LATITUDE_OF_ORIGIN));
+        cosφ0 = cos(φ0);
+        sinφ0 = sin(φ0);
+    }
+
+    /**
+     * Converts the specified (λ,φ) coordinate and stores the (<var>x</var>,<var>y</var>) result in {@code dstPts}.
+     *
+     * @param  srcPts    source point coordinate, as (<var>longitude</var>, <var>latitude</var>) in radians.
+     * @param  srcOff    the offset of the single coordinate to be converted in the source array.
+     * @param  dstPts    the array into which the converted coordinate is returned (may be the same than {@code srcPts}).
+     * @param  dstOff    the offset of the location of the converted coordinate that is stored in the destination array.
+     * @param  derivate  {@code true} for computing the derivative, or {@code false} if not needed.
+     * @return the matrix of the projection derivative at the given source position,
+     *         or {@code null} if the {@code derivate} argument is {@code false}.
+     * @throws ProjectionException if the coordinate can not be converted.
+     */
+    @Override
+    public Matrix transform(final double[] srcPts, final int srcOff,
+                            final double[] dstPts, final int dstOff,
+                            final boolean derivate) throws ProjectionException
+    {
+        final double  λ     = srcPts[srcOff  ];
+        final double  φ     = srcPts[srcOff+1];
+        final double  cosλ  = cos(λ);
+        final double  sinλ  = sin(λ);
+        final double  cosφ  = cos(φ);
+        final double  sinφ  = sin(φ);
+        final double  cosc  = sinφ0*sinφ + cosφ0*cosφ*cosλ;
+        final double  c     = acos(cosc);
+        final boolean ind   = abs(c) < ANGULAR_TOLERANCE;
+        final double  k     = ind ? 1 : c/sin(c);
+        final double  cφcλ  = cosφ * cosλ;
+        final double  cφsλ  = cosφ * sinλ;
+        final double  x     = k * cφsλ;
+        final double  y     = k * (cosφ0*sinφ - sinφ0*cφcλ);
+        if (dstPts != null) {
+            dstPts[dstOff  ] = x;
+            dstPts[dstOff+1] = y;
+        }
+        if (!derivate) {
+            return null;
+        }
+        /*
+         * Formulas below can be verified with Maxima.
+         *
+         * https://svn.apache.org/repos/asf/sis/analysis/Azimuthal%20Equidistant%20(Spherical).wxmx
+         */
+        final double t    = ind ? 1./3 : (1/k - cosc) / (1 - cosc*cosc);
+        final double sφcλ = sinφ * cosλ;
+        final double tλ   = cφsλ * cosφ0*t;
+        final double tφ   = (cosφ0*sφcλ - sinφ0*cosφ) * t;
+        return new Matrix2(x*tλ + k*cφcλ,                           // ∂x/∂λ
+                           x*tφ - k*sinλ*sinφ,                      // ∂x/∂φ
+                           y*tλ + x*sinφ0,                          // ∂y/∂λ
+                           y*tφ + k*(sinφ0*sφcλ + cosφ0*cosφ));     // ∂y/∂φ
+    }
+
+    /**
+     * Converts the specified (<var>x</var>,<var>y</var>) coordinates
+     * and stores the result in {@code dstPts} (angles in radians).
+     *
+     * @param  srcPts  the array containing the source point coordinate, as linear distance on a unit sphere or ellipse.
+     * @param  srcOff  the offset of the point to be converted in the source array.
+     * @param  dstPts  the array into which the converted point coordinate is returned (may be the same than {@code srcPts}).
+     * @param  dstOff  the offset of the location of the converted point that is stored in the destination array.
+     * @throws ProjectionException if the point can not be converted.
+     */
+    @Override
+    protected void inverseTransform(final double[] srcPts, final int srcOff,
+                                    final double[] dstPts, final int dstOff)
+            throws ProjectionException
+    {
+        final double x    = srcPts[srcOff  ];
+        final double y    = srcPts[srcOff+1];
+        final double D    = hypot(x, y);
+        final double sinD = sin(D);
+        final double cosD = cos(D);
+        dstPts[dstOff  ]  = atan2(x*sinD, (cosφ0*cosD*D - sinφ0*sinD*y));
+        dstPts[dstOff+1]  = asin(cosD*sinφ0 + sinD*cosφ0*y/D);
+    }
+
+    /**
+     * Returns the names of additional internal parameters which need to be taken in account when
+     * comparing two {@code AzimuthalEquidistant} projections or formatting them in debug mode.
+     *
+     * <p>We could report any of the internal parameters. But since they are all derived from φ₀ and
+     * the {@linkplain #eccentricity eccentricity} and since the eccentricity is already reported by
+     * the super-class, we report only φ₀ as a representative of the internal parameters.</p>
+     */
+    @Override
+    final String[] getInternalParameterNames() {
+        return new String[] {"φ₀"};
+    }
+
+    /**
+     * Returns the values of additional internal parameters which need to be taken in account when
+     * comparing two {@code ObliqueStereographic} projections or formatting them in debug mode.
+     */
+    @Override
+    final double[] getInternalParameterValues() {
+        return new double[] {(cosφ0 < PI/4) ? acos(cosφ0) : asin(sinφ0)};
+    }
+}
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
new file mode 100644
index 0000000..5e26b4c
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistant.java
@@ -0,0 +1,223 @@
+/*
+ * 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.EnumMap;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.referencing.operation.OperationMethod;
+import org.opengis.parameter.ParameterDescriptor;
+import org.apache.sis.parameter.Parameters;
+import org.apache.sis.referencing.operation.transform.ContextualParameters;
+import org.apache.sis.referencing.operation.matrix.MatrixSIS;
+import org.apache.sis.util.Workaround;
+
+import static java.lang.Math.*;
+import static org.apache.sis.internal.referencing.provider.ModifiedAzimuthalEquidistant.*;
+
+
+/**
+ * <cite>Modified Azimuthal Equidistant</cite> projection (EPSG:9832).
+ * See the following references for an overview:
+ * <ul>
+ *   <li><a href="https://en.wikipedia.org/wiki/Azimuthal_equidistant_projection">Azimuthal equidistant projection</a></li>
+ *   <li><a href="https://mathworld.wolfram.com/AzimuthalEquidistantProjection.html">Azimuthal Equidistant Projection</a></li>
+ * </ul>
+ *
+ * <h2>Description</h2>
+ * An approximation of the oblique form of the <cite>Azimuthal Equidistant</cite> projection.
+ * For relatively short distances (e.g. under 800 kilometres) this modification introduces no significant error.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ *
+ * @see AzimuthalEquidistant
+ *
+ * @since 1.1
+ * @module
+ */
+public class ModifiedAzimuthalEquidistant extends AzimuthalEquidistant {
+    /**
+     * For compatibility with different versions during deserialization.
+     */
+    private static final long serialVersionUID = 96569177715708509L;
+
+    /**
+     * A term involving radius of curvature ν₀, the latitude of origin φ₀ and the eccentricity.
+     * The semi-major axis length <var>a</var> is omitted since it is handled outside this class.
+     */
+    private final double ℯ2_ν0_sinφ0;
+
+    /**
+     * The ℯ⋅sin(φ₀)/√(1 − ℯ²) term, used in direct projection.
+     */
+    private final double G;
+
+    /**
+     * The ℯ⋅cos(φ₀)/√(1 − ℯ²) term. This is the <var>H</var> term in EPSG guidance notes
+     * but without the cos(α) term (omitted because α depends on the point to project).
+     *
+     * <p>Note that during inverse projection, EPSG guidance notes has a <var>A</var> as:
+     * −ℯ²⋅cos²φ₀/(1 − ℯ²)⋅cos²α. We opportunistically use Hp² for that purpose.</p>
+     */
+    private final double Hp;
+
+    /**
+     * The 3⋅ℯ²⋅sin(φ₀)⋅cos(φ₀)/(1 − ℯ²) term. This is the <var>B</var> term in EPSG guidance notes
+     * for inverse projection but without the terms that depend on coordinates of transformed point.
+     */
+    private final double Bp;
+
+    /**
+     * Work around for RFE #4093999 in Sun's bug database
+     * ("Relax constraint on placement of this()/super() call in constructors").
+     */
+    @Workaround(library="JDK", version="1.8")
+    private static Initializer initializer(final OperationMethod method, final Parameters parameters) {
+        final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new EnumMap<>(ParameterRole.class);
+        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, (byte) 0);
+    }
+
+    /**
+     * Creates a Modified Azimuthal Equidistant projection from the given parameters.
+     * The {@code method} argument can be the description of one of the following:
+     *
+     * <ul>
+     *   <li><cite>"Modified Azimuthal Equidistant"</cite>.</li>
+     * </ul>
+     *
+     * @param method      description of the projection parameters.
+     * @param parameters  the parameter values of the projection to create.
+     */
+    public ModifiedAzimuthalEquidistant(final OperationMethod method, final Parameters parameters) {
+        this(initializer(method, parameters));
+    }
+
+    /**
+     * Work around for RFE #4093999 in Sun's bug database
+     * ("Relax constraint on placement of this()/super() call in constructors").
+     */
+    @Workaround(library="JDK", version="1.8")
+    private ModifiedAzimuthalEquidistant(final Initializer initializer) {
+        super(initializer);
+        final double axisRatio, ν0, f;
+        axisRatio   = initializer.axisLengthRatio().doubleValue();
+        ν0          = initializer.radiusOfCurvature(sinφ0);
+        ℯ2_ν0_sinφ0 = eccentricitySquared * ν0 * sinφ0;
+        f           = eccentricity / axisRatio;                 // √(1 - ℯ²) = b/a
+        G           = f * sinφ0;
+        Hp          = f * cosφ0;
+        Bp          = 3*eccentricitySquared * (sinφ0*cosφ0) / (1 - eccentricitySquared);
+
+        final MatrixSIS denormalize = context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION);
+        denormalize.convertBefore(0, ν0, null);
+        denormalize.convertBefore(1, ν0, null);
+    }
+
+    /**
+     * Converts the specified (λ,φ) coordinate and stores the (<var>x</var>,<var>y</var>) result in {@code dstPts}.
+     *
+     * @return the matrix of the projection derivative at the given source position,
+     *         or {@code null} if the {@code derivate} argument is {@code false}.
+     * @throws ProjectionException if the coordinate can not be converted.
+     */
+    @Override
+    public Matrix transform(final double[] srcPts, final int srcOff,
+                            final double[] dstPts, final int dstOff,
+                            final boolean derivate) throws ProjectionException
+    {
+        final double λ     = srcPts[srcOff  ];
+        final double φ     = srcPts[srcOff+1];
+        final double cosλ  = cos(λ);
+        final double sinλ  = sin(λ);
+        final double cosφ  = cos(φ);
+        final double sinφ  = sin(φ);
+        final double rν    = sqrt(1 - eccentricitySquared*(sinφ*sinφ));
+        final double tanΨ  = ((1 - eccentricitySquared)*sinφ + ℯ2_ν0_sinφ0*rν) / cosφ;
+        final double rcosΨ = sqrt(1 + tanΨ*tanΨ);
+        final double α     = atan2(sinλ, cosφ0*tanΨ - sinφ0*cosλ);
+        final double sinα  = sin(α);
+        final double cosα  = cos(α);
+        final double H     = cosα * Hp;
+        /*
+         * Equations are:    s  =  asin(cos(φ₀)⋅sin(Ψ) − sin(φ₀)⋅cos(Ψ)) ⋅ signum(cos(α))     for small α
+         *                   s  =  asin(sin(λ)⋅cos(Ψ) / sin(α))                               for other α
+         *
+         * Using identity:   sin(atan(x))  =  x / √(1 + x²)
+         * Rewrite as:       sin(Ψ)  =   tan(Ψ) / √(1 + tan²Ψ)
+         */
+        double c;
+        if (abs(sinα) < ANGULAR_TOLERANCE) {
+            c = (cosφ0*tanΨ - sinφ0) / rcosΨ;
+            if (cosα < 0) c = -c;
+        } else {
+            c = sinλ / (rcosΨ * sinα);
+        }
+        c = asin(c);                    // After this line this is the `s` value in EPSG guidance notes.
+        final double s2 = c  * c;
+        final double s3 = s2 * c;
+        final double s4 = s2 * s2;
+        final double s5 = s4 * c;
+        final double H2 = H*H;
+        final double GH = G*H;
+        c *= 1 - (s2/6   *  H2*(1 -   H2))
+               + (s3/8   *  GH*(1 - 2*H2))
+               + (s4/120 * (H2*(4 - 7*H2) - 3*(G*G)*(1 - 7*H2)))
+               - (s5/48  * GH);
+
+        if (dstPts != null) {
+            dstPts[dstOff  ] = c * sinα;
+            dstPts[dstOff+1] = c * cosα;
+        }
+        if (!derivate) {
+            return null;
+        }
+        throw new ProjectionException("Derivative not yet implemented.");
+    }
+
+    /**
+     * Converts the specified (<var>x</var>,<var>y</var>) coordinates
+     * and stores the result in {@code dstPts} (angles in radians).
+     */
+    @Override
+    protected void inverseTransform(final double[] srcPts, final int srcOff,
+                                    final double[] dstPts, final int dstOff)
+            throws ProjectionException
+    {
+        final double x    = srcPts[srcOff  ];
+        final double y    = srcPts[srcOff+1];
+        final double α    = atan2(x, y);                // Actually α′ in EPSG guidance notes.
+        final double sinα = sin(α);
+        final double cosα = cos(α);
+              double negA = Hp * cosα; negA *= negA;    // mA = −A  compared to EPSG guidance note.
+        final double B    = Bp * (1 + negA) * cosα;
+        final double D2   = x*x + y*y;
+        final double D    = sqrt(D2);                   // D = c′/ν₀, but division by ν₀ is already done here.
+        final double J    = D + (negA*(1 -   negA)*(D2*D )/6)
+                              - (   B*(1 - 3*negA)*(D2*D2)/24);
+        final double J2   = J*J;
+        final double K    = 1 + (negA*J2/2) - (B*(J2*J)/6);
+        final double sinJ = sin(J);
+        final double sinΨ = sinφ0*cos(J) + cosφ0*sinJ*cosα;
+        final double cosΨ = sqrt(1 - sinΨ*sinΨ);
+        dstPts[dstOff  ]  = asin(sinα*sinJ / cosΨ);
+        dstPts[dstOff+1]  = atan((1 - eccentricitySquared*sinφ0*K / sinΨ) * (sinΨ/cosΨ)
+                               / (1 - eccentricitySquared));
+    }
+}
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 64adcd0..d443612 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
@@ -370,11 +370,11 @@ public class ObliqueStereographic extends NormalizedProjection {
         final double ψ = log((1 + sinχ) / ((1 - sinχ)*c)) / (2*n);
         double φ = 2*atan(exp(ψ)) - PI/2;                               // First approximation
         final double he = eccentricity/2;
-        final double me = 1 - eccentricitySquared;
+        final double ome = 1 - eccentricitySquared;
         for (int it=0; it<MAXIMUM_ITERATIONS; it++) {
             final double ℯsinφ = eccentricity * sin(φ);
             final double ψi = log(tan(φ/2 + PI/4) * pow((1 - ℯsinφ) / (1 + ℯsinφ), he));
-            final double Δφ = (ψ - ψi) * cos(φ) * (1 - ℯsinφ*ℯsinφ) / me;
+            final double Δφ = (ψ - ψi) * cos(φ) * (1 - ℯsinφ*ℯsinφ) / ome;
             φ += Δφ;
             if (!(abs(Δφ) > ITERATION_TOLERANCE)) {     // Use '!' for accepting NaN.
                 dstPts[dstOff  ] = λ;
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 d9ef536..b4c4bcd 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
@@ -227,6 +227,7 @@ public class Polyconic extends MeridianArcBased {
         final double y = srcPts[srcOff+1];
         double φ = y;                           // A = (M₀ + (N-FE)/a)      — Snyder 18-18 with M₀=0, FE=0 and a=1.
         final double B = y*y + x*x;             // B = A² + ((E-FE)²/a²)    — Snyder 18-19 with FE=0 and a=1.
+        final double ome = 1 - eccentricitySquared;
         int i = MAXIMUM_ITERATIONS;
         double dφ;
         do {
@@ -239,7 +240,7 @@ public class Polyconic extends MeridianArcBased {
             final double rν    = sqrt(1 - eccentricitySquared * sinφ2);
             final double C     = rν * sinφ/cosφ;
             final double M     = distance(φ, sinφ, cosφ);
-            final double Mp    = (1 - eccentricitySquared) + sinφ2*(ci2 + sinφ2*(ci4 + sinφ2*ci6));     // Derived from Snyder 18-17
+            final double Mp    = ome + sinφ2*(ci2 + sinφ2*(ci4 + sinφ2*ci6));     // Derived from Snyder 18-17
             final double M2B   = M*M + B;
             final double sin2φ = sin(2*φ);
             /*
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 ae5ddfe..11937c1 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
@@ -55,6 +55,8 @@ org.apache.sis.internal.referencing.provider.ObliqueMercatorCenter
 org.apache.sis.internal.referencing.provider.ObliqueMercatorTwoPoints
 org.apache.sis.internal.referencing.provider.ObliqueMercatorTwoPointsCenter
 org.apache.sis.internal.referencing.provider.Orthographic
+org.apache.sis.internal.referencing.provider.ModifiedAzimuthalEquidistant
+org.apache.sis.internal.referencing.provider.AzimuthalEquidistantSpherical
 org.apache.sis.internal.referencing.provider.ZonedTransverseMercator
 org.apache.sis.internal.referencing.provider.Sinusoidal
 org.apache.sis.internal.referencing.provider.Polyconic
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 12002e6..fd483a4 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
@@ -105,6 +105,8 @@ public final strictfp class ProvidersTest extends TestCase {
             ObliqueMercatorTwoPoints.class,
             ObliqueMercatorTwoPointsCenter.class,
             Orthographic.class,
+            ModifiedAzimuthalEquidistant.class,
+            AzimuthalEquidistantSpherical.class,
             ZonedTransverseMercator.class,
             SatelliteTracking.class,
             Sinusoidal.class,
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/AzimuthalEquidistantTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/AzimuthalEquidistantTest.java
new file mode 100644
index 0000000..c716b81
--- /dev/null
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/AzimuthalEquidistantTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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 org.opengis.util.FactoryException;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.internal.referencing.provider.MapProjection;
+import org.apache.sis.internal.referencing.Formulas;
+import org.apache.sis.test.DependsOn;
+import org.junit.Test;
+
+
+/**
+ * Tests the {@link AzimuthalEquidistant} class.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+@DependsOn(NormalizedProjectionTest.class)
+public strictfp class AzimuthalEquidistantTest extends MapProjectionTestCase {
+    /**
+     * Returns the method to be tested.
+     */
+    MapProjection method() {
+        return new org.apache.sis.internal.referencing.provider.AzimuthalEquidistantSpherical();
+    }
+
+    /**
+     * Tests the forward and inverse projection using test point given in Snyder page 337.
+     * The Snyder's test uses a sphere of radius R=3 and a center at 40°N and 100°W.
+     * The test in this class modify the longitude to 10°W for avoiding to mix wraparound
+     * considerations in this test.
+     *
+     * @throws FactoryException if an error occurred while creating the projection.
+     * @throws TransformException if an error occurred while projecting the test point.
+     */
+    @Test
+    public void testSpherical() throws FactoryException, TransformException {
+        createCompleteProjection(method(),
+                  3,            // Semi-major axis
+                  3,            // Semi-minor axis
+                -10,            // Longitude of natural origin (central-meridian)
+                 40,            // Latitude of natural origin
+                Double.NaN,     // Standard parallel 1
+                Double.NaN,     // Standard parallel 2
+                Double.NaN,     // Scale factor
+                  0,            // False easting
+                  0);           // False Northing
+
+        tolerance = 2E-7;
+        verifyTransform(new double[] {
+            -170,               // Was 1OO°E in Snyder test, shifted by 90° in our test.
+             -20                // 20°S
+        }, new double[] {
+            -5.8311398,
+             5.5444634
+        });
+    }
+
+    /**
+     * Tests with the point published in EPSG guidance note.
+     *
+     * @throws FactoryException if an error occurred while creating the projection.
+     * @throws TransformException if an error occurred while projecting the test point.
+     */
+    @Test
+    public void testWithEPSG() throws FactoryException, TransformException {
+        /*
+         * Since we are testing spherical formulas with a sample point calculated
+         * for ellipsoidal formulas, we have to use a high tolerance threshold.
+         */
+        tolerance = 20;
+        testWithEPSG(CLARKE_A, CLARKE_B);
+    }
+
+    /**
+     * Tests with the point published in EPSG guidance note.
+     * Callers must set {@link #tolerance} before to invoke this method.
+     *
+     * @param  semiMajor {@link #CLARKE_A}, or an alternative value if desired.
+     * @param  semiMinor {@link #CLARKE_B}, or an alternative value if desired.
+     * @throws FactoryException if an error occurred while creating the projection.
+     * @throws TransformException if an error occurred while projecting the test point.
+     */
+    final void testWithEPSG(final double semiMajor, final double semiMinor) throws FactoryException, TransformException {
+        createCompleteProjection(method(),
+                semiMajor,
+                semiMinor,
+                138 + (10 +  7.48/60)/60,       // Longitude of natural origin (central-meridian)
+                  9 + (32 + 48.15/60)/60,       // Latitude of natural origin
+                Double.NaN,                     // Standard parallel 1
+                Double.NaN,                     // Standard parallel 2
+                Double.NaN,                     // Scale factor
+                40000,                          // False easting
+                60000);                         // False Northing
+        /*
+         * Test point given in EPSG guidance note.
+         */
+        verifyTransform(new double[] {
+            138 + (11 + 34.908/60)/60,          // 138°11'34.908"E
+              9 + (35 + 47.493/60)/60           //   9°35'47.493"N
+        }, new double[] {
+            42665.90,
+            65509.82
+        });
+        /*
+         * North of map origin, for entering in the special case for c/sin(c)
+         * when c is close to zero. This point is not given by EPSG guidance
+         * notes; this is an anti-regression test.
+         */
+        verifyTransform(new double[] {
+            138 + (10 +  7.48/60)/60 + 0.00000000001,
+              9 + (32 + 48.15/60)/60 + 0.01
+        }, new double[] {
+            40000.00,
+            61105.98
+        });
+    }
+
+    /**
+     * Tests the derivatives at a few points on a sphere. This method compares the derivatives computed
+     * by the projection with an estimation of derivatives computed by the finite differences method.
+     *
+     * @throws FactoryException if an error occurred while creating the map projection.
+     * @throws TransformException if an error occurred while projecting a point.
+     */
+    @Test
+    public void testDerivative() throws FactoryException, TransformException {
+        createCompleteProjection(method(),
+                CLARKE_A,
+                CLARKE_B,
+                 40,                // Longitude of natural origin (central-meridian)
+                 25,                // Latitude of natural origin
+                Double.NaN,         // Standard parallel 1
+                Double.NaN,         // Standard parallel 2
+                Double.NaN,         // Scale factor
+                40000,              // False easting
+                60000);             // False Northing
+        final double delta = (1.0 / 60) / 1852;                 // Approximately 1 metre.
+        derivativeDeltas = new double[] {delta, delta};
+        tolerance = Formulas.LINEAR_TOLERANCE / 100;
+        verifyDerivative(30, 27);
+        verifyDerivative(27, 20);
+        verifyDerivative(40, 25);
+    }
+}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistantTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistantTest.java
new file mode 100644
index 0000000..ddca1c2
--- /dev/null
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistantTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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 org.opengis.util.FactoryException;
+import org.opengis.referencing.operation.TransformException;
+import org.apache.sis.internal.referencing.provider.MapProjection;
+import org.apache.sis.test.DependsOn;
+import org.junit.Test;
+
+
+/**
+ * Tests the {@link ModifiedAzimuthalEquidistant} class.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+@DependsOn(NormalizedProjectionTest.class)
+public final strictfp class ModifiedAzimuthalEquidistantTest extends AzimuthalEquidistantTest {
+    /**
+     * Returns the method to be tested.
+     */
+    @Override
+    MapProjection method() {
+        return new org.apache.sis.internal.referencing.provider.ModifiedAzimuthalEquidistant();
+    }
+
+    /**
+     * Tests the projection on a sphere. We override the method provides in parent class
+     * because the point provided in Snyder is too far from projection centre.
+     *
+     * @throws FactoryException if an error occurred while creating the projection.
+     * @throws TransformException if an error occurred while projecting the test point.
+     */
+    @Test
+    @Override
+    public void testSpherical() throws FactoryException, TransformException {
+        tolerance = 20;                     // Same tolerance than in parent class.
+        final double r = 6357767.51;        // Conformal sphere radius at the latitude being tested.
+        testWithEPSG(r, r);
+    }
+
+    /**
+     * Tests with the point published in EPSG guidance note.
+     *
+     * @throws FactoryException if an error occurred while creating the projection.
+     * @throws TransformException if an error occurred while projecting the test point.
+     */
+    @Test
+    @Override
+    public void testWithEPSG() throws FactoryException, TransformException {
+        tolerance = 0.01;
+        testWithEPSG(CLARKE_A, CLARKE_B);
+    }
+
+    /**
+     * Not yet supported.
+     */
+    @Test
+    @Override
+    @org.junit.Ignore("Implementation not yet completed")
+    public void testDerivative() throws FactoryException, TransformException {
+        // TODO
+    }
+}
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
index c89627d..3cafaa9 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
@@ -192,6 +192,8 @@ import org.junit.BeforeClass;
     org.apache.sis.referencing.operation.projection.PolyconicTest.class,
     org.apache.sis.referencing.operation.projection.MollweideTest.class,
     org.apache.sis.referencing.operation.projection.OrthographicTest.class,
+    org.apache.sis.referencing.operation.projection.AzimuthalEquidistantTest.class,
+    org.apache.sis.referencing.operation.projection.ModifiedAzimuthalEquidistantTest.class,
     org.apache.sis.referencing.operation.projection.SatelliteTrackingTest.class,
 
     // Coordinate operation and derived Coordinate Reference Systems (cyclic dependency).


Mime
View raw message