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 8f1e806 Begin implementation of "Modified Azimuthal Equidistant" projection (EPSG:9832). Only forward projection for now. Inverse projection and derivative will be in a next commit. 8f1e806 is described below commit 8f1e80667c5b2249d405b0aedd380c1040d966be Author: Martin Desruisseaux AuthorDate: Tue Mar 17 22:43:52 2020 +0100 Begin implementation of "Modified Azimuthal Equidistant" projection (EPSG:9832). Only forward projection for now. Inverse projection and derivative will be in a next commit. https://issues.apache.org/jira/browse/SIS-237 --- .../provider/ModifiedAzimuthalEquidistant.java | 163 +++++++++++++++++ .../projection/ModifiedAzimuthalEquidistant.java | 198 +++++++++++++++++++++ ...g.opengis.referencing.operation.OperationMethod | 1 + .../referencing/provider/ProvidersTest.java | 1 + .../ModifiedAzimuthalEquidistantTest.java | 50 ++++++ .../sis/test/suite/ReferencingTestSuite.java | 1 + 6 files changed, 414 insertions(+) diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ModifiedAzimuthalEquidistant.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ModifiedAzimuthalEquidistant.java new file mode 100644 index 0000000..9b2abd1 --- /dev/null +++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/ModifiedAzimuthalEquidistant.java @@ -0,0 +1,163 @@ +/* + * 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.apache.sis.parameter.ParameterBuilder; +import org.apache.sis.metadata.iso.citation.Citations; +import org.apache.sis.parameter.Parameters; +import org.apache.sis.referencing.operation.projection.NormalizedProjection; +import org.opengis.referencing.operation.PlanarProjection; + + +/** + * The provider for "Modified Azimuthal Equidistant" projection (EPSG:9832). + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.1 + * + * @see GeoTIFF parameters for Azimuthal Equidistant + * + * @since 1.1 + * @module + */ +@XmlTransient +public final class ModifiedAzimuthalEquidistant extends MapProjection { + /** + * For cross-version compatibility. + */ + private static final long serialVersionUID = -9025540136016917410L; + + /** + * The operation parameter descriptor for the Latitude of natural origin (φ₀) parameter value. + * Valid values range is (-90 … 90)° and default value is 0°. + * + * + * + * + * + * + * + *
Parameter names
EPSG: Latitude of natural origin
GeoTIFF: CenterLat
Proj4: lat_0
+ */ + public static final ParameterDescriptor LATITUDE_OF_ORIGIN; + + /** + * The operation parameter descriptor for the Longitude of natural origin (λ₀) parameter value. + * Valid values range is [-180 … 180]° and default value is 0°. + * + * + * + * + * + * + * + *
Parameter names
EPSG: Longitude of natural origin
GeoTIFF: CenterLong
Proj4: lon_0
+ */ + public static final ParameterDescriptor LONGITUDE_OF_ORIGIN; + + /** + * The operation parameter descriptor for the False easting (FE) parameter value. + * Valid values range is unrestricted and default value is 0 metre. + * + * + * + * + * + * + * + *
Parameter names
EPSG: False easting
GeoTIFF: FalseEasting
Proj4: x_0
+ */ + public static final ParameterDescriptor FALSE_EASTING; + + /** + * The operation parameter descriptor for the False northing (FN) parameter value. + * Valid values range is unrestricted and default value is 0 metre. + * + * + * + * + * + * + * + *
Parameter names
EPSG: False northing
GeoTIFF: FalseNorthing
Proj4: y_0
+ */ + public static final ParameterDescriptor FALSE_NORTHING; + + /** + * Returns a parameter with the same names and identifiers than the given parameter, + * except OGC, ESRI and netCDF names which are omitted. We omit those names for now + * because we have not seen a reference about what those parameter names should be. + * This may be revisited in future SIS versions. + * + *

The OGC and GeoTIFF names kept by this method are actually the names for + * Azimuthal Equidistant (not modified) projection.

+ */ + private static ParameterBuilder erase(final ParameterBuilder builder, ParameterDescriptor template) { + return builder.addNamesAndIdentifiers(template) // Copy from this parameter… + .rename(Citations.OGC, (CharSequence[]) null) // … except for those names. + .rename(Citations.ESRI, (CharSequence[]) null) + .rename(Citations.NETCDF, (CharSequence[]) null); + } + + /** + * The group of all parameters expected by this coordinate operation. + */ + static final ParameterDescriptorGroup PARAMETERS; + static { + final ParameterBuilder builder = builder(); + LATITUDE_OF_ORIGIN = createLatitude (erase(builder, Orthographic.LATITUDE_OF_ORIGIN), true); + LONGITUDE_OF_ORIGIN = createLongitude(erase(builder, Orthographic.LONGITUDE_OF_ORIGIN)); + FALSE_EASTING = createShift (erase(builder, Orthographic.FALSE_EASTING)); + FALSE_NORTHING = createShift (erase(builder, Orthographic.FALSE_NORTHING)); + + PARAMETERS = builder.addIdentifier("9832") + .addName("Modified Azimuthal Equidistant") + .createGroupForMapProjection( + LATITUDE_OF_ORIGIN, + LONGITUDE_OF_ORIGIN, + FALSE_EASTING, + FALSE_NORTHING); + } + + /** + * Constructs a new provider. + */ + public ModifiedAzimuthalEquidistant() { + super(PARAMETERS); + } + + /** + * Returns the operation type for this map projection. + */ + @Override + public Class getOperationType() { + return PlanarProjection.class; + } + + /** + * {@inheritDoc} + * + * @return the map projection created from the given parameter values. + */ + @Override + protected final NormalizedProjection createProjection(final Parameters parameters) { + return new org.apache.sis.referencing.operation.projection.ModifiedAzimuthalEquidistant(this, parameters); + } +} diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistant.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistant.java new file mode 100644 index 0000000..51e5663 --- /dev/null +++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistant.java @@ -0,0 +1,198 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.referencing.operation.projection; + +import java.util.EnumMap; +import org.opengis.referencing.operation.Matrix; +import org.opengis.referencing.operation.OperationMethod; +import org.opengis.parameter.ParameterDescriptor; +import org.apache.sis.parameter.Parameters; +import org.apache.sis.referencing.operation.transform.ContextualParameters; +import org.apache.sis.referencing.operation.matrix.MatrixSIS; +import org.apache.sis.internal.util.Numerics; +import org.apache.sis.util.ComparisonMode; +import org.apache.sis.util.Workaround; + +import static java.lang.Math.*; +import static org.apache.sis.internal.referencing.provider.ModifiedAzimuthalEquidistant.*; + + +/** + * Modified Azimuthal Equidistant projection (EPSG:9832). + * See the following references for an overview: + * + * + *

Description

+ * An approximation of the oblique form of the Azimuthal Equidistant projection. + * For relatively short distances (e.g. under 800 kilometres) this modification introduces no significant error. + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.1 + * @since 1.1 + * @module + */ +public class ModifiedAzimuthalEquidistant extends NormalizedProjection { + /** + * Sine and cosine of the latitude of origin φ₀. + */ + private final double sinφ0, cosφ0; + + /** + * The radius of curvature (assuming a=1) at the latitude of origin + * multiplied by the sine of that latitude. + */ + private final double ℯ2_ν0_sinφ0; + + /** + * The ℯ⋅sin(φ₀)/√(1 − ℯ²) term. + */ + private final double G; + + /** + * The ℯ⋅cos(φ₀)/√(1 − ℯ²) term. This is the H term in EPSG guidance notes + * but without the cos(α) term. + */ + private final double Hp; + + /** + * 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, LONGITUDE_OF_ORIGIN); + roles.put(ParameterRole.FALSE_EASTING, FALSE_EASTING); + roles.put(ParameterRole.FALSE_NORTHING, FALSE_NORTHING); + return new Initializer(method, parameters, roles, (byte) 0); + } + + /** + * Creates a Modified Azimuthal Equidistant projection from the given parameters. + * The {@code method} argument can be the description of one of the following: + * + *
    + *
  • "Modified Azimuthal Equidistant".
  • + *
+ * + * @param method description of the projection parameters. + * @param parameters the parameter values of the projection to create. + */ + public ModifiedAzimuthalEquidistant(final OperationMethod method, final Parameters parameters) { + this(initializer(method, parameters)); + } + + /** + * Work around for RFE #4093999 in Sun's bug database + * ("Relax constraint on placement of this()/super() call in constructors"). + */ + @Workaround(library="JDK", version="1.8") + private ModifiedAzimuthalEquidistant(final Initializer initializer) { + super(initializer); + final double φ0 = toRadians(initializer.getAndStore(LATITUDE_OF_ORIGIN)); + cosφ0 = cos(φ0); + sinφ0 = sin(φ0); + final double ν0 = initializer.radiusOfCurvature(sinφ0); + ℯ2_ν0_sinφ0 = eccentricitySquared * ν0 * sinφ0; + final double f = eccentricity / sqrt(1 - eccentricitySquared); + G = f * sinφ0; + Hp = f * cosφ0; + + final MatrixSIS denormalize = context.getMatrix(ContextualParameters.MatrixRole.DENORMALIZATION); + denormalize.convertBefore(0, ν0, null); + denormalize.convertBefore(1, ν0, null); + } + + /** + * Converts the specified (λ,φ) coordinate and stores the (x,y) result in {@code dstPts}. + * + * @return the matrix of the projection derivative at the given source position, + * or {@code null} if the {@code derivate} argument is {@code false}. + * @throws ProjectionException if the coordinate can not be converted. + */ + @Override + public Matrix transform(final double[] srcPts, final int srcOff, + final double[] dstPts, final int dstOff, + final boolean derivate) throws ProjectionException + { + final double λ = srcPts[srcOff ]; + final double φ = srcPts[srcOff+1]; + final double cosλ = cos(λ); + final double sinλ = sin(λ); + final double cosφ = cos(φ); + final double sinφ = sin(φ); + final double rν = sqrt(1 - eccentricitySquared*(sinφ*sinφ)); + final double Ψ = atan((1 - eccentricitySquared)*tan(φ) + ℯ2_ν0_sinφ0*rν/cosφ); + final double α = atan2(sinλ, cosφ0*tan(Ψ) - sinφ0*cosλ); + final double sinα = sin(α); + final double cosα = cos(α); + final double H = cosα * Hp; + double c; + if (abs(α) < ANGULAR_TOLERANCE) { + c = cosφ0*sin(Ψ) - sinφ0*cos(Ψ); + if (abs(α) > PI/2) c = -c; + } else { + c = sinλ*cos(Ψ) / sinα; + } + c = asin(c); // After this line this is the `s` value in EPSG guidance notes. + final double s2 = c * c; + final double s3 = s2 * c; + final double s4 = s2 * s2; + final double s5 = s4 * c; + final double H2 = H*H; + final double GH = G*H; + c *= 1 - (s2/6 * H2*(1 - H2)) + + (s3/8 * GH*(1 - 2*H2)) + + (s4/120 * (H2*(4 - 7*H2) - 3*(G*G)*(1 - 7*H2))) + - (s5/48 * GH); + if (dstPts != null) { + dstPts[dstOff ] = c*sinα; + dstPts[dstOff+1] = c*cosα; + } + if (!derivate) { + return null; + } + throw new ProjectionException("Derivative not yet implemented."); + } + + /** + * Converts the specified (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) + throws ProjectionException + { + throw new ProjectionException("Inverse transform not yet implemented."); + } + + /** + * Compares the given object with this transform for equivalence. + */ + @Override + public boolean equals(final Object object, final ComparisonMode mode) { + if (super.equals(object, mode)) { + final ModifiedAzimuthalEquidistant that = (ModifiedAzimuthalEquidistant) object; + return Numerics.epsilonEqual(ℯ2_ν0_sinφ0, that.ℯ2_ν0_sinφ0, mode); + } + return false; + } +} diff --git a/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod b/core/sis-referencing/src/main/resources/META-INF/services/org.opengis.referencing.operation.OperationMethod index ae5ddfe..b549967 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.Orthographic +org.apache.sis.internal.referencing.provider.ModifiedAzimuthalEquidistant org.apache.sis.internal.referencing.provider.ZonedTransverseMercator org.apache.sis.internal.referencing.provider.Sinusoidal org.apache.sis.internal.referencing.provider.Polyconic diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/ProvidersTest.java index 3373bae..7bc59ed 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 @@ -104,6 +104,7 @@ public final strictfp class ProvidersTest extends TestCase { ObliqueMercatorTwoPoints.class, ObliqueMercatorTwoPointsCenter.class, Orthographic.class, + ModifiedAzimuthalEquidistant.class, ZonedTransverseMercator.class, SatelliteTracking.class, Sinusoidal.class, diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistantTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistantTest.java new file mode 100644 index 0000000..f131653 --- /dev/null +++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ModifiedAzimuthalEquidistantTest.java @@ -0,0 +1,50 @@ +/* + * 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.test.DependsOn; +import org.junit.Test; + + +/** + * Tests the {@link ModifiedAzimuthalEquidistant} class. + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.1 + * @since 1.1 + * @module + */ +@DependsOn(NormalizedProjectionTest.class) +public final strictfp class ModifiedAzimuthalEquidistantTest extends MapProjectionTestCase { + /** + * Tests the "Modified Azimuthal Equidistant" (EPSG:9832) projection method. + * This test is defined in GeoAPI conformance test suite. + * + * @throws FactoryException if an error occurred while creating the map projection. + * @throws TransformException if an error occurred while projecting a coordinate. + * + * @see org.opengis.test.referencing.ParameterizedTransformTest#testModifiedAzimuthalEquidistant() + */ + @Test + @org.junit.Ignore("Implementation not yet completed") + public void runGeoapiTest() throws FactoryException, TransformException { + createGeoApiTest(new org.apache.sis.internal.referencing.provider.ModifiedAzimuthalEquidistant()) + .testModifiedAzimuthalEquidistant(); + } +} 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 5e26fe4..7d9facb 100644 --- a/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java +++ b/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java @@ -192,6 +192,7 @@ import org.junit.BeforeClass; org.apache.sis.referencing.operation.projection.PolyconicTest.class, org.apache.sis.referencing.operation.projection.MollweideTest.class, org.apache.sis.referencing.operation.projection.OrthographicTest.class, + org.apache.sis.referencing.operation.projection.ModifiedAzimuthalEquidistantTest.class, org.apache.sis.referencing.operation.projection.SatelliteTrackingTest.class, // Coordinate operation and derived Coordinate Reference Systems (cyclic dependency).