You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2021/03/18 23:34:55 UTC
[sis] branch geoapi-4.0 updated: Add a test case for the fix
provided in previous commit.
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 ab6393c Add a test case for the fix provided in previous commit.
ab6393c is described below
commit ab6393c7253adfb2ecce268c0764b80a5356e92e
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Fri Mar 19 00:28:00 2021 +0100
Add a test case for the fix provided in previous commit.
---
.../org/apache/sis/image/ResamplingGridTest.java | 12 +++-
.../java/org/apache/sis/geometry/Envelopes.java | 36 ++++++++--
.../java/org/apache/sis/geometry/Shapes2D.java | 2 +-
.../operation/CoordinateOperationRegistry.java | 2 +-
.../org/apache/sis/geometry/TransformTestCase.java | 44 +++++++++---
.../operation/HardCodedConversions.java | 81 ++++++++++++++++------
6 files changed, 135 insertions(+), 42 deletions(-)
diff --git a/core/sis-feature/src/test/java/org/apache/sis/image/ResamplingGridTest.java b/core/sis-feature/src/test/java/org/apache/sis/image/ResamplingGridTest.java
index 54cdd7d..f6bacc2 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/image/ResamplingGridTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/image/ResamplingGridTest.java
@@ -182,13 +182,21 @@ public final strictfp class ResamplingGridTest extends TestCase {
}
/**
+ * Creates a <cite>Lambert Conic Conformal (1SP)</cite> projection
+ * with a Latitude of natural origin arbitrarily set to 40.
+ */
+ private static MathTransform lambertProjection() {
+ return HardCodedConversions.createCRS(HardCodedConversions.LAMBERT).getConversionFromBase().getMathTransform();
+ }
+
+ /**
* Tests {@link ResamplingGrid} with a Lambert Conic Conformal projection.
*
* @throws TransformException if an error occurred while transforming a coordinate.
*/
@Test
public void testLambert() throws TransformException {
- final MathTransform projection = HardCodedConversions.lambert().getConversionFromBase().getMathTransform();
+ final MathTransform projection = lambertProjection();
final Rectangle domain = new Rectangle(-20, 30, 40, 20);
final MathTransform2D tr = compare("Lambert", projection, domain);
assertInstanceOf("Expected a non-linear transform.", ResamplingGrid.class, tr);
@@ -205,7 +213,7 @@ public final strictfp class ResamplingGridTest extends TestCase {
*/
@Test
public void testLambertOnSmallArea() throws TransformException {
- final MathTransform projection = HardCodedConversions.lambert().getConversionFromBase().getMathTransform();
+ final MathTransform projection = lambertProjection();
final Rectangle2D domain = new Rectangle2D.Double(-20, 50, 0.025, 0.025);
final MathTransform2D tr = compare("Lambert (small area)", projection, domain);
assertInstanceOf("Expected a linear transform.", AffineTransform2D.class, tr);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java
index 16285ae..5d318bb 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java
@@ -106,6 +106,18 @@ import static org.apache.sis.util.StringBuilders.trimFractionalPart;
*/
public final class Envelopes extends Static {
/**
+ * Fraction of the axis span to accept as close enough to an envelope boundary. This is used for coordinates
+ * that are suppose to be on a boundary, for checking if it is really on the boundary side where it should be.
+ * For example on the longitude axis, bounds are -180° and +180° with wraparound meaning and the span is 360°.
+ * A {@code SPAN_FRACTION_AS_BOUND} value of 0.25 means that we accept a margin of 0.25 × 360° = 90° on each
+ * side: longitudes between -180 and -90 are clipped to the -180° bounds, and longitudes between +180 and +90
+ * and clipped to the +180° bounds. We use a large fraction because we use it in contexts where the longitude
+ * is not supposed to be in the envelope interior. We could use a 0.5 value for clipping to the nearest bound,
+ * but a smaller value is used for safety.
+ */
+ static final double SPAN_FRACTION_AS_BOUND = 0.25;
+
+ /**
* Do not allow instantiation of this class.
*/
private Envelopes() {
@@ -800,7 +812,7 @@ poles: for (int i=0; i<dimension; i++) {
if (CoordinateOperations.isWrapAround(axis)) {
revertPt = mt.transform(sourcePt, revertPt);
final double delta = Math.abs(revertPt.getOrdinate(i) - extremum);
- if (!(delta < 0.25 * (axis.getMaximumValue() - axis.getMinimumValue()))) {
+ if (!(delta < SPAN_FRACTION_AS_BOUND * (axis.getMaximumValue() - axis.getMinimumValue()))) {
continue;
}
}
@@ -888,11 +900,23 @@ poles: for (int i=0; i<dimension; i++) {
try {
sourcePt = inverse.transform(targetPt, sourcePt);
if (sourceBox.contains(sourcePt)) {
- revertPt = mt.transform(sourcePt, revertPt);
- final double delta = Math.abs(revertPt.getOrdinate(wrapAroundDimension) - value);
- if (delta < 0.25 * (max - min)) {
- transformed.add(targetPt);
- }
+ /*
+ * The `value` limit is typically the 180°E or 180°W longitude value. We could test
+ * its validity as below (see similar code in other loop above for explanation):
+ *
+ * revertPt = mt.transform(sourcePt, revertPt);
+ * final double delta = Math.abs(revertPt.getOrdinate(wrapAroundDimension) - value);
+ * if (delta < SPAN_FRACTION_AS_BOUND * (max - min)) {
+ * transformed.add(targetPt);
+ * }
+ *
+ * But we don't because this block is executed when another coordinate is at its bounds,
+ * and that other coordinate is usually the latitude at 90°S or 90°N. In such case, all
+ * longitude values are the same point on Earth (a pole) and can be anything. Comparing
+ * `revertPt` coordinate with `value` is meaningless. In order to keep above check, we
+ * would need to determine if `targetPt` is at a pole. But we have fully reliable way.
+ */
+ transformed.add(targetPt);
}
} catch (TransformException exception) {
if (warning == null) {
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Shapes2D.java b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Shapes2D.java
index 927eba5..7459d34 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/geometry/Shapes2D.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/geometry/Shapes2D.java
@@ -496,7 +496,7 @@ public final class Shapes2D extends Static {
if (CoordinateOperations.isWrapAround(axis)) {
revertPt = mt.transform(sourcePt, revertPt);
final double delta = Math.abs((dimension == 0 ? revertPt.getX() : revertPt.getY()) - extremum);
- if (!(delta < 0.25 * (axis.getMaximumValue() - axis.getMinimumValue()))) {
+ if (!(delta < Envelopes.SPAN_FRACTION_AS_BOUND * (axis.getMaximumValue() - axis.getMinimumValue()))) {
continue;
}
}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
index dcbcb97..7b8a5af 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
@@ -691,7 +691,7 @@ class CoordinateOperationRegistry {
* @param operation the operation to invert, or {@code null}.
* @return the inverse of {@code operation}, or {@code null} if none.
* @throws NoninvertibleTransformException if the operation is not invertible.
- * @throws FactoryException if the operation creation failed for an other reason.
+ * @throws FactoryException if the operation creation failed for another reason.
*/
private CoordinateOperation inverse(final CoordinateOperation operation)
throws NoninvertibleTransformException, FactoryException
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/geometry/TransformTestCase.java b/core/sis-referencing/src/test/java/org/apache/sis/geometry/TransformTestCase.java
index d38fa26..0514d7f 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/geometry/TransformTestCase.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/geometry/TransformTestCase.java
@@ -48,7 +48,7 @@ import static org.apache.sis.test.ReferencingAssert.*;
* All tests performed by this class are two-dimensional.
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 0.8
+ * @version 1.1
*
* @param <G> the type of geometric objects, either {@link GeneralEnvelope} or {@link java.awt.geom.Rectangle2D}.
*
@@ -136,16 +136,7 @@ public abstract strictfp class TransformTestCase<G> extends TestCase {
@Test
@DependsOnMethod("testTransform")
public final void testTransformOverPole() throws FactoryException, TransformException {
- final ProjectedCRS sourceCRS = (ProjectedCRS) CRS.fromWKT(
- "PROJCS[“WGS 84 / Antarctic Polar Stereographic”,\n" +
- " GEOGCS[“WGS 84”,\n" +
- " DATUM[“World Geodetic System 1984”,\n" +
- " SPHEROID[“WGS 84”, 6378137.0, 298.257223563]],\n" +
- " PRIMEM[“Greenwich”, 0.0],\n" +
- " UNIT[“degree”, 0.017453292519943295]],\n" +
- " PROJECTION[“Polar Stereographic (variant B)”],\n" +
- " PARAMETER[“standard_parallel_1”, -71.0],\n" +
- " UNIT[“m”, 1.0]]");
+ final ProjectedCRS sourceCRS = HardCodedConversions.createCRS(HardCodedConversions.POLAR_STEREOGRAPHIC);
final GeographicCRS targetCRS = sourceCRS.getBaseCRS();
final Conversion conversion = inverse(sourceCRS.getConversionFromBase());
final MathTransform2D transform = (MathTransform2D) conversion.getMathTransform();
@@ -244,9 +235,40 @@ public abstract strictfp class TransformTestCase<G> extends TestCase {
}
/**
+ * Tests conversion from a UTM projection to geographic CRS where the resulting envelope crosses the anti-meridian.
+ * Contrarily to {@link #testTransformOverAntiMeridian()}, the longitude range is outside the [-180 … +180]° range.
+ * This is because the projection has a large central meridian which is added to the result.
+ *
+ * @throws FactoryException if an error occurred while creating the operation.
+ * @throws TransformException if an error occurred while transforming the envelope.
+ *
+ * @since 1.1
+ */
+ @Test
+ @DependsOnMethod("testTransformOverAntiMeridian")
+ public void testProjectionOutsideLongitudeRange() throws FactoryException, TransformException {
+ final ProjectedCRS sourceCRS = HardCodedConversions.createCRS(HardCodedConversions.UTM);
+ final GeographicCRS targetCRS = sourceCRS.getBaseCRS();
+ final Conversion conversion = inverse(sourceCRS.getConversionFromBase());
+ final G rectangle = createFromExtremums(sourceCRS,
+ -402748, 7965673, // Computed by SIS (not validated by external authority).
+ 1312383, 9912935);
+
+ // Longitude span anti-meridian (-214° to 45°).
+ final G expected = createFromExtremums(targetCRS,
+ -213.637, 70.141,
+ -44.959, 89.147);
+
+ final G actual = transform(conversion, rectangle);
+ assertGeometryEquals(expected, actual, 0.001, 0.001);
+ }
+
+ /**
* Returns the inverse of the given conversion. This method is not strictly correct
* since we reuse the properties (name, aliases, etc.) from the given conversion.
* However those properties are not significant for the purpose of this test.
+ *
+ * @see org.apache.sis.referencing.operation.CoordinateOperationRegistry#inverse(SingleOperation)
*/
private static Conversion inverse(final Conversion conversion) throws NoninvertibleTransformException {
return new DefaultConversion(IdentifiedObjects.getProperties(conversion), conversion.getTargetCRS(),
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/HardCodedConversions.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/HardCodedConversions.java
index 48878fc..aa50924 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/HardCodedConversions.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/HardCodedConversions.java
@@ -23,7 +23,9 @@ import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.crs.ProjectedCRS;
import org.opengis.referencing.operation.OperationMethod;
import org.apache.sis.internal.referencing.provider.Mercator1SP;
+import org.apache.sis.internal.referencing.provider.TransverseMercator;
import org.apache.sis.internal.referencing.provider.LambertConformal1SP;
+import org.apache.sis.internal.referencing.provider.PolarStereographicB;
import org.apache.sis.referencing.crs.DefaultProjectedCRS;
import org.apache.sis.referencing.crs.HardCodedCRS;
import org.apache.sis.referencing.cs.HardCodedCS;
@@ -45,8 +47,39 @@ public final strictfp class HardCodedConversions {
public static final DefaultConversion MERCATOR;
static {
final OperationMethod method = new Mercator1SP();
- MERCATOR = new DefaultConversion(Collections.singletonMap(OperationMethod.NAME_KEY, "Mercator"),
- method, null, method.getParameters().createValue());
+ MERCATOR = create("Mercator", method, method.getParameters().createValue());
+ }
+
+ /**
+ * A defining conversion for a <cite>Universal Transverse Mercator zone 9</cite> projection.
+ * Pseudo Well-Known Text for the {@link org.opengis.referencing.operation.MathTransform}:
+ *
+ * {@preformat wkt
+ * Param_MT["Transverse Mercator",
+ * Parameter["Longitude of natural origin", -129, Unit["degree"]],
+ * Parameter["Scale factor at natural origin", 0.9996],
+ * Parameter["False easting", 500000, Unit["metre"]]]]
+ * }
+ */
+ public static final DefaultConversion UTM;
+ static {
+ final OperationMethod method = new TransverseMercator();
+ final ParameterValueGroup pg = method.getParameters().createValue();
+ pg.parameter("Longitude of natural origin").setValue(-129);
+ pg.parameter("Scale factor at natural origin").setValue(0.9996);
+ pg.parameter("False easting").setValue(500000);
+ UTM = create("UTM zone 9N", method, pg);
+ }
+
+ /**
+ * A defining conversion for a <cite>Antarctic Polar Stereographic</cite> projection.
+ */
+ public static final DefaultConversion POLAR_STEREOGRAPHIC;
+ static {
+ final OperationMethod method = new PolarStereographicB();
+ final ParameterValueGroup pg = method.getParameters().createValue();
+ pg.parameter("Latitude of standard parallel").setValue(-71);
+ POLAR_STEREOGRAPHIC = create("Antarctic Polar Stereographic", method, pg);
}
/**
@@ -58,8 +91,14 @@ public final strictfp class HardCodedConversions {
final OperationMethod method = new LambertConformal1SP();
final ParameterValueGroup pg = method.getParameters().createValue();
pg.parameter("Latitude of natural origin").setValue(40);
- LAMBERT = new DefaultConversion(Collections.singletonMap(OperationMethod.NAME_KEY, "Lambert Conic Conformal"),
- method, null, pg);
+ LAMBERT = create("Lambert Conic Conformal", method, pg);
+ }
+
+ /**
+ * Creates a defining conversion of the given name with given parameter values.
+ */
+ private static DefaultConversion create(final String name, final OperationMethod method, final ParameterValueGroup pg) {
+ return new DefaultConversion(Collections.singletonMap(OperationMethod.NAME_KEY, name), method, null, pg);
}
/**
@@ -69,6 +108,20 @@ public final strictfp class HardCodedConversions {
}
/**
+ * Creates a two-dimension CRS using the given conversion on the WGS84 datum.
+ * This CRS uses (<var>easting</var>, <var>northing</var>) coordinates in metres.
+ * The base CRS uses (<var>longitude</var>, <var>latitude</var>) axes
+ * and the prime meridian is Greenwich.
+ *
+ * @param conversion the defining conversion as one of the constant defined in this class.
+ * @return two-dimensional projection using the given method.
+ */
+ public static DefaultProjectedCRS createCRS(final DefaultConversion conversion) {
+ return new DefaultProjectedCRS(Collections.singletonMap(ProjectedCRS.NAME_KEY, conversion.getName()),
+ HardCodedCRS.WGS84, conversion, HardCodedCS.PROJECTED);
+ }
+
+ /**
* A two-dimensional Mercator projection using the WGS84 datum.
* This CRS uses (<var>easting</var>, <var>northing</var>) coordinates in metres.
* The base CRS uses (<var>longitude</var>, <var>latitude</var>) axes
@@ -80,8 +133,7 @@ public final strictfp class HardCodedConversions {
* @return two-dimensional Mercator projection.
*/
public static DefaultProjectedCRS mercator() {
- return new DefaultProjectedCRS(name("Mercator"),
- HardCodedCRS.WGS84, HardCodedConversions.MERCATOR, HardCodedCS.PROJECTED);
+ return createCRS(MERCATOR);
}
/**
@@ -94,7 +146,7 @@ public final strictfp class HardCodedConversions {
*/
public static DefaultProjectedCRS mercator3D() {
return new DefaultProjectedCRS(name("Mercator (3D)"),
- HardCodedCRS.WGS84_3D, HardCodedConversions.MERCATOR, HardCodedCS.PROJECTED_3D);
+ HardCodedCRS.WGS84_3D, MERCATOR, HardCodedCS.PROJECTED_3D);
}
/**
@@ -105,24 +157,11 @@ public final strictfp class HardCodedConversions {
* @return two- or three-dimensional Mercator projection.
*/
public static DefaultProjectedCRS mercator(final GeographicCRS baseCRS) {
- return new DefaultProjectedCRS(name("Mercator (other)"), baseCRS, HardCodedConversions.MERCATOR,
+ return new DefaultProjectedCRS(name("Mercator (other)"), baseCRS, MERCATOR,
baseCRS.getCoordinateSystem().getDimension() == 3 ? HardCodedCS.PROJECTED_3D : HardCodedCS.PROJECTED);
}
/**
- * A two-dimensional Lambert Conic Conformal (1SP) projection using the WGS84 datum.
- * This CRS uses (<var>easting</var>, <var>northing</var>) coordinates in metres.
- * The base CRS uses (<var>longitude</var>, <var>latitude</var>) axes
- * and the prime meridian is Greenwich.
- *
- * @return two-dimensional Lambert Conic Conformal (1SP) projection.
- */
- public static DefaultProjectedCRS lambert() {
- return new DefaultProjectedCRS(name("Lambert Conic Conformal"),
- HardCodedCRS.WGS84, HardCodedConversions.LAMBERT, HardCodedCS.PROJECTED);
- }
-
- /**
* Puts the given name in a property map CRS constructors.
*/
private static Map<String,?> name(final String name) {