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 5eacd24  Fix a confusion when `Envelopes.transform(…)` check if +180° 
or -180° of longitude should be added to the bounding box.
5eacd24 is described below

commit 5eacd246987d289cf6aaeca93f7b359419df7116
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Thu Mar 18 16:32:29 2021 +0100

    Fix a confusion when `Envelopes.transform(…)` check if +180° or -180° of 
longitude should be added to the bounding box.
---
 .../java/org/apache/sis/geometry/Envelopes.java    | 109 ++++++++++++---------
 .../java/org/apache/sis/geometry/Shapes2D.java     |  52 +++++-----
 2 files changed, 93 insertions(+), 68 deletions(-)

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 a65100e..16285ae 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
@@ -637,7 +637,7 @@ nextPoint:  for (int pointIndex = 0;;) {                // 
Break condition at th
                 }
             }
         }
-        MathTransform mt = operation.getMathTransform();
+        final MathTransform mt = operation.getMathTransform();
         final double[] centerPt = new double[mt.getTargetDimensions()];
         final GeneralEnvelope transformed = transform(mt, envelope, centerPt);
         /*
@@ -701,7 +701,7 @@ nextPoint:  for (int pointIndex = 0;;) {                // 
Break condition at th
         }
         /*
          * Checks for singularity points. For example the south pole is a 
singularity point in
-         * geographic CRS because is is located at the maximal value allowed 
by one particular
+         * geographic CRS because it is located at the maximal value allowed 
by one particular
          * axis, namely latitude. This point is not a singularity in the 
stereographic projection,
          * because axes extends toward infinity in all directions 
(mathematically) and because the
          * South pole has nothing special apart being the origin (0,0).
@@ -716,13 +716,14 @@ nextPoint:  for (int pointIndex = 0;;) {                
// Break condition at th
          *             to the source CRS. Note that the longitude is set to 
the the center of
          *             the envelope longitude range (more on this below).
          *
-         * 2) If the singularity point computed above is inside the source 
envelope, add that
-         *    point to the target (transformed) envelope.
+         * 2) If the singularity point computed above is inside the source 
envelope,
+         *    add that point to the target (transformed) envelope.
          *
-         * 3) If step #2 added the point, iterate over all other axes. If an 
other bounded axis
-         *    is found and that axis is of kind "WRAPAROUND", test for 
inclusion the same point
-         *    than the point tested at step #1, except for the coordinate of 
the axis found in
-         *    this step. That coordinate is set to the minimal and maximal 
values of that axis.
+         * 3) For each singularity point found at step #2, check if there is a 
wraparound axis
+         *    in a dimension other than the dimension that was set to an axis 
bounds value.
+         *    If such wraparound axis is found, test for inclusion of the same 
point than the
+         *    point tested at step #1, except for the coordinate of the 
wraparound axis which
+         *    is set to the minimal and maximal values (reminder: step #1 used 
the center value).
          *
          *    Example: If the above steps found that the point (90°S, 30°W) 
need to be included,
          *             then this step #3 will also test the points (90°S, 
180°W) and (90°S, 180°E).
@@ -734,16 +735,18 @@ nextPoint:  for (int pointIndex = 0;;) {                
// Break condition at th
          * may fail to transform the (-180°, 90°) coordinate while the (-180°, 
30°) coordinate
          * is a valid point.
          */
-        TransformException warning = null;
-        AbstractEnvelope generalEnvelope = null;
-        DirectPosition sourcePt = null;
-        DirectPosition targetPt = null;
+        MathTransform      inverse   = null;
+        TransformException warning   = null;
+        AbstractEnvelope   sourceBox = null;
+        DirectPosition     sourcePt  = null;
+        DirectPosition     targetPt  = null;
+        DirectPosition     revertPt  = null;
         long includedMinValue = 0;              // A bitmask for each 
dimension.
         long includedMaxValue = 0;
         long isWrapAroundAxis = 0;
-        long dimensionBitMask = 1;
         final int dimension = targetCS.getDimension();
-poles:  for (int i=0; i<dimension; i++, dimensionBitMask <<= 1) {
+poles:  for (int i=0; i<dimension; i++) {
+            final long dimensionBitMask = 1L << i;
             final CoordinateSystemAxis axis = targetCS.getAxis(i);
             if (axis == null) {                 // Should never be null, but 
check as a paranoiac safety.
                 continue;
@@ -759,37 +762,52 @@ poles:  for (int i=0; i<dimension; i++, dimensionBitMask 
<<= 1) {
                      */
                     continue;
                 }
-                if (targetPt == null) {
+                if (inverse == null) {
                     try {
-                        mt = mt.inverse();
+                        inverse = mt.inverse();
                     } catch (NoninvertibleTransformException exception) {
                         /*
-                         * If the transform is non invertible, this method 
can't do anything. This
-                         * is not a fatal error because the envelope has 
already be transformed by
-                         * the caller. We lost the check for singularity 
points performed by this
-                         * method, but it make no difference in the common 
case where the source
-                         * envelope didn't contains any of those points.
+                         * If the transform is non-invertible, this "poles" 
loop can not do anything.
+                         * This is not a fatal error because the envelope has 
already be transformed
+                         * by the code before this loop. We lost the check 
below for singularity points,
+                         * but it makes no difference in the common case where 
all those points would
+                         * have been outside the source envelope anyway.
                          *
-                         * Note that this exception is normal if target 
dimension is smaller than
-                         * source dimension, since the math transform can not 
reconstituate the
-                         * lost dimensions. So we don't log any warning in 
this case.
+                         * Note that this exception is normal if the number of 
target dimension is smaller
+                         * than the number of source dimension, because the 
math transform can not guess
+                         * coordinates in the lost dimensions. So we do not 
log any warning in this case.
                          */
                         if (dimension >= mt.getSourceDimensions()) {
                             warning = exception;
                         }
                         break poles;
                     }
-                    targetPt = new 
GeneralDirectPosition(mt.getSourceDimensions());
-                    for (int j=0; j<dimension; j++) {
-                        targetPt.setOrdinate(j, centerPt[j]);
-                    }
-                    // TODO: avoid the hack below if we provide a 
contains(DirectPosition)
-                    //       method in the GeoAPI 
org.opengis.geometry.Envelope interface.
-                    generalEnvelope = AbstractEnvelope.castOrCopy(envelope);
+                    targetPt  = new GeneralDirectPosition(centerPt.clone());
+                    sourceBox = AbstractEnvelope.castOrCopy(envelope);
                 }
                 targetPt.setOrdinate(i, extremum);
                 try {
-                    sourcePt = mt.transform(targetPt, sourcePt);
+                    sourcePt = inverse.transform(targetPt, sourcePt);
+                    if (sourceBox.contains(sourcePt)) {
+                        /*
+                         * The point is inside the source envelope and 
consequently should be added in the
+                         * transformed envelope. However there is a possible 
confusion: if the axis that we
+                         * tested is a wraparound axis, then (for example) 
+180° and -180° of longitude may
+                         * be the same point in source CRS, despite being 2 
very different points in target
+                         * CRS. We do yet another projection in opposite 
direction for checking if we really
+                         * have the point that we wanted to test.
+                         */
+                        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()))) {
+                                continue;
+                            }
+                        }
+                        transformed.add(targetPt);
+                        if (testMax) includedMaxValue |= dimensionBitMask;
+                        else         includedMinValue |= dimensionBitMask;
+                    }
                 } catch (TransformException exception) {
                     /*
                      * This exception may be normal. For example if may occur 
when projecting
@@ -801,18 +819,12 @@ poles:  for (int i=0; i<dimension; i++, dimensionBitMask 
<<= 1) {
                     } else {
                         warning.addSuppressed(exception);
                     }
-                    continue;
-                }
-                if (generalEnvelope.contains(sourcePt)) {
-                    transformed.add(targetPt);
-                    if (testMax) includedMaxValue |= dimensionBitMask;
-                    else         includedMinValue |= dimensionBitMask;
                 }
             } while ((testMax = !testMax) == true);
             /*
              * Keep trace of axes of kind WRAPAROUND, except if the two 
extremum values of that
              * axis have been included in the envelope  (in which case the 
next step after this
-             * loop doesn't need to be executed for that axis).
+             * loop does not need to be executed for this axis).
              */
             if ((includedMinValue & includedMaxValue & dimensionBitMask) == 0 
&& CoordinateOperations.isWrapAround(axis)) {
                 isWrapAroundAxis |= dimensionBitMask;
@@ -833,7 +845,7 @@ poles:  for (int i=0; i<dimension; i++, dimensionBitMask 
<<= 1) {
         if (includedBoundsValue != 0) {
             while (isWrapAroundAxis != 0) {
                 final int wrapAroundDimension = 
Long.numberOfTrailingZeros(isWrapAroundAxis);
-                dimensionBitMask = 1 << wrapAroundDimension;
+                final long dimensionBitMask = 1L << wrapAroundDimension;
                 isWrapAroundAxis &= ~dimensionBitMask;              // Clear 
now the bit, for the next iteration.
                 final CoordinateSystemAxis wrapAroundAxis = 
targetCS.getAxis(wrapAroundDimension);
                 final double min = wrapAroundAxis.getMinimumValue();
@@ -861,7 +873,7 @@ poles:  for (int i=0; i<dimension; i++, dimensionBitMask 
<<= 1) {
                          * then skip completely this case and the next one, 
i.e. skip c={0,1}
                          * or skip c={2,3}.
                          */
-                        double value = max;
+                        final double value;
                         if ((c & 1) == 0) {         // `true` if we are 
testing "wrapAroundMin".
                             if (((c == 0 ? includedMinValue : 
includedMaxValue) & bm) == 0) {
                                 c++;                // Skip also the case for 
"wrapAroundMax".
@@ -869,20 +881,25 @@ poles:  for (int i=0; i<dimension; i++, dimensionBitMask 
<<= 1) {
                             }
                             targetPt.setOrdinate(axisIndex, (c == 0) ? 
axis.getMinimumValue() : axis.getMaximumValue());
                             value = min;
+                        } else {
+                            value = max;
                         }
                         targetPt.setOrdinate(wrapAroundDimension, value);
                         try {
-                            sourcePt = mt.transform(targetPt, sourcePt);
+                            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);
+                                }
+                            }
                         } catch (TransformException exception) {
                             if (warning == null) {
                                 warning = exception;
                             } else {
                                 warning.addSuppressed(exception);
                             }
-                            continue;
-                        }
-                        if (generalEnvelope.contains(sourcePt)) {
-                            transformed.add(targetPt);
                         }
                     }
                     targetPt.setOrdinate(axisIndex, centerPt[axisIndex]);
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 7b565a7..927eba5 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
@@ -402,7 +402,7 @@ public final class Shapes2D extends Static {
         if (envelope == null) {
             return null;
         }
-        MathTransform2D mt = 
MathTransforms.bidimensional(operation.getMathTransform());
+        final MathTransform2D mt = 
MathTransforms.bidimensional(operation.getMathTransform());
         final double[] center = new double[2];
         destination = transform(mt, envelope, destination, center);
         /*
@@ -461,9 +461,11 @@ public final class Shapes2D extends Static {
          * The `border` variable in the loop below is used in order to 
compress 2 dimensions
          * and 2 extremums in a single loop, in this order: (xmin, xmax, ymin, 
ymax).
          */
-        TransformException warning = null;
-        Point2D sourcePt = null;
-        Point2D targetPt = null;
+        MathTransform2D    inverse  = null;
+        TransformException warning  = null;
+        Point2D            sourcePt = null;
+        Point2D            targetPt = null;
+        Point2D            revertPt = null;
         int includedBoundsValue = 0;                        // A bitmask for 
each (dimension, extremum) pairs.
         for (int border=0; border<4; border++) {            // 2 dimensions 
and 2 extremums compacted in a flag.
             final int dimension = border >>> 1;             // The dimension 
index being examined.
@@ -475,9 +477,9 @@ public final class Shapes2D extends Static {
             if (!Double.isFinite(extremum)) {
                 continue;
             }
-            if (targetPt == null) {
+            if (inverse == null) {
                 try {
-                    mt = mt.inverse();
+                    inverse = mt.inverse();
                 } catch (NoninvertibleTransformException exception) {
                     Envelopes.recoverableException(Shapes2D.class, exception);
                     return destination;
@@ -490,18 +492,24 @@ public final class Shapes2D extends Static {
                 default: throw new AssertionError(border);
             }
             try {
-                sourcePt = mt.transform(targetPt, sourcePt);
+                sourcePt = inverse.transform(targetPt, sourcePt);
+                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()))) {
+                        continue;
+                    }
+                }
+                if (envelope.contains(sourcePt)) {
+                    destination.add(targetPt);
+                    includedBoundsValue |= (1 << border);
+                }
             } catch (TransformException exception) {
                 if (warning == null) {
                     warning = exception;
                 } else {
                     warning.addSuppressed(exception);
                 }
-                continue;
-            }
-            if (envelope.contains(sourcePt)) {
-                destination.add(targetPt);
-                includedBoundsValue |= (1 << border);
             }
         }
         /*
@@ -549,24 +557,24 @@ public final class Shapes2D extends Static {
                 final int dimensionToAdd = (border >>> 1) & 1;
                 final CoordinateSystemAxis toAdd = 
targetCS.getAxis(dimensionToAdd);
                 final CoordinateSystemAxis added = 
targetCS.getAxis(dimensionToAdd ^ 1);
-                double x = (border & 1) == 0 ? toAdd.getMinimumValue() : 
toAdd.getMaximumValue();
-                double y = (border & 4) == 0 ? added.getMinimumValue() : 
added.getMaximumValue();
-                if (dimensionToAdd != 0) {
-                    final double t=x; x=y; y=t;
+                final double x = (border & 1) == 0 ? toAdd.getMinimumValue() : 
toAdd.getMaximumValue();
+                final double y = (border & 4) == 0 ? added.getMinimumValue() : 
added.getMaximumValue();
+                if (dimensionToAdd == 0) {
+                    targetPt.setLocation(x, y);
+                } else {
+                    targetPt.setLocation(y, x);
                 }
-                targetPt.setLocation(x, y);
                 try {
-                    sourcePt = mt.transform(targetPt, sourcePt);
+                    sourcePt = inverse.transform(targetPt, sourcePt);
+                    if (envelope.contains(sourcePt)) {
+                        destination.add(targetPt);
+                    }
                 } catch (TransformException exception) {
                     if (warning == null) {
                         warning = exception;
                     } else {
                         warning.addSuppressed(exception);
                     }
-                    continue;
-                }
-                if (envelope.contains(sourcePt)) {
-                    destination.add(targetPt);
                 }
             }
         }

Reply via email to