sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 01/01: First draft of "Sinusoidal equal-area" projection, also known as "Sanson-Flamsteed". This commit put only the spherical formulas. Ellipsoidal formulas will come later. https://issues.apache.org/jira/browse/SIS-450
Date Sat, 20 Apr 2019 21:34:26 GMT
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 14efbad156e1ab89269e271755f0276d51c41953
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Sat Apr 20 23:29:14 2019 +0200

    First draft of "Sinusoidal equal-area" projection, also known as "Sanson-Flamsteed".
    This commit put only the spherical formulas. Ellipsoidal formulas will come later.
    https://issues.apache.org/jira/browse/SIS-450
---
 .../referencing/provider/MercatorSpherical.java    |   2 +-
 .../internal/referencing/provider/Sinusoidal.java  |  97 +++++++++
 .../operation/projection/Sinusoidal.java           | 221 +++++++++++++++++++++
 ...g.opengis.referencing.operation.OperationMethod |   1 +
 .../referencing/provider/ProvidersTest.java        |   1 +
 .../operation/projection/SinusoidalTest.java       | 109 ++++++++++
 .../sis/test/suite/ReferencingTestSuite.java       |   1 +
 7 files changed, 431 insertions(+), 1 deletion(-)

diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MercatorSpherical.java
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MercatorSpherical.java
index 481eeef..64cd5f5 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MercatorSpherical.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/MercatorSpherical.java
@@ -58,7 +58,7 @@ public final class MercatorSpherical extends AbstractMercator {
          * Visualisation CRS / Mercator", now deprecated). But at the difference of what
we did in Mercator2SP, we keep
          * the EPSG name here since there is no "standard parallel" parameter.  So the "Scale
factor at natural origin"
          * parameter name still okay provided that the "Latitude of natural origin" parameter
value stay zero (which is
-         * normally enforced by the Mercator1SP.LATITUDE_OF_ORIGIN minimum and maximum values).
+         * enforced by the Mercator1SP.LATITUDE_OF_ORIGIN minimum and maximum values).
          */
         final ParameterDescriptor<Double> scaleFactor = createScale(builder
                 .addNamesAndIdentifiers(Mercator1SP.SCALE_FACTOR)
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Sinusoidal.java
b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Sinusoidal.java
new file mode 100644
index 0000000..9590bc7
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Sinusoidal.java
@@ -0,0 +1,97 @@
+/*
+ * 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.parameter.ParameterNotFoundException;
+import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.internal.util.Constants;
+import org.apache.sis.parameter.Parameters;
+import org.apache.sis.referencing.operation.projection.NormalizedProjection;
+
+
+/**
+ * The provider for <cite>"sinusoidal equal-area"</cite> projection.
+ * This projection has no associated EPSG code.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ *
+ * @see <a href="https://en.wikipedia.org/wiki/Sinusoidal_projection">Sinusoidal projection
on Wikipedia</a>
+ * @see <a href="http://geotiff.maptools.org/proj_list/sinusoidal.html">GeoTIFF parameters
for Sinusoidal</a>
+ *
+ * @since 1.0
+ * @module
+ */
+@XmlTransient
+public final class Sinusoidal extends MapProjection {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -3236247448683326299L;
+
+    /**
+     * The operation parameter descriptor for the <cite>Longitude of projection center</cite>
(λ₀) parameter value.
+     * Valid values range is [-180 … 180]° and default value is 0°.
+     */
+    public static final ParameterDescriptor<Double> CENTRAL_MERIDIAN = ESRI.CENTRAL_MERIDIAN;
+
+    /**
+     * The operation parameter descriptor for the <cite>False easting</cite>
(FE) parameter value.
+     * Valid values range is unrestricted and default value is 0 metre.
+     */
+    public static final ParameterDescriptor<Double> FALSE_EASTING = ESRI.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.
+     */
+    public static final ParameterDescriptor<Double> FALSE_NORTHING = ESRI.FALSE_NORTHING;
+
+    /**
+     * The group of all parameters expected by this coordinate operation.
+     */
+    private static final ParameterDescriptorGroup PARAMETERS;
+    static {
+        PARAMETERS = builder().setCodeSpace(Citations.OGC, Constants.OGC)
+                .addName      ("Sinusoidal")
+                .addName      ("Sanson-Flamsteed")
+                .addName      (Citations.GEOTIFF,  "CT_Sinusoidal")
+                .addIdentifier(Citations.GEOTIFF,  "24")
+                .addName      (Citations.PROJ4,    "sinu")
+                .createGroupForMapProjection(CENTRAL_MERIDIAN, FALSE_EASTING, FALSE_NORTHING);
+    }
+
+    /**
+     * Constructs a new provider.
+     */
+    public Sinusoidal() {
+        super(PARAMETERS);
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return the map projection created from the given parameter values.
+     */
+    @Override
+    protected NormalizedProjection createProjection(final Parameters parameters) throws ParameterNotFoundException
{
+        return new org.apache.sis.referencing.operation.projection.Sinusoidal(this, parameters);
+    }
+}
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
new file mode 100644
index 0000000..fc94ef6
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Sinusoidal.java
@@ -0,0 +1,221 @@
+/*
+ * 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.util.FactoryException;
+import org.opengis.parameter.ParameterDescriptor;
+import org.opengis.referencing.operation.Matrix;
+import org.opengis.referencing.operation.OperationMethod;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.MathTransformFactory;
+import org.opengis.referencing.operation.TransformException;
+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.Sinusoidal.*;
+
+
+/**
+ * <cite>Sinusoidal equal-area</cite> projection, also known as <cite>"Sanson-Flamsteed"</cite>.
+ * See the <a href="https://en.wikipedia.org/wiki/Sinusoidal_projection">Sinusoidal
projection on Wikipedia</a>
+ * for an overview.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+public class Sinusoidal extends NormalizedProjection {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 7908925241331303236L;
+
+    /**
+     * 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, CENTRAL_MERIDIAN);
+        roles.put(ParameterRole.FALSE_EASTING,    FALSE_EASTING);
+        roles.put(ParameterRole.FALSE_NORTHING,   FALSE_NORTHING);
+        return new Initializer(method, parameters, roles, (byte) 0);
+    }
+
+    /**
+     * Creates a Sinusoidal projection from the given parameters.
+     * The {@code method} argument can be the description of one of the following:
+     *
+     * <ul>
+     *   <li><cite>"Sinusoidal"</cite>, also known as <cite>"Sanson-Flamsteed"</cite>.</li>
+     * </ul>
+     *
+     * @param method      description of the projection parameters.
+     * @param parameters  the parameter values of the projection to create.
+     */
+    public Sinusoidal(final OperationMethod method, final Parameters parameters) {
+        super(initializer(method, parameters));
+    }
+
+    /**
+     * Creates a new projection initialized to the same parameters than the given one.
+     */
+    Sinusoidal(final Sinusoidal other) {
+        super(other);
+    }
+
+    /**
+     * Returns the sequence of <cite>normalization</cite> → {@code this} →
<cite>denormalization</cite> transforms
+     * as a whole. The transform returned by this method expects (<var>longitude</var>,
<var>latitude</var>)
+     * coordinates in <em>degrees</em> and returns (<var>x</var>,<var>y</var>)
coordinates in <em>metres</em>.
+     *
+     * <p>The non-linear part of the returned transform will be {@code this} transform,
except if the ellipsoid
+     * is spherical. In the later case, {@code this} transform will be replaced by a simplified
implementation.</p>
+     *
+     * @param  factory  the factory to use for creating the transform.
+     * @return the map projection from (λ,φ) to (<var>x</var>,<var>y</var>)
coordinates.
+     * @throws FactoryException if an error occurred while creating a transform.
+     */
+    @Override
+    public MathTransform createMapProjection(final MathTransformFactory factory) throws FactoryException
{
+        Sinusoidal kernel = this;
+        if (eccentricity == 0) {
+            kernel = new Spherical(this);
+        }
+        return context.completeTransform(factory, kernel);
+    }
+
+    /**
+     * Converts the specified (λ,φ) coordinate (units in radians) and stores the result
in {@code dstPts}
+     * (linear distance on a unit sphere). In addition, opportunistically computes the projection
derivative
+     * if {@code derivate} is {@code true}.
+     *
+     * @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(φ);
+        // TODO: replace by ellipsoidal formulas.
+        if (dstPts != null) {
+            dstPts[dstOff]   = λ * cosφ;
+            dstPts[dstOff+1] = φ;
+        }
+        return derivate ? new Matrix2(cosφ, -λ*sin(φ), 0, 1) : null;
+    }
+
+    /**
+     * 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)
+    {
+        final double x = srcPts[srcOff  ];
+        final double φ = srcPts[srcOff+1];
+        // TODO: replace by ellipsoidal formulas.
+        dstPts[dstOff  ] = x / cos(φ);
+        dstPts[dstOff+1] = φ;
+    }
+
+
+    /**
+     * Provides the transform equations for the spherical case of the Sinusoidal projection.
+     *
+     * @author  Martin Desruisseaux (Geomatys)
+     * @version 1.0
+     * @since   1.0
+     * @module
+     */
+    private static final class Spherical extends Sinusoidal {
+        /**
+         * For cross-version compatibility.
+         */
+        private static final long serialVersionUID = -5843301120207230310L;
+
+        /**
+         * Constructs a new map projection from the parameters of the given projection.
+         *
+         * @param  other  the other projection (usually ellipsoidal) from which to copy the
parameters.
+         */
+        Spherical(final Sinusoidal other) {
+            super(other);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @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(φ);
+            if (dstPts != null) {
+                dstPts[dstOff  ] = λ * cosφ;            // Part of Snyder 30-1
+                dstPts[dstOff+1] = φ;                   // Part of Snyder 30-2
+            }
+            return derivate ? new Matrix2(cosφ, -λ*sin(φ), 0, 1) : null;
+        }
+
+        /**
+         * Converts a list of coordinate points. This method performs the same calculation
than above
+         * {@link #transform(double[], int, double[], int, boolean)} method, but is overridden
for efficiency.
+         */
+        @Override
+        public void transform(final double[] srcPts, int srcOff,
+                              final double[] dstPts, int dstOff, int numPts)
+                throws TransformException
+        {
+            if (srcPts != dstPts || srcOff != dstOff) {
+                super.transform(srcPts, srcOff, dstPts, dstOff, numPts);
+            } else {
+                while (--numPts >= 0) {
+                    dstPts[dstOff] *= cos(dstPts[dstOff+1]);
+                    dstOff += DIMENSION;
+                }
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        protected void inverseTransform(final double[] srcPts, final int srcOff,
+                                        final double[] dstPts, final int dstOff)
+        {
+            final double x = srcPts[srcOff  ];
+            final double φ = srcPts[srcOff+1];
+            dstPts[dstOff  ] = x / cos(φ);              // Part of Snyder 30-5
+            dstPts[dstOff+1] = φ;                       // Part of Snyder 30-6
+        }
+    }
+}
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 9e2aa85..7868784 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,7 @@ 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.ZonedTransverseMercator
+org.apache.sis.internal.referencing.provider.Sinusoidal
 org.apache.sis.internal.referencing.provider.Mollweide
 org.apache.sis.internal.referencing.provider.NTv2
 org.apache.sis.internal.referencing.provider.NADCON
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 b544c6e..64e2d69 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
@@ -83,6 +83,7 @@ public final strictfp class ProvidersTest extends TestCase {
             PseudoMercator.class,
             RegionalMercator.class,
             MillerCylindrical.class,
+            Sinusoidal.class,
             Mollweide.class,
             LambertConformal1SP.class,
             LambertConformal2SP.class,
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/SinusoidalTest.java
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/SinusoidalTest.java
new file mode 100644
index 0000000..acd8869
--- /dev/null
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/SinusoidalTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.referencing.operation.TransformException;
+import org.opengis.util.FactoryException;
+import org.apache.sis.internal.referencing.Formulas;
+import org.apache.sis.test.DependsOnMethod;
+import org.apache.sis.test.DependsOn;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests the {@link Sinusoidal} projection.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+@DependsOn(NormalizedProjectionTest.class)
+public final strictfp class SinusoidalTest extends MapProjectionTestCase {
+    /**
+     * Creates a new instance of {@link Sinusoidal} concatenated with the (de)normalization
matrices.
+     * The new instance is stored in the inherited {@link #transform} field.
+     *
+     * @param  ellipse  {@code false} for a sphere, or {@code true} for WGS84 ellipsoid.
+     */
+    private void createProjection(final boolean ellipse) throws FactoryException {
+        createCompleteProjection(new org.apache.sis.internal.referencing.provider.Sinusoidal(),
+                ellipse ? WGS84_A : 6400000,
+                ellipse ? WGS84_B : 6400000,
+                Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN);
+        tolerance = Formulas.LINEAR_TOLERANCE;  // Not NORMALIZED_TOLERANCE since this is
not a NormalizedProjection.
+    }
+
+    /**
+     * Tests the projection of a few points on a sphere.
+     *
+     * @throws FactoryException if an error occurred while creating the map projection.
+     * @throws TransformException if an error occurred while projecting a point.
+     */
+    @Test
+    public void testSpherical() throws FactoryException, TransformException {
+        createProjection(false);
+        assertTrue(isInverseTransformSupported);
+        verifyTransform(
+            new double[] {              // (λ,φ) coordinates in degrees to project.
+                  2,      1
+            },
+            new double[] {              // Expected (x,y) results in metres.
+               223368.12, 111701.07     // Values taken from PROJ.4.
+            });
+    }
+
+    /**
+     * Tests the projection of a few points on an ellipsoid.
+     *
+     * @throws FactoryException if an error occurred while creating the map projection.
+     * @throws TransformException if an error occurred while projecting a point.
+     */
+    @Test
+    @org.junit.Ignore("Ellipsoidal formula not yet implemented.")
+    public void testEllipsoidal() throws FactoryException, TransformException {
+        createProjection(true);
+        assertTrue(isInverseTransformSupported);
+        verifyTransform(
+            new double[] {              // (λ,φ) coordinates in degrees to project.
+                  2,      1
+            },
+            new double[] {              // Expected (x,y) results in metres.
+               222605.30, 110574.39     // Values taken from PROJ.4.
+            });
+    }
+
+    /**
+     * Tests the derivatives at a few points. 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
+    @DependsOnMethod("testInverseDerivative")
+    public void testDerivative() throws FactoryException, TransformException {
+        createProjection(false);
+        final double delta = (100.0 / 60) / 1852;               // Approximatively 100 metres.
+        derivativeDeltas = new double[] {delta, delta};
+        tolerance = 1E-6;                                       // More severe than Formulas.LINEAR_TOLERANCE.
+        verifyDerivative(15,  30);
+        verifyDerivative(10, -60);
+    }
+}
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 040e08d..972fcdb 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
@@ -175,6 +175,7 @@ import org.junit.BeforeClass;
     org.apache.sis.referencing.operation.projection.ObliqueMercatorTest.class,
     org.apache.sis.referencing.operation.projection.CylindricalEqualAreaTest.class,
     org.apache.sis.referencing.operation.projection.AlbersEqualAreaTest.class,
+    org.apache.sis.referencing.operation.projection.SinusoidalTest.class,
     org.apache.sis.referencing.operation.projection.MollweideTest.class,
 
     // Coordinate operation and derived Coordinate Reference Systems (cyclic dependency).


Mime
View raw message