This is an automated email from the ASF dual-hosted git repository. jsorel 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 e15aa86 Mollweide : add support for Mollweide operation e15aa86 is described below commit e15aa86a1bac6771d592866bb6a03d317ef25d80 Author: jsorel <johann.so...@geomatys.com> AuthorDate: Fri Jul 27 17:13:07 2018 +0200 Mollweide : add support for Mollweide operation --- .../internal/referencing/provider/Mollweide.java | 102 +++++++++++++++ .../operation/projection/Mollweide.java | 129 ++++++++++++++++++ ...g.opengis.referencing.operation.OperationMethod | 1 + .../referencing/provider/ProvidersTest.java | 1 + .../operation/projection/MollweideTest.java | 145 +++++++++++++++++++++ .../sis/test/suite/ReferencingTestSuite.java | 1 + 6 files changed, 379 insertions(+) diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mollweide.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mollweide.java new file mode 100644 index 0000000..72e3571 --- /dev/null +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Mollweide.java @@ -0,0 +1,102 @@ +/* + * 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 org.apache.sis.internal.util.Constants; +import org.apache.sis.parameter.ParameterBuilder; +import org.apache.sis.parameter.Parameters; +import org.apache.sis.referencing.operation.projection.NormalizedProjection; +import org.opengis.parameter.ParameterDescriptor; +import org.opengis.parameter.ParameterDescriptorGroup; +import org.opengis.parameter.ParameterNotFoundException; + +/** + * The provider for <cite>"Mollweide"</cite> projection. + * There are no EPSG projection using this operation. + * + * @see <a href="http://mathworld.wolfram.com/MollweideProjection.html">Mathworld formulas</a> + * + * @author Johann Sorel (Geomatys) + * @version 1.0 + * @since 1.0 + * @module + */ +public class Mollweide extends MapProjection { + + /** + * The operation parameter descriptor for the {@linkplain + * org.apache.sis.internal.util.Constants#centralMeridian + * central meridian} parameter value. + * + * This parameter is <a href="package-summary.html#Obligation">mandatory</a>. + * Valid values range is [-180 … 180]° and default value is 0°. + */ + public static final ParameterDescriptor<Double> CENTRAL_MERIDIAN; + + /** + * The operation parameter descriptor for the {@linkplain + * org.apache.sis.internal.util.Constants.Parameters#falseEasting + * false easting} parameter value. + * + * This parameter is <a href="package-summary.html#Obligation">mandatory</a>. + * Valid values range is unrestricted and default value is 0 metre. + */ + public static final ParameterDescriptor<Double> FALSE_EASTING; + + /** + * The operation parameter descriptor for the {@linkplain + * org.apache.sis.internal.util.Constants.Parameters#falseNorthing + * false northing} parameter value. + * + * This parameter is <a href="package-summary.html#Obligation">mandatory</a>. + * Valid values range is unrestricted and default value is 0 metre. + */ + public static final ParameterDescriptor<Double> FALSE_NORTHING; + + /** + * The group of all parameters expected by this coordinate operation. + */ + static final ParameterDescriptorGroup PARAMETERS; + /** + * Parameters creation, which must be done before to initialize the {@link #PARAMETERS} field. + * Note that the central Meridian and Latitude of Origin are shared with ObliqueStereographic. + */ + static { + final ParameterBuilder builder = new ParameterBuilder(); + + CENTRAL_MERIDIAN = createLongitude(builder.addName(Constants.CENTRAL_MERIDIAN)); + FALSE_EASTING = createShift(builder.addName(Constants.FALSE_EASTING)); + FALSE_NORTHING = createShift(builder.addName(Constants.FALSE_NORTHING)); + + PARAMETERS = new ParameterBuilder() + .addName("Mollweide") + .createGroupForMapProjection( + CENTRAL_MERIDIAN, + FALSE_EASTING, + FALSE_NORTHING); + } + + public Mollweide() { + super(PARAMETERS); + } + + @Override + protected NormalizedProjection createProjection(Parameters parameters) throws ParameterNotFoundException { + return new org.apache.sis.referencing.operation.projection.Mollweide(this, Parameters.castOrWrap(parameters)); + } + +} diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mollweide.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mollweide.java new file mode 100644 index 0000000..be97193 --- /dev/null +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mollweide.java @@ -0,0 +1,129 @@ +/* + * 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 static java.lang.Math.*; +import java.util.HashMap; +import java.util.Map; +import org.apache.sis.parameter.Parameters; +import org.apache.sis.referencing.operation.matrix.MatrixSIS; +import org.apache.sis.referencing.operation.transform.ContextualParameters; +import org.opengis.parameter.ParameterDescriptor; +import org.opengis.referencing.operation.Matrix; +import org.opengis.referencing.operation.OperationMethod; + +/** + * <cite>Mollweide</cite> projection. + * The Mollweide projection do not preserve angles,surfaces + * + * TODO : this transform causes issues with large envelopes, we need to have the + * information about tranform capability (bijective,subjective,...) + * to correctly choose an appropriate common CRS for intersection. + * + * @see <a href="http://mathworld.wolfram.com/MollweideProjection.html">Mathworld formulas</a> + * + * @author Johann Sorel (Geomatys) + * @version 1.0 + * @since 1.0 + * @module + */ +public class Mollweide extends NormalizedProjection{ + + private static final Map<ParameterRole, ParameterDescriptor<? extends Number>> ROLES = new HashMap<>(); + static { + ROLES.put(ParameterRole.CENTRAL_MERIDIAN, org.apache.sis.internal.referencing.provider.Mollweide.CENTRAL_MERIDIAN); + ROLES.put(ParameterRole.FALSE_EASTING, org.apache.sis.internal.referencing.provider.Mollweide.FALSE_EASTING); + ROLES.put(ParameterRole.FALSE_NORTHING, org.apache.sis.internal.referencing.provider.Mollweide.FALSE_NORTHING); + } + + private static final double SR2 = sqrt(2); + private static final double LAMDA_LIMIT = 2*SR2*PI; + + /** + * Constructs a new map projection from the supplied parameters. + * + * @param parameters The parameters of the projection to be created. + */ + public Mollweide(final OperationMethod method, final Parameters parameters) { + super(method, parameters, ROLES); + final MatrixSIS normalize = context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION); + final MatrixSIS denormalize = context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION); + normalize.convertBefore(0, 2*SR2, null); + denormalize.convertBefore(0, 1/PI, null); + denormalize.convertBefore(1, SR2, null); + } + + @Override + public Matrix transform(double[] srcPts, int srcOff, double[] dstPts, int dstOff, boolean derivate) throws ProjectionException { + if (derivate) { + //TODO + throw new ProjectionException("Derivation not supported"); + } + + final double λ = srcPts[srcOff]; //longitude + final double φ = srcPts[srcOff + 1]; //latitude + double primeθ = 2 * asin( (2 * φ) / PI ); + + final double sinφ = sin(φ); + /* + if sinφ is 1 or -1 we are on a pole. + iteration would produce NaN values. + */ + if (abs(sinφ) != 1) { + final double pisinφ = PI * sinφ; + int nbIte = MAXIMUM_ITERATIONS; + double deltaθ = Double.MAX_VALUE; + do { + if (--nbIte < 0) { + throw new ProjectionException("Operation does not converge"); + } + deltaθ = - (primeθ + sin(primeθ) - pisinφ) / (1 + cos(primeθ)); + primeθ += deltaθ; + } while (abs(deltaθ) > ITERATION_TOLERANCE); + } + final double θ = primeθ * 0.5; + + final double x = λ * cos(θ); + final double y = sin(θ); + + dstPts[dstOff ] = x; + dstPts[dstOff + 1] = y; + + Matrix matrix = null; + return matrix; + } + + @Override + protected void inverseTransform(double[] srcPts, int srcOff, double[] dstPts, int dstOff) throws ProjectionException { + + final double x = srcPts[srcOff]; + final double y = srcPts[srcOff + 1]; + final double θ = asin(y); + + final double θθ = 2 * θ; + final double φ = asin( (θθ + sin(θθ)) / PI ); + double λ = x / cos(θ); + + if (abs(λ) > LAMDA_LIMIT) { + λ = Double.NaN; + } + + dstPts[dstOff] = λ; + dstPts[dstOff+1] = φ; + } + +} 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 77628da..09c1df7 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,3 +55,4 @@ org.apache.sis.internal.referencing.provider.NTv2 org.apache.sis.internal.referencing.provider.NADCON org.apache.sis.internal.referencing.provider.FranceGeocentricInterpolation org.apache.sis.internal.referencing.provider.Interpolation1D +org.apache.sis.internal.referencing.provider.Mollweide 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 4b6eb94..14b5cb1 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 @@ -82,6 +82,7 @@ public final strictfp class ProvidersTest extends TestCase { PseudoMercator.class, RegionalMercator.class, MillerCylindrical.class, + Mollweide.class, LambertConformal1SP.class, LambertConformal2SP.class, LambertConformalWest.class, diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MollweideTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MollweideTest.java new file mode 100644 index 0000000..9c7bf32 --- /dev/null +++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MollweideTest.java @@ -0,0 +1,145 @@ +/* + * 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.apache.sis.internal.system.DefaultFactories; +import org.apache.sis.internal.util.Constants; +import org.apache.sis.parameter.Parameters; +import org.apache.sis.test.DependsOn; +import org.apache.sis.test.TestCase; +import static org.junit.Assert.*; +import org.junit.Test; +import org.opengis.parameter.ParameterNotFoundException; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.MathTransformFactory; +import org.opengis.referencing.operation.TransformException; +import org.opengis.util.FactoryException; + +/** + * Tests for Mollveide transform. + * + * @author Johann Sorel (Geomatys) + * @version 1.0 + * @since 1.0 + * @module + */ +@DependsOn(NormalizedProjectionTest.class) +public class MollweideTest extends TestCase { + + private double tolerance = 0.00001; + + public MollweideTest() { + } + + @Test + public void testTransform() throws TransformException, ParameterNotFoundException, FactoryException { + final org.apache.sis.internal.referencing.provider.Mollweide provider = new org.apache.sis.internal.referencing.provider.Mollweide(); + final Parameters parameters = Parameters.castOrWrap(provider.getParameters().createValue()); + parameters.parameter(Constants.CENTRAL_MERIDIAN).setValue(0.0); + parameters.parameter(Constants.FALSE_EASTING).setValue(0.0); + parameters.parameter(Constants.FALSE_NORTHING).setValue(0.0); + parameters.parameter(Constants.SEMI_MAJOR).setValue(6378137); + parameters.parameter(Constants.SEMI_MINOR).setValue(6378137); + + + final MathTransform trs = provider.createMathTransform(DefaultFactories.forClass(MathTransformFactory.class), parameters); + final MathTransform invtrs = trs.inverse(); + + final double[] in = new double[2]; + final double[] out = new double[2]; + + // at (0,0) point should be unchanged + in[0] = 0.0; + in[1] = 0.0; + trs.transform(in, 0, out, 0, 1); + assertEquals(0.0, out[0], tolerance); + assertEquals(0.0, out[1], tolerance); + + // at (0,±90) north/south poles singularity + in[0] = 0.0; + in[1] = 90; + trs.transform(in, 0, out, 0, 1); + assertEquals(0.0, out[0], tolerance); + assertEquals(9020047.848073645, out[1], tolerance); + invtrs.transform(out, 0, in, 0, 1); + assertEquals(0.0, in[0], tolerance); + assertEquals(90.0, in[1], tolerance); + + in[0] = 0.0; + in[1] = -90; + trs.transform(in, 0, out, 0, 1); + assertEquals(0.0, out[0], tolerance); + assertEquals(-9020047.848073645, out[1], tolerance); + invtrs.transform(out, 0, in, 0, 1); + assertEquals(0.0, in[0], tolerance); + assertEquals(-90.0, in[1], tolerance); + + // at (0,~90) point near north pole singularity should be close to ~9.000.000 + in[0] = 0.0; + in[1] = 89; + trs.transform(in, 0, out, 0, 1); + assertEquals(0.0, out[0], tolerance); + assertEquals(8997266.89915323, out[1], tolerance); + invtrs.transform(out, 0, in, 0, 1); + assertEquals(0.0, in[0], tolerance); + assertEquals(89.0, in[1], tolerance); + + //other random points + //compared to epsg.io (Bad reference, find something more trustable) + //https://epsg.io/transform#s_srs=4326&t_srs=54009&x=-150.0000000&y=-70.0000000 + in[0] = 12.0; + in[1] = 50.0; + trs.transform(in, 0, out, 0, 1); + assertEquals(912759.82345261, out[0], tolerance); + assertEquals(5873471.95621065, out[1], tolerance); + invtrs.transform(out, 0, in, 0, 1); + assertEquals(12.0, in[0], tolerance); + assertEquals(50.0, in[1], tolerance); + + + in[0] = -150.0; + in[1] = -70.0; + trs.transform(in, 0, out, 0, 1); + assertEquals(-7622861.35718471, out[0], tolerance); + assertEquals(-7774469.60789149, out[1], tolerance); + invtrs.transform(out, 0, in, 0, 1); + assertEquals(-150.0, in[0], tolerance); + assertEquals(-70.0, in[1], tolerance); + + in[0] = -179.9999; + in[1] = 0.0; + trs.transform(in, 0, out, 0, 1); + assertEquals(-18040085.67387191, out[0], tolerance); + assertEquals(0.0, out[1], tolerance); + invtrs.transform(out, 0, in, 0, 1); + assertEquals(-179.9999, in[0], tolerance); + assertEquals(0.0, in[1], tolerance); + + //outside of validity area, should have NaN with the reverse transform + in[0] = -180.0001; + in[1] = 0.0; + trs.transform(in, 0, out, 0, 1); + assertEquals(-1.8040105718422677E7, out[0], tolerance); + assertEquals(0.0, out[1], tolerance); + invtrs.transform(out, 0, in, 0, 1); + assertEquals(Double.NaN, in[0], tolerance); + assertEquals(0.0, in[1], tolerance); + + + } + +} 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 9e395d9..c63c51e 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 @@ -171,6 +171,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.MollweideTest.class, // Coordinate operation and derived Coordinate Reference Systems (cyclic dependency). org.apache.sis.referencing.operation.DefaultTransformationTest.class,