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 a65df89e7f Add Robinson projection. https://issues.apache.org/jira/browse/SIS-599 a65df89e7f is described below commit a65df89e7f226c5bf21479cb4a072f4386f66dca Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Thu May 2 19:15:20 2024 +0200 Add Robinson projection. https://issues.apache.org/jira/browse/SIS-599 --- ...g.opengis.referencing.operation.OperationMethod | 1 + .../main/module-info.java | 1 + .../operation/projection/Initializer.java | 7 +- .../operation/projection/Mollweide.java | 11 - .../operation/projection/ProjectionVariant.java | 19 +- .../referencing/operation/projection/Robinson.java | 264 +++++++++++++++++++++ .../operation/projection/Sinusoidal.java | 5 - .../referencing/operation/provider/Mollweide.java | 2 +- .../provider/{Mollweide.java => Robinson.java} | 32 ++- .../operation/transform/ContextualParameters.java | 17 +- .../operation/projection/RobinsonTest.java | 118 +++++++++ .../operation/provider/ProvidersTest.java | 1 + 12 files changed, 434 insertions(+), 44 deletions(-) diff --git a/endorsed/src/org.apache.sis.referencing/main/META-INF/services/org.opengis.referencing.operation.OperationMethod b/endorsed/src/org.apache.sis.referencing/main/META-INF/services/org.opengis.referencing.operation.OperationMethod index 9609787f4e..ae50990dda 100644 --- a/endorsed/src/org.apache.sis.referencing/main/META-INF/services/org.opengis.referencing.operation.OperationMethod +++ b/endorsed/src/org.apache.sis.referencing/main/META-INF/services/org.opengis.referencing.operation.OperationMethod @@ -66,6 +66,7 @@ org.apache.sis.referencing.operation.provider.Sinusoidal org.apache.sis.referencing.operation.provider.PseudoSinusoidal org.apache.sis.referencing.operation.provider.Polyconic org.apache.sis.referencing.operation.provider.Mollweide +org.apache.sis.referencing.operation.provider.Robinson org.apache.sis.referencing.operation.provider.SouthPoleRotation org.apache.sis.referencing.operation.provider.NorthPoleRotation org.apache.sis.referencing.operation.provider.NTv2 diff --git a/endorsed/src/org.apache.sis.referencing/main/module-info.java b/endorsed/src/org.apache.sis.referencing/main/module-info.java index d79c190aac..c19d16bee7 100644 --- a/endorsed/src/org.apache.sis.referencing/main/module-info.java +++ b/endorsed/src/org.apache.sis.referencing/main/module-info.java @@ -133,6 +133,7 @@ module org.apache.sis.referencing { org.apache.sis.referencing.operation.provider.PseudoSinusoidal, org.apache.sis.referencing.operation.provider.Polyconic, org.apache.sis.referencing.operation.provider.Mollweide, + org.apache.sis.referencing.operation.provider.Robinson, org.apache.sis.referencing.operation.provider.SouthPoleRotation, org.apache.sis.referencing.operation.provider.NorthPoleRotation, org.apache.sis.referencing.operation.provider.NTv2, diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Initializer.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Initializer.java index 2492127180..6b4535f28c 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Initializer.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Initializer.java @@ -195,7 +195,12 @@ final class Initializer { * Set meridian rotation, scale factor, false easting and false northing parameter values * in the (de)normalization matrices. */ - context.normalizeGeographicInputs(λ0); + if (variant == null || variant.useRadians()) { + context.normalizeGeographicInputs(λ0); + } else if (λ0 != 0) { + context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION) + .convertBefore(0, null, DoubleDouble.of(-λ0, true)); + } final MatrixSIS denormalize = context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION); denormalize.convertAfter(0, k, DoubleDouble.of(fe, true)); denormalize.convertAfter(1, k, DoubleDouble.of(fn, true)); diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Mollweide.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Mollweide.java index 16de7b69f7..845db2afba 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Mollweide.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Mollweide.java @@ -17,7 +17,6 @@ package org.apache.sis.referencing.operation.projection; import java.util.EnumMap; -import java.util.regex.Pattern; import static java.lang.Math.*; import org.opengis.parameter.ParameterDescriptor; import org.opengis.referencing.operation.Matrix; @@ -64,16 +63,6 @@ public class Mollweide extends NormalizedProjection { /** The spherical case. */ SPHERICAL; - /** The expected name pattern of an operation method for this variant. */ - @Override public Pattern getOperationNamePattern() { - return null; - } - - /** EPSG identifier of an operation method for this variant. */ - @Override public String getIdentifier() { - return null; - } - /** Requests the use of authalic radius. */ @Override public boolean useAuthalicRadius() { return true; diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/ProjectionVariant.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/ProjectionVariant.java index 76be73266b..61474e2059 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/ProjectionVariant.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/ProjectionVariant.java @@ -32,7 +32,9 @@ interface ProjectionVariant { * * @return the operation name pattern for this variant, or {@code null} if none. */ - Pattern getOperationNamePattern(); + default Pattern getOperationNamePattern() { + return null; + } /** * Returns the EPSG identifier to compare against the operation method. @@ -40,7 +42,9 @@ interface ProjectionVariant { * * @return EPSG identifier for this variant, or {@code null} if none. */ - String getIdentifier(); + default String getIdentifier() { + return null; + } /** * Whether this variant is a spherical variant using authalic radius. @@ -58,4 +62,15 @@ interface ProjectionVariant { default boolean useAuthalicRadius() { return false; } + + /** + * Whether this variant uses longitude and latitude values in radians. + * This is the case of almost all map projections. + * A value of {@code false} will cause the map projection to work in degrees instead. + * + * @return whether this variant uses longitude and latitude values in radians. + */ + default boolean useRadians() { + return true; + } } diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Robinson.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Robinson.java new file mode 100644 index 0000000000..4d51ebd276 --- /dev/null +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Robinson.java @@ -0,0 +1,264 @@ +/* + * 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 static java.lang.Math.*; +import org.opengis.parameter.ParameterDescriptor; +import org.opengis.referencing.operation.Matrix; +import org.opengis.referencing.operation.OperationMethod; +import org.apache.sis.parameter.Parameters; +import org.apache.sis.math.Fraction; +import org.apache.sis.util.Workaround; +import org.apache.sis.util.privy.DoubleDouble; +import org.apache.sis.referencing.internal.Resources; +import org.apache.sis.referencing.operation.matrix.Matrix2; +import org.apache.sis.referencing.operation.matrix.MatrixSIS; +import org.apache.sis.referencing.operation.transform.ContextualParameters; +import static org.apache.sis.referencing.operation.provider.Robinson.*; + + +/** + * <cite>Robinson</cite> projection. + * This projection is different than other projections in that it computes + * by interpolations of tabulated values instead of analytic function. + * The table is indexed by latitude at a constant interval. + * + * <p>While the current implementation supports only the Robinson projection, + * it could be generalized to any projection using interpolations in a similar way. + * For example, the "Natural Earth" projection was initially defined by interpolations. + * See {@link Variant} for a note about how to generalize.</p> + * + * <h2>References</h2> + * <p>Snyder, J. P. (1990). <u>The Robinson projection: A computation algorithm.</u> + * Cartography and Geographic Information Systems, 17 (4), p. 301-305.</p> + * + * @author Martin Desruisseaux (Geomatys) + * + * @see <a href="https://en.wikipedia.org/wiki/Robinson_projection">Robinson projection on Wikipedia</a> + */ +public class Robinson extends NormalizedProjection { + /** + * For cross-version compatibility. + */ + private static final long serialVersionUID = -2998244461334786203L; + + /** + * The projection variants supported by the enclosing class. + * + * <h2>Future evolution</h2> + * If there is a need to support other interpolated projection than Robinson, then the {@link #TABLE}, + * {@link #LAST_INDEX} and {@link #LATITUDE_INCREMENT} constants should move in this enumeration. + */ + private enum Variant implements ProjectionVariant { + /** The Robinson projection. */ + ROBINSON; + + /** Requests the use of authalic radius. */ + @Override public boolean useAuthalicRadius() { + return true; + } + + /** Requests the use of degrees. */ + @Override public boolean useRadians() { + return false; + } + } + + /** + * 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, Variant.ROBINSON); + } + + /** + * Increment in degrees between two rows of the interpolation table. + * + * @see #TABLE + */ + private static final int LATITUDE_INCREMENT = 5; + + /** + * Conversion factor from latitude in degrees to index in the interpolation table. + */ + private static final Fraction TO_INDEX = new Fraction(1, LATITUDE_INCREMENT); + + /** + * Multiplication factors of <var>x</var> and <var>y</var> axes after projection. + */ + private static final Fraction XF = new Fraction( 8487, 10000), // 0.8487 + YF = new Fraction(13523, 10000); // 1.3523 + + /** + * Creates a Robinson projection from the given parameters. + * + * @param method description of the projection parameters. + * @param parameters the parameter values of the projection to create. + */ + public Robinson(final OperationMethod method, final Parameters parameters) { + super(initializer(method, parameters), null); + final MatrixSIS normalize = context.getMatrix(ContextualParameters.MatrixRole.NORMALIZATION); + final MatrixSIS denormalize = context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION); + normalize .convertAfter (0, DoubleDouble.DEGREES_TO_RADIANS, null); + normalize .convertAfter (1, TO_INDEX, null); + denormalize.convertBefore(0, XF, null); + denormalize.convertBefore(1, YF, null); + } + + /** + * Interpolation table from 5°S to 90°N inclusive with a step of 5° of latitude. + * The first column (XLR) is the ratio of the length of the parallel to the length of the equator. + * The second column (PR) is proportional to the distance of from the equator to the parallel + * divided by the equator length. + */ + private static final double[] TABLE = { + // XLR PR + 0.9986, -0.0620, // -5° + 1.0000, 0.0000, // 0° + 0.9986, 0.0620, // 5° + 0.9954, 0.1240, // 10° + 0.9900, 0.1860, // 15° + 0.9822, 0.2480, // 20° + 0.9730, 0.3100, // 25° + 0.9600, 0.3720, // 30° + 0.9427, 0.4340, // 35° + 0.9216, 0.4958, // 40° + 0.8962, 0.5571, // 45° + 0.8679, 0.6176, // 50° + 0.8350, 0.6769, // 55° + 0.7986, 0.7346, // 60° + 0.7597, 0.7903, // 65° + 0.7186, 0.8435, // 70° + 0.6732, 0.8936, // 75° + 0.6213, 0.9394, // 80° + 0.5722, 0.9761, // 85° + 0.5322, 1.0000 // 90° + }; + + /** + * Highest valid value for the index of the latitude. + * Assertion: {@code (LAST_INDEX << 1) + 5 == TABLE.length - 1}. + */ + private static final int LAST_INDEX = 17; + + /** + * Projects the specified (Λ,φ) coordinates and stores the (<var>x</var>,<var>y</var>) result in {@code dstPts}. + * The units of measurement are implementation-specific (see super-class javadoc). + * The results must be multiplied by the denormalization matrix before to get linear distances. + * + * @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 coordinates cannot 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 φm = srcPts[srcOff+1]; // In multiple of `LATITUDE_INCREMENT`. + final double φa = abs(φm); + int i = Math.min((int) φa, LAST_INDEX); + final double p = φa - i; + double tb, t0, t1; + + tb = TABLE[i <<= 1]; // Value of XLR for the range of latitudes before current range. + t0 = TABLE[i+2]; // Value of XLR for the lower bound of current range of latitudes. + t1 = TABLE[i+4]; // Value of XLR for the upper bound of current range of latitudes. + final double xp1 = t1 - tb; + final double xp2 = p*(t1 - 2*t0 + tb); + final double xr = t0 + p*(xp1 + xp2)/2; + + tb = TABLE[i+1]; // Value of PR for the range of latitudes before current range. + t0 = TABLE[i+3]; // Value of PR for the lower bound of current range of latitudes. + t1 = TABLE[i+5]; // Value of PR for the upper bound of current range of latitudes. + final double yp1 = t1 - tb; + final double yp2 = p*(t1 - 2*t0 + tb); + final double ya = t0 + p*(yp1 + yp2)/2; + + if (dstPts != null) { + dstPts[dstOff ] = xr * λ; + dstPts[dstOff+1] = copySign(ya, φm); + } + if (!derivate) return null; + return new Matrix2(xr, (xp1/2 + xp2)*abs(λ), + 0, (yp1/2 + yp2)); + } + + /** + * 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 ya = abs(y); + int i = Math.min((int) (ya*(90/LATITUDE_INCREMENT)), LAST_INDEX); // First estimation. + double p; + do { + int ti = (i << 1) | 1; + double ym = TABLE[ti ]; + double y0 = TABLE[ti+2]; + double y1 = TABLE[ti+4]; + double u = y1 - ym; + double t = 2*(ya - y0) / u; + double c = t*(y1 - 2*y0 + ym) / u; + p = t*(1 - c*(1 - 2*c)); + } while (p < 0 && --i >= 0); // Recompute if the first estimation was too high. + i = max(i, 0); + /* + * Above loop computed an estimated position for the latitude band that contains φ. + * Refine the result for the actual latitude value (not only the band containing it). + */ + int nbIter = MAXIMUM_ITERATIONS; + double φm = p + i; // In multiple of `LATITUDE_INCREMENT`. + do { + i <<= 1; + double tb, t0, t1; + tb = TABLE[i+1]; + t0 = TABLE[i+3]; + t1 = TABLE[i+5]; + final double yp1 = t1 - tb; // Same formulas as the forward case. + final double yp2 = p*(t1 - 2*t0 + tb); + final double err = t0 + p*(yp1 + yp2)/2 - ya; // Difference between interpolated y and desired y. + final double dy = (yp1/2 + yp2); // Derivative ∂y/∂φ (last term in the Jacobian matrix). + φm -= err / dy; // Convert the error in y to an error in φ. + if (!(abs(err) > ITERATION_TOLERANCE)) { // Use `!` for accepting NaN. + tb = TABLE[i+0]; + t0 = TABLE[i+2]; + t1 = TABLE[i+4]; + dstPts[dstOff] = x / (t0 + p*(t1 - tb + p*(t1 - 2*t0 + tb))/2); + dstPts[dstOff+1] = copySign(φm, y); + return; + } + i = Math.min((int) φm, LAST_INDEX); + p = φm - i; + } while (--nbIter >= 0); + throw new ProjectionException(Resources.format(Resources.Keys.NoConvergence)); + } +} diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Sinusoidal.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Sinusoidal.java index 00123d060a..7dccde4ed5 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Sinusoidal.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/projection/Sinusoidal.java @@ -73,11 +73,6 @@ public class Sinusoidal extends MeridianArcBased { @Override public Pattern getOperationNamePattern() { return operationName; } - - /** EPSG identifier of an operation method for this variant. */ - @Override public String getIdentifier() { - return null; - } } /** diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mollweide.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mollweide.java index 742899dc80..9214a12e32 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mollweide.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mollweide.java @@ -42,7 +42,7 @@ public final class Mollweide extends MapProjection { private static final long serialVersionUID = -6434031854504431260L; /** - * The operation parameter descriptor for the <cite>Longitude of natural origin</cite> (λ₀) parameter value. + * The operation parameter descriptor for the <cite>Longitude of projection centre</cite> (λ₀) parameter value. * Valid values range is [-180 … 180]° and default value is 0°. * * <!-- Generated by ParameterNameTableGenerator --> diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mollweide.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Robinson.java similarity index 81% copy from endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mollweide.java copy to endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Robinson.java index 742899dc80..94c694f99d 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Mollweide.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/provider/Robinson.java @@ -20,29 +20,27 @@ import jakarta.xml.bind.annotation.XmlTransient; import org.opengis.parameter.ParameterDescriptor; import org.opengis.parameter.ParameterDescriptorGroup; import org.apache.sis.parameter.Parameters; +import org.apache.sis.util.privy.Constants; import org.apache.sis.metadata.iso.citation.Citations; import org.apache.sis.referencing.operation.projection.NormalizedProjection; /** - * The provider for <q>Mollweide</q> (also known as <q>Homalographic</q>) projection. - * As of version 9.4 of EPSG geodetic dataset, there is no known EPSG code for this projection. + * The provider for <q>Robinson</q> projection. * - * @author Johann Sorel (Geomatys) * @author Martin Desruisseaux (Geomatys) * - * @see <a href="https://mathworld.wolfram.com/MollweideProjection.html">Mathworld formulas</a> - * @see <a href="https://gdal.org/proj_list/mollweide.html">GeoTIFF parameters for Mollweide</a> + * @see <a href="https://gdal.org/proj_list/robinson.html">GeoTIFF parameters for Robinson</a> */ @XmlTransient -public final class Mollweide extends MapProjection { +public final class Robinson extends MapProjection { /** * For cross-version compatibility. */ - private static final long serialVersionUID = -6434031854504431260L; + private static final long serialVersionUID = -4027297503846506501L; /** - * The operation parameter descriptor for the <cite>Longitude of natural origin</cite> (λ₀) parameter value. + * The operation parameter descriptor for the <cite>Longitude of projection centre</cite> (λ₀) parameter value. * Valid values range is [-180 … 180]° and default value is 0°. * * <!-- Generated by ParameterNameTableGenerator --> @@ -92,12 +90,12 @@ public final class Mollweide extends MapProjection { */ private static final ParameterDescriptorGroup PARAMETERS; static { - PARAMETERS = builder().setCodeSpace(Citations.ESRI, "ESRI") - .addName("Mollweide") - .addName(null, "Homalographic") - .addName(null, "Homolographic") - .addName(null, "Elliptical") - .addName(null, "Babinet") + PARAMETERS = builder().setCodeSpace(Citations.OGC, Constants.OGC) + .addName( "Robinson") + .addName(Citations.ESRI, "Robinson") + .addName(Citations.GEOTIFF, "CT_Robinson") + .addName(Citations.PROJ4, "robin") + .addIdentifier(Citations.GEOTIFF, "23") .createGroupForMapProjection( CENTRAL_MERIDIAN, FALSE_EASTING, @@ -107,17 +105,17 @@ public final class Mollweide extends MapProjection { /** * Constructs a new provider. */ - public Mollweide() { + public Robinson() { super(PARAMETERS); } /** - * {@inheritDoc} + * Creates a map projection on an ellipsoid having a semi-major axis length of 1. * * @return the map projection created from the given parameter values. */ @Override protected NormalizedProjection createProjection(final Parameters parameters) { - return new org.apache.sis.referencing.operation.projection.Mollweide(this, parameters); + return new org.apache.sis.referencing.operation.projection.Robinson(this, parameters); } } diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ContextualParameters.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ContextualParameters.java index 2e29db0d64..ff10c7f010 100644 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ContextualParameters.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/transform/ContextualParameters.java @@ -282,15 +282,15 @@ public class ContextualParameters extends Parameters implements Serializable { values = forward.values; } else { final List<GeneralParameterDescriptor> descriptors = desc.descriptors(); - final ParameterValue<?>[] values = new ParameterValue<?>[descriptors.size()]; + final var copy = new ParameterValue<?>[descriptors.size()]; int count = 0; - for (int i=0; i < values.length; i++) { - final ContextualParameter<?> p = new ContextualParameter<>((ParameterDescriptor<?>) descriptors.get(i)); + for (int i=0; i < copy.length; i++) { + final var p = new ContextualParameter<>((ParameterDescriptor<?>) descriptors.get(i)); if (mapper.test(forward, p)) { - values[count++] = p; + copy[count++] = p; } } - this.values = ArraysExt.resize(values, count); + values = ArraysExt.resize(copy, count); } isFrozen = true; } @@ -402,8 +402,9 @@ public class ContextualParameters extends Parameters implements Serializable { * @since 0.7 */ public final MatrixSIS getMatrix(MatrixRole role) { - final Matrix fallback; + @SuppressWarnings("LocalVariableHidesMemberVariable") final ContextualParameters inverse; + final Matrix fallback; synchronized (this) { switch (role) { default: throw new AssertionError(role); @@ -455,6 +456,7 @@ public class ContextualParameters extends Parameters implements Serializable { if (λ0 != 0) { offset = DoubleDouble.of(-λ0, true).multiply(toRadians); } + @SuppressWarnings("LocalVariableHidesMemberVariable") final MatrixSIS normalize = (MatrixSIS) this.normalize; // Must be the same instance, not a copy. normalize.convertBefore(0, toRadians, offset); normalize.convertBefore(1, toRadians, null); @@ -479,8 +481,9 @@ public class ContextualParameters extends Parameters implements Serializable { */ public synchronized MatrixSIS denormalizeGeographicOutputs(final double λ0) { ensureModifiable(); + @SuppressWarnings("LocalVariableHidesMemberVariable") + final var denormalize = (MatrixSIS) this.denormalize; // Must be the same instance, not a copy. final DoubleDouble toDegrees = DoubleDouble.RADIANS_TO_DEGREES; - final MatrixSIS denormalize = (MatrixSIS) this.denormalize; // Must be the same instance, not a copy. denormalize.convertAfter(0, toDegrees, (λ0 != 0) ? λ0 : null); denormalize.convertAfter(1, toDegrees, null); return denormalize; diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/RobinsonTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/RobinsonTest.java new file mode 100644 index 0000000000..f0a84c4592 --- /dev/null +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/projection/RobinsonTest.java @@ -0,0 +1,118 @@ +/* + * 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.parameter.Parameters; +import org.apache.sis.geometry.DirectPosition2D; +import org.apache.sis.referencing.privy.Formulas; +import org.apache.sis.referencing.operation.provider.MapProjection; + +// Test dependencies +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + + +/** + * Tests the {@link Robinson} projection. + * + * @author Martin Desruisseaux (Geomatys) + */ +public final class RobinsonTest extends MapProjectionTestCase { + /** + * Creates a new test case. + */ + public RobinsonTest() { + final double delta = (100.0 / 60) / 1852; // Approximately 100 metres. + derivativeDeltas = new double[] {delta, delta}; + } + + /** + * Returns the provider for the "Robinson" projection. + */ + private static MapProjection provider() { + return new org.apache.sis.referencing.operation.provider.Robinson(); + } + + /** + * Tests the first point given in Snyder example which does not involve interpolation. + * Tests also a few simple points at equator and on pole. + * + * @throws FactoryException if an error occurred while creating the map projection. + * @throws TransformException if an error occurred while projecting a coordinate. + */ + @Test + public void testSimplePoints() throws FactoryException, TransformException { + final MapProjection provider = provider(); + final Parameters pg = Parameters.castOrWrap(provider.getParameters().createValue()); + pg.parameter("semi-major").setValue(1); + pg.parameter("semi-minor").setValue(1); + transform = provider.createMathTransform(null, pg); + tolerance = Formulas.ANGULAR_TOLERANCE; + + final double[] expected = { + 0.740630, 0, // 0°N. + 0.711005, 0.503055, // 30°N, result from Snyder. + 0.591467, 0.993400, // 60°N. + 0.394164, 1.352300, // 90°N. + }; + for (int i=0; i <= 3; i++) { + double λ = 50; + double φ = i * 30; + final var p = new DirectPosition2D(λ, φ); + assertSame(p, transform.transform(p, p)); + assertEquals(expected[i*2 ], p.x, 0.000001); + assertEquals(expected[i*2 + 1], p.y, 0.000001); + + assertSame(p, transform.inverse().transform(p, p)); + assertEquals(λ, p.x, Formulas.ANGULAR_TOLERANCE); + assertEquals(φ, p.y, Formulas.ANGULAR_TOLERANCE); + + verifyDerivative(λ, φ); + } + } + + /** + * Tests the second point given in Snyder example which involves interpolation. + * + * @throws FactoryException if an error occurred while creating the map projection. + * @throws TransformException if an error occurred while projecting a coordinate. + */ + @Test + public void testInterpolation() throws FactoryException, TransformException { + final MapProjection provider = provider(); + final Parameters pg = Parameters.castOrWrap(provider.getParameters().createValue()); + pg.parameter("semi-major").setValue(6370997); + pg.parameter("semi-minor").setValue(6370997); + transform = provider.createMathTransform(null, pg); + tolerance = Formulas.LINEAR_TOLERANCE; + + final double λ = -102; + final double φ = -47; + final var p = new DirectPosition2D(λ, φ); + assertSame(p, transform.transform(p, p)); + assertEquals(-8521076, p.x, 5); + assertEquals(-5009012, p.y, 5); + + assertSame(p, transform.inverse().transform(p, p)); + assertEquals(λ, p.x, Formulas.ANGULAR_TOLERANCE); + assertEquals(φ, p.y, Formulas.ANGULAR_TOLERANCE); + + verifyDerivative(λ, φ); + } +} diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProvidersTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProvidersTest.java index 72e3e25d60..54746ba1ed 100644 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProvidersTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/provider/ProvidersTest.java @@ -113,6 +113,7 @@ public final class ProvidersTest extends TestCase { PseudoSinusoidal.class, Polyconic.class, Mollweide.class, + Robinson.class, SouthPoleRotation.class, NorthPoleRotation.class, NTv2.class,