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 The following commit(s) were added to refs/heads/geoapi-4.0 by this push: new 33b6d27 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 33b6d27 is described below commit 33b6d27e2d5507e44d07656868298b6ae9289a3b Author: Martin Desruisseaux 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 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 "sinusoidal equal-area" projection. + * This projection has no associated EPSG code. + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.0 + * + * @see Sinusoidal projection on Wikipedia + * @see GeoTIFF parameters for Sinusoidal + * + * @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 Longitude of projection center (λ₀) parameter value. + * Valid values range is [-180 … 180]° and default value is 0°. + */ + public static final ParameterDescriptor CENTRAL_MERIDIAN = ESRI.CENTRAL_MERIDIAN; + + /** + * The operation parameter descriptor for the False easting (FE) parameter value. + * Valid values range is unrestricted and default value is 0 metre. + */ + public static final ParameterDescriptor FALSE_EASTING = ESRI.FALSE_EASTING; + + /** + * The operation parameter descriptor for the False northing (FN) parameter value. + * Valid values range is unrestricted and default value is 0 metre. + */ + public static final ParameterDescriptor 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..e323f04 --- /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.*; + + +/** + * Sinusoidal equal-area projection, also known as "Sanson-Flamsteed". + * See the Sinusoidal projection on Wikipedia + * 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> 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: + * + *
    + *
  • "Sinusoidal", also known as "Sanson-Flamsteed".
  • + *
+ * + * @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 normalization → {@code this} → denormalization transforms + * as a whole. The transform returned by this method expects (longitude, latitude) + * coordinates in degrees and returns (x,y) coordinates in metres. + * + *

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.

+ * + * @param factory the factory to use for creating the transform. + * @return the map projection from (λ,φ) to (x,y) 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 (x,y) 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).