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 2022/08/21 14:31:10 UTC

[sis] branch geoapi-4.0 updated (e6d4c167a5 -> b5cccfe9ce)

This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a change to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


    from e6d4c167a5 Merge remote-tracking branch 'origin/fix/zero-based-transform' into geoapi-4.0.
     add f5a308bb58 fix(Core): add control points if required when converting an envelope to a polygon.
     new b5cccfe9ce Merge remote-tracking branch 'origin/fix/world-envelope-to-polygon' into geoapi-4.0. The fix, which adds intermediate points during the "envelope to JTS" conversion, is modified in two ways:

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../apache/sis/internal/feature/Geometries.java    | 128 +++++++++++++++++----
 .../internal/feature/SpatialOperationContext.java  |  23 +++-
 .../apache/sis/internal/feature/package-info.java  |   2 +-
 .../sis/internal/filter/GeometryConverter.java     |  13 ++-
 .../apache/sis/internal/filter/package-info.java   |   2 +-
 .../sis/filter/BinarySpatialFilterTestCase.java    |  34 +++++-
 .../filter/BinarySpatialFilterUsingESRI_Test.java  |  11 +-
 .../BinarySpatialFilterUsingJava2D_Test.java       |  11 +-
 .../sis/internal/feature/GeometriesTestCase.java   |  21 +++-
 9 files changed, 212 insertions(+), 33 deletions(-)


[sis] 01/01: Merge remote-tracking branch 'origin/fix/world-envelope-to-polygon' into geoapi-4.0. The fix, which adds intermediate points during the "envelope to JTS" conversion, is modified in two ways:

Posted by de...@apache.org.
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

commit b5cccfe9ce5e6d135276dbb837cec2e8b8eada40
Merge: e6d4c167a5 f5a308bb58
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sun Aug 21 16:26:11 2022 +0200

    Merge remote-tracking branch 'origin/fix/world-envelope-to-polygon' into geoapi-4.0.
    The fix, which adds intermediate points during the "envelope to JTS" conversion,
    is modified in two ways:
    
    - Instead of adding mid-points, add points on the edges of axis ranges (e.g. 180°E or 180°W).
    - Disabled for now, because generally not needed after SIS-547 and SIS-486 new fixes.
      We need to revisit what could be a public API for enabling on demand.

 .../apache/sis/internal/feature/Geometries.java    | 128 +++++++++++++++++----
 .../internal/feature/SpatialOperationContext.java  |  23 +++-
 .../apache/sis/internal/feature/package-info.java  |   2 +-
 .../sis/internal/filter/GeometryConverter.java     |  13 ++-
 .../apache/sis/internal/filter/package-info.java   |   2 +-
 .../sis/filter/BinarySpatialFilterTestCase.java    |  34 +++++-
 .../filter/BinarySpatialFilterUsingESRI_Test.java  |  11 +-
 .../BinarySpatialFilterUsingJava2D_Test.java       |  11 +-
 .../sis/internal/feature/GeometriesTestCase.java   |  21 +++-
 9 files changed, 212 insertions(+), 33 deletions(-)

diff --cc core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java
index 4a2a9a2b91,9a14b019db..1040107663
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java
@@@ -21,10 -21,11 +21,12 @@@ import java.io.Serializable
  import java.nio.ByteBuffer;
  import java.util.Optional;
  import java.util.Iterator;
 -import org.apache.sis.internal.referencing.WraparoundAxesFinder;
--import org.opengis.geometry.DirectPosition;
  import org.opengis.geometry.Envelope;
--import org.opengis.referencing.crs.CoordinateReferenceSystem;
++import org.opengis.geometry.DirectPosition;
++import org.opengis.geometry.MismatchedDimensionException;
  import org.opengis.referencing.cs.CoordinateSystem;
++import org.opengis.referencing.cs.CoordinateSystemAxis;
++import org.opengis.referencing.crs.CoordinateReferenceSystem;
  import org.apache.sis.geometry.AbstractEnvelope;
  import org.apache.sis.geometry.GeneralEnvelope;
  import org.apache.sis.geometry.WraparoundMethod;
@@@ -54,7 -55,7 +56,7 @@@ import org.apache.sis.util.Classes
   * @author  Johann Sorel (Geomatys)
   * @author  Martin Desruisseaux (Geomatys)
   * @author  Alexis Manin (Geomatys)
-- * @version 1.1
++ * @version 1.3
   * @since   0.7
   * @module
   */
@@@ -273,6 -274,6 +275,27 @@@ public abstract class Geometries<G> imp
       */
      public abstract GeometryWrapper<G> parseWKB(ByteBuffer data) throws Exception;
  
++    /**
++     * Creates and wraps a point from the given position.
++     *
++     * @param  point  the point to convert to a geometry.
++     * @return the given point converted to a geometry.
++     */
++    public final GeometryWrapper<G> createPoint(final DirectPosition point) {
++        final Object geometry;
++        final int n = point.getDimension();
++        switch (n) {
++            case 2: geometry = createPoint(point.getOrdinate(0), point.getOrdinate(1)); break;
++            case 3: geometry = createPoint(point.getOrdinate(0), point.getOrdinate(1), point.getOrdinate(2)); break;
++            default: throw new MismatchedDimensionException(Errors.format(Errors.Keys.MismatchedDimension_3, "point", (n <= 2) ? 2 : 3, n));
++        }
++        final GeometryWrapper<G> wrapper = castOrWrap(geometry);
++        if (point.getCoordinateReferenceSystem() != null) {
++            wrapper.setCoordinateReferenceSystem(point.getCoordinateReferenceSystem());
++        }
++        return wrapper;
++    }
++
      /**
       * Returns whether this library can produce geometry backed by the {@code float} primitive type
       * instead of the {@code double} primitive type. If single-precision mode is supported, using
@@@ -387,21 -388,41 +410,78 @@@
      /**
       * Creates a polyline made of points describing a rectangle whose start point is the lower left corner.
       * The sequence of points describes each corner, going in clockwise direction and repeating the starting
--     * point to properly close the ring.
 -     * In case a wrap-around ambiguity resides, control points are also added in the middle of the rectangle edges.
--     *
-      * @param  xd  dimension of first axis.
-      * @param  yd  dimension of second axis.
 -     * @param xd dimension of first axis.
 -     * @param yd dimension of second axis.
 -     * @param xPeriod Maximum span on <em>first</em> axis before triggering a wrap-around.
 -     *                If no wrap-around is possible, please set it to {@link Double#POSITIVE_INFINITY}.
 -     * @param yPeriod Maximum span on <em>second</em> axis before triggering a wrap-around.
 -     *                If no wrap-around is possible, please set it to {@link Double#POSITIVE_INFINITY}.
--     * @return a polyline made of a sequence of 5 points describing the given rectangle.
++     * point to properly close the ring. If wraparound may happen on at least one axis, then this method may
++     * add intermediate points on the axes where the envelope crosses the axis limit.
++     *
++     * @param  xd      dimension of first axis.
++     * @param  yd      dimension of second axis.
++     * @param  expand  whether to expand the envelope to full axis range if there is a wraparound.
++     * @param  addPts  whether to allow insertion of intermediate points on edges of axis domains.
++     * @return a polyline made of a sequence of at least 5 points describing the given rectangle.
       */
-     private GeometryWrapper<G> createGeometry2D(final Envelope envelope, final int xd, final int yd) {
 -    private GeometryWrapper<G> createGeometry2D(final Envelope envelope, final int xd, final int yd, double xPeriod, double yPeriod) {
--        final DirectPosition lc = envelope.getLowerCorner();
--        final DirectPosition uc = envelope.getUpperCorner();
--        final double xmin = lc.getOrdinate(xd);
--        final double ymin = lc.getOrdinate(yd);
--        final double xmax = uc.getOrdinate(xd);
--        final double ymax = uc.getOrdinate(yd);
-         return createWrapper(createPolyline(true, BIDIMENSIONAL, Vector.create(new double[] {
 -        final boolean applyXWrapAround = xPeriod / 2 < xmax - xmin;
 -        final boolean applyYWrapAround = yPeriod / 2 < ymax - ymin;
 -        if (applyXWrapAround && applyYWrapAround) {
 -            final double xmid = (xmin + xmax) / 2;
 -            final double ymid = (ymin + ymax) / 2;
 -            return createWrapper(createPolyline(true, BIDIMENSIONAL, Vector.create(new double[] {
 -                    xmin, ymin,  xmin, ymid,  xmin, ymax,  xmid, ymax,  xmax, ymid,  xmax, ymax,  xmax, ymid,  xmax, ymin,  xmid, ymin,  xmin, ymin})));
 -        } else if (applyXWrapAround) {
 -            final double xmid = (xmin + xmax) / 2;
 -            return createWrapper(createPolyline(true, BIDIMENSIONAL, Vector.create(new double[] {
 -                    xmin, ymin,  xmin, ymax,  xmid, ymax,  xmax, ymax,  xmax, ymin,  xmid, ymin,  xmin, ymin})));
 -        } else if (applyYWrapAround) {
 -            final double ymid = (ymin + ymax) / 2;
 -            return createWrapper(createPolyline(true, BIDIMENSIONAL, Vector.create(new double[] {
 -                    xmin, ymin,  xmin, ymid,  xmin, ymax,  xmax, ymax,  xmax, ymid,  xmax, ymin,  xmin, ymin})));
 -        } else return createWrapper(createPolyline(true, BIDIMENSIONAL, Vector.create(new double[] {
--                             xmin, ymin,  xmin, ymax,  xmax, ymax,  xmax, ymin,  xmin, ymin})));
++    private GeometryWrapper<G> createGeometry2D(final Envelope envelope, final int xd, final int yd,
++                                                final boolean expand, final boolean addPts)
++    {
++        final double  xmin, ymin, xmax, ymax;
++        if (expand) {
++            xmin = envelope.getMinimum(xd);
++            ymin = envelope.getMinimum(yd);
++            xmax = envelope.getMaximum(xd);
++            ymax = envelope.getMaximum(yd);
++        } else {
++            final DirectPosition lc = envelope.getLowerCorner();
++            final DirectPosition uc = envelope.getUpperCorner();
++            xmin = lc.getOrdinate(xd);
++            ymin = lc.getOrdinate(yd);
++            xmax = uc.getOrdinate(xd);
++            ymax = uc.getOrdinate(yd);
++        }
++        final double[] coordinates;
++        /*
++         * Find if some intermediate points need to be added. We add points only at the edges of axis domain,
++         * for example at 180°E or 180°W. Furthermore we add points only on axes having increasing values,
++         * i.e. we do not add points on axes using the "end point < start point" convention.
++         */
++        final CoordinateReferenceSystem crs;
++        if (addPts && (crs  = envelope.getCoordinateReferenceSystem()) != null) {
++            final double  xminIn,  yminIn,  xmaxIn,  ymaxIn;        // Intermediate min/max.
++            final boolean addXmin, addYmin, addXmax, addYmax;       // Whether to add intermediate min/max.
++            int n = 5*BIDIMENSIONAL;                                // Number of coordinate values.
++
++            final CoordinateSystem cs = crs.getCoordinateSystem();
++            CoordinateSystemAxis axis = cs.getAxis(xd);
++            xminIn = axis.getMinimumValue();
++            xmaxIn = axis.getMaximumValue();
++            axis   = cs.getAxis(yd);
++            yminIn = axis.getMinimumValue();
++            ymaxIn = axis.getMaximumValue();
++            final boolean addX = xmin <= xmax;      // Whether we can add intermediates X/Y.
++            final boolean addY = ymin <= ymax;
++            if (addXmin = (addX && xminIn > xmin)) n += 2*BIDIMENSIONAL;
++            if (addYmin = (addY && yminIn > ymin)) n += 2*BIDIMENSIONAL;
++            if (addXmax = (addX && xmaxIn < xmax)) n += 2*BIDIMENSIONAL;
++            if (addYmax = (addY && ymaxIn < ymax)) n += 2*BIDIMENSIONAL;
++
++            int i = 0;
++            coordinates = new double[n];
++            /*Envelope*/ {coordinates[i++] = xmin;    coordinates[i++] = ymin;}
++            if (addYmin) {coordinates[i++] = xmin;    coordinates[i++] = yminIn;}
++            if (addYmax) {coordinates[i++] = xmin;    coordinates[i++] = ymaxIn;}
++            /*Envelope*/ {coordinates[i++] = xmin;    coordinates[i++] = ymax;}
++            if (addXmin) {coordinates[i++] = xminIn;  coordinates[i++] = ymax;}
++            if (addXmax) {coordinates[i++] = xmaxIn;  coordinates[i++] = ymax;}
++            /*Envelope*/ {coordinates[i++] = xmax;    coordinates[i++] = ymax;}
++            if (addYmax) {coordinates[i++] = xmax;    coordinates[i++] = ymaxIn;}
++            if (addYmin) {coordinates[i++] = xmax;    coordinates[i++] = yminIn;}
++            /*Envelope*/ {coordinates[i++] = xmax;    coordinates[i++] = ymin;}
++            if (addXmax) {coordinates[i++] = xmaxIn;  coordinates[i++] = ymin;}
++            if (addXmin) {coordinates[i++] = xminIn;  coordinates[i++] = ymin;}
++            /*Envelope*/ {coordinates[i++] = xmin;    coordinates[i++] = ymin;}
++            assert i == n : i;
++        } else {
++            coordinates = new double[] {xmin, ymin,  xmin, ymax,  xmax, ymax,  xmax, ymin,  xmin, ymin};
++        }
++        return createWrapper(createPolyline(true, BIDIMENSIONAL, Vector.create(coordinates)));
      }
  
      /**
@@@ -446,27 -472,27 +526,31 @@@
              case NORMALIZE: {
                  throw new IllegalArgumentException();
              }
++            /*
++             * TODO: `addPts` is `false` in all cases. We have not yet determined
++             *       what could be a public API for enabling this option.
++             */
              case NONE: {
-                 result = createGeometry2D(envelope, xd, yd);
 -                result = createGeometry2D(envelope, xd, yd, xPeriod, yPeriod);
++                result = createGeometry2D(envelope, xd, yd, false, false);
                  break;
              }
              default: {
                  final GeneralEnvelope ge = new GeneralEnvelope(envelope);
                  ge.normalize();
                  ge.wraparound(strategy);
-                 result = createGeometry2D(ge, xd, yd);
 -                result = createGeometry2D(ge, xd, yd, xPeriod, yPeriod);
++                result = createGeometry2D(ge, xd, yd, true, false);
                  break;
              }
              case SPLIT: {
                  final Envelope[] parts = AbstractEnvelope.castOrCopy(envelope).toSimpleEnvelopes();
                  if (parts.length == 1) {
-                     result = createGeometry2D(parts[0], xd, yd);
 -                    result = createGeometry2D(parts[0], xd, yd, xPeriod, yPeriod);
++                    result = createGeometry2D(parts[0], xd, yd, true, false);
                      break;
                  }
                  @SuppressWarnings({"unchecked", "rawtypes"})
                  final GeometryWrapper<G>[] polygons = new GeometryWrapper[parts.length];
                  for (int i=0; i<parts.length; i++) {
-                     polygons[i] = createGeometry2D(parts[i], xd, yd);
 -                    polygons[i] = createGeometry2D(parts[i], xd, yd, xPeriod, yPeriod);
++                    polygons[i] = createGeometry2D(parts[i], xd, yd, true, false);
                      polygons[i].setCoordinateReferenceSystem(crs);
                  }
                  result = createMultiPolygon(polygons);
diff --cc core/sis-feature/src/main/java/org/apache/sis/internal/feature/SpatialOperationContext.java
index 82d090097a,82d090097a..5c08b642ef
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/SpatialOperationContext.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/SpatialOperationContext.java
@@@ -61,7 -61,7 +61,7 @@@ import org.opengis.filter.DistanceOpera
   * <p>The serialization form is not a committed API and may change in any future version.</p>
   *
   * @author  Martin Desruisseaux (Geomatys)
-- * @version 1.1
++ * @version 1.3
   * @since   1.1
   * @module
   */
@@@ -79,11 -79,11 +79,13 @@@ public final class SpatialOperationCont
      /**
       * Approximate geographic area of geometries, or {@code null} if unspecified.
       */
++    @SuppressWarnings("serial")         // Not statically typed as Serializable.
      private final GeographicBoundingBox areaOfInterest;
  
      /**
       * The target CRS in which to transform geometries, or {@code null} for inferring automatically.
       */
++    @SuppressWarnings("serial")         // Not statically typed as Serializable.
      private final CoordinateReferenceSystem computationCRS;
  
      /**
@@@ -92,6 -92,6 +94,7 @@@
       * Note that it does not mean that the units of measurement must be meters; only that they must
       * be compatible with meters.
       */
++    @SuppressWarnings("serial")         // Not statically typed as Serializable.
      private final Unit<?> systemUnit;
  
      /**
@@@ -103,6 -103,6 +106,7 @@@
      /**
       * The common CRS found by {@link #transform(GeometryWrapper[])}. May be null.
       */
++    @SuppressWarnings("serial")         // Not statically typed as Serializable.
      CoordinateReferenceSystem commonCRS;
  
      /**
@@@ -335,6 -335,6 +339,17 @@@ select: if (commonCRS == null) 
       * This is defined in a separated class for lazy static field initialization.
       */
      private static final class Projector {
++        /**
++         * Whether the operation {@linkplain #method} used by this projector can handle longitude wraparounds
++         * as a continuous mathematical function. It is the case of projections using longitude value only in
++         * trigonometric functions such as {@link Math#sin(double)}. It is <strong>not</strong> the case of
++         * Mercator projection, where wraparounds cause a sudden jump from big positive values to big negative
++         * values (or conversely).
++         *
++         * @see org.apache.sis.referencing.operation.projection.LongitudeWraparound
++         */
++        private static final boolean CONTINUOUS_WRAPAROUND = false;
++
          /** A singleton map containing the name to assign to the CRS. */
          private final Map<String,?> name;
  
@@@ -401,10 -401,10 +416,14 @@@
              }
              /*
               * Create a projected coordinate reference system for the geometry center.
++             * The central meridian should be set only if it does not cause insertion
++             * of `LongitudeWraparound` transform.
               */
              final ParameterValueGroup p = method.getParameters().createValue();
              p.parameter(Constants.STANDARD_PARALLEL_1).setValue(latitude);
--            p.parameter(Constants.CENTRAL_MERIDIAN).setValue(longitude);
++            if (CONTINUOUS_WRAPAROUND) {
++                p.parameter(Constants.CENTRAL_MERIDIAN).setValue(longitude);
++            }
              final DefaultConversion conversion = new DefaultConversion(name, method, null, p);
              return new DefaultProjectedCRS(name, baseCRS, conversion, cartCS);
          }
diff --cc core/sis-feature/src/main/java/org/apache/sis/internal/feature/package-info.java
index 4e460ef936,4e460ef936..2bb87081ab
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/package-info.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/package-info.java
@@@ -28,7 -28,7 +28,7 @@@
   * @author  Johann Sorel (Geomatys)
   * @author  Martin Desruisseaux (Geomatys)
   * @author  Alexis Manin (Geomatys)
-- * @version 1.2
++ * @version 1.3
   * @since   0.7
   * @module
   */
diff --cc core/sis-feature/src/main/java/org/apache/sis/internal/filter/GeometryConverter.java
index 2d362d29b5,1fdfc0ca4c..435dce5cbf
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/GeometryConverter.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/GeometryConverter.java
@@@ -19,8 -19,9 +19,10 @@@ package org.apache.sis.internal.filter
  import java.util.List;
  import java.util.Collection;
  import java.util.Collections;
 -import org.opengis.geometry.DirectPosition;
  import org.opengis.util.ScopedName;
  import org.opengis.geometry.Envelope;
++import org.opengis.geometry.DirectPosition;
++import org.opengis.geometry.MismatchedDimensionException;
  import org.opengis.metadata.extent.GeographicBoundingBox;
  import org.apache.sis.util.Classes;
  import org.apache.sis.util.ArgumentChecks;
@@@ -42,7 -43,7 +44,7 @@@ import org.opengis.filter.InvalidFilter
   *
   * @author  Martin Desruisseaux (Geomatys)
   * @author  Alexis Manin (Geomatys)
-- * @version 1.1
++ * @version 1.3
   *
   * @param  <R>  the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
   * @param  <G>  the geometry implementation type.
@@@ -73,6 -74,6 +75,7 @@@ final class GeometryConverter<R,G> exte
       *
       * @see #getParameters()
       */
++    @SuppressWarnings("serial")         // Not statically typed as Serializable.
      final Expression<? super R, ?> expression;
  
      /**
@@@ -149,9 -150,21 +152,13 @@@
              envelope = new ImmutableEnvelope((GeographicBoundingBox) value);
          } else if (value instanceof Envelope) {
              envelope = (Envelope) value;
 -        } else if (value instanceof DirectPosition) {
 -            final DirectPosition pt = (DirectPosition) value;
 -            final Object geometry;
 -            if (pt.getDimension() == 2) {
 -                geometry = library.createPoint(pt.getOrdinate(0), pt.getOrdinate(1));
 -            } else if (pt.getDimension() == 3) {
 -                geometry = library.createPoint(pt.getOrdinate(0), pt.getOrdinate(1), pt.getOrdinate(2));
 -            } else throw new InvalidFilterValueException(Errors.format(
 -                        Errors.Keys.IllegalClass_2, library.rootClass, Classes.getClass(value)));
 -            final GeometryWrapper<G> wrapper = library.castOrWrap(geometry);
 -            if (pt.getCoordinateReferenceSystem() != null) wrapper.setCoordinateReferenceSystem(pt.getCoordinateReferenceSystem());
 -            return wrapper;
          } else try {
--            return library.castOrWrap(value);
--        } catch (ClassCastException e) {
++            if (value instanceof DirectPosition) {
++                return library.createPoint((DirectPosition) value);
++            } else {
++                return library.castOrWrap(value);
++            }
++        } catch (ClassCastException | MismatchedDimensionException e) {
              throw new InvalidFilterValueException(Errors.format(
                      Errors.Keys.IllegalClass_2, library.rootClass, Classes.getClass(value)), e);
          }
diff --cc core/sis-feature/src/main/java/org/apache/sis/internal/filter/package-info.java
index 915385a223,915385a223..bf70a5ca86
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/filter/package-info.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/filter/package-info.java
@@@ -20,7 -20,7 +20,7 @@@
   *
   * @author  Johann Sorel (Geomatys)
   * @author  Martin Desruisseaux (Geomatys)
-- * @version 1.1
++ * @version 1.3
   * @since   1.1
   * @module
   */
diff --cc core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterTestCase.java
index 5879bc6d82,be5f44f78e..0526fc1c7a
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterTestCase.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterTestCase.java
@@@ -18,9 -18,14 +18,14 @@@ package org.apache.sis.filter
  
  import javax.measure.Quantity;
  import javax.measure.quantity.Length;
 -import org.apache.sis.geometry.DirectPosition2D;
 -import org.apache.sis.geometry.GeneralEnvelope;
 -import org.apache.sis.setup.GeometryLibrary;
 -import org.apache.sis.test.Assume;
 -import org.opengis.filter.DistanceOperatorName;
  import org.opengis.geometry.Envelope;
++import org.opengis.util.FactoryException;
++import org.opengis.referencing.operation.TransformException;
  import org.opengis.referencing.crs.CoordinateReferenceSystem;
++import org.opengis.referencing.crs.GeographicCRS;
  import org.apache.sis.geometry.Envelope2D;
++import org.apache.sis.geometry.GeneralEnvelope;
++import org.apache.sis.geometry.DirectPosition2D;
  import org.apache.sis.geometry.WraparoundMethod;
  import org.apache.sis.internal.feature.Geometries;
  import org.apache.sis.measure.Quantities;
@@@ -41,6 -46,6 +46,7 @@@ import org.opengis.filter.Expression
  import org.opengis.filter.Literal;
  import org.opengis.filter.FilterFactory;
  import org.opengis.filter.DistanceOperator;
++import org.opengis.filter.DistanceOperatorName;
  import org.opengis.filter.BinarySpatialOperator;
  
  
@@@ -51,7 -56,7 +57,7 @@@
   * @author  Johann Sorel (Geomatys)
   * @author  Martin Desruisseaux (Geomatys)
   * @author  Alexis Manin (Geomatys)
-- * @version 1.1
++ * @version 1.3
   *
   * @param  <G> root class of geometry implementation.
   *
@@@ -347,4 -352,22 +353,30 @@@ public abstract strictfp class BinarySp
          BinarySpatialOperator<Feature> overlaps = factory.overlaps(literal(Polygon.CONTAINS), right);
          assertSerializedEquals(overlaps);
      }
+ 
+     /**
+      * Ensures that a world geographic envelope, once converted to a polygon and reprojected, remain coherent.
 -     * This is a regression test. In the past, the operation pipeline [envelope -> polygon -> reprojected polygon]
++     * This is a regression test. In the past, the operation pipeline [envelope → polygon → reprojected polygon]
+      * caused the result to degenerate to single line following the anti-meridian.
++     *
++     * @throws FactoryException if an error occurred while fetching a CRS.
++     * @throws TransformException if a coordinate conversion was required but failed.
+      */
+     @Test
 -    public void testSpatialContextDoesNotDegenerateEnvelope() throws Exception {
 -        Assume.assumeTrue("Require reprojection. Only supported for JTS for now", library.library == GeometryLibrary.JTS);
 -        final Envelope e1 = new Envelope2D(HardCodedCRS.WGS84, -180, -90, 360, 180);
++    public void testSpatialContextDoesNotDegenerateEnvelope() throws FactoryException, TransformException {
++        final GeographicCRS sourceCRS = HardCodedCRS.WGS84;
++        final Envelope e1 = new Envelope2D(sourceCRS, -180, -90, 360, 180);
+         final DistanceFilter<?, G> within = new DistanceFilter<>(DistanceOperatorName.WITHIN,
+                 library, factory.literal(e1),
 -                factory.literal(new DirectPosition2D(HardCodedCRS.WGS84, 44, 2)),
++                factory.literal(new DirectPosition2D(sourceCRS, 44, 2)),
+                 Quantities.create(1.0, Units.METRE));
+ 
+         final GeneralEnvelope envInCtx = within.context.transform(within.expression1.apply(null)).getEnvelope();
 -        assertNotEquals(envInCtx.getMinimum(0), envInCtx.getMaximum(0), 1000);
++        final double xmin = envInCtx.getMinimum(0);
++        final double xmax = envInCtx.getMaximum(0);
++        assertNotEquals("Degenerated envelope.", xmin, xmax, 1000);
++
++        final double expected = sourceCRS.getDatum().getEllipsoid().getSemiMajorAxis() * (2*Math.PI);
++        assertEquals(expected, xmax - xmin, expected / 1000);
+     }
  }
diff --cc core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterUsingESRI_Test.java
index ed3ae6ae4f,ed3ae6ae4f..bb7332439e
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterUsingESRI_Test.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterUsingESRI_Test.java
@@@ -26,7 -26,7 +26,7 @@@ import org.junit.Test
   *
   * @author  Johann Sorel (Geomatys)
   * @author  Martin Desruisseaux (Geomatys)
-- * @version 1.1
++ * @version 1.3
   * @since   1.1
   * @module
   */
@@@ -64,4 -64,4 +64,13 @@@ public final strictfp class BinarySpati
      @Ignore("Not yet mapped to an ESRI operation.")
      public void testWithReprojection() {
      }
++
++    /**
++     * Test ignored for now (not yet mapped to an ESRI operation).
++     */
++    @Test
++    @Override
++    @Ignore("Requires geometry reprojection.")
++    public void testSpatialContextDoesNotDegenerateEnvelope() {
++    }
  }
diff --cc core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterUsingJava2D_Test.java
index 5fccd95640,5fccd95640..0dd298f1bc
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterUsingJava2D_Test.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/BinarySpatialFilterUsingJava2D_Test.java
@@@ -25,7 -25,7 +25,7 @@@ import org.junit.Test
   * Tests {@link BinarySpatialFilter} implementations using ESRI library.
   *
   * @author  Martin Desruisseaux (Geomatys)
-- * @version 1.1
++ * @version 1.3
   * @since   1.1
   * @module
   */
@@@ -91,6 -91,6 +91,15 @@@ public final strictfp class BinarySpati
      public void testWithReprojection() {
      }
  
++    /**
++     * Test ignored for now (not yet mapped to a Java2D operation).
++     */
++    @Test
++    @Override
++    @Ignore("Requires geometry reprojection.")
++    public void testSpatialContextDoesNotDegenerateEnvelope() {
++    }
++
      /**
       * Test ignored for now because {@link java.awt.geom.Path2D} does not implement {@code equals(Object)}.
       */
diff --cc core/sis-feature/src/test/java/org/apache/sis/internal/feature/GeometriesTestCase.java
index 73a9d0c714,a0633569e2..59acd7822e
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/GeometriesTestCase.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/GeometriesTestCase.java
@@@ -17,9 -17,10 +17,11 @@@
  package org.apache.sis.internal.feature;
  
  import java.util.Arrays;
++import java.util.EnumSet;
  import java.util.Iterator;
 -import org.apache.sis.geometry.Envelope2D;
  import org.opengis.geometry.Envelope;
  import org.apache.sis.setup.GeometryLibrary;
++import org.apache.sis.geometry.Envelope2D;
  import org.apache.sis.geometry.GeneralEnvelope;
  import org.apache.sis.geometry.WraparoundMethod;
  import org.apache.sis.referencing.crs.HardCodedCRS;
@@@ -37,7 -38,7 +39,7 @@@ import static org.junit.Assert.*
   *
   * @author  Martin Desruisseaux (Geomatys)
   * @author  Alexis Manin (Geomatys)
-- * @version 1.1
++ * @version 1.3
   * @since   1.0
   * @module
   */
@@@ -218,13 -223,30 +220,30 @@@ public abstract strictfp class Geometri
          e.setRange(1, 1000, 1007);      // Time
          e.setRange(2,    2,    3);      // Latitude
          e.setRange(3,   89,   19);      // Longitude (span anti-meridian).
 -
 -        assertToGeometryEquals(e, WraparoundMethod.NONE,       2, 89,  2, 19,  3, 19,  3, 89,  2, 89);
 -
 -        assertToGeometryEquals(e, WraparoundMethod.CONTIGUOUS, 2, -271,  2, -126,  2, 19,  3, 19,  3, -126,  3, -271, 2, -271);
 -
 -        assertToGeometryEquals(e, WraparoundMethod.EXPAND,     2, -180,  2, 0,  2, 180,  3, 180,  3, 0,  3, -180,  2, -180);
 -
 -        assertToGeometryEquals(e, WraparoundMethod.SPLIT,      2, 89,  2, 180,  3, 180,  3, 89,  2, 89,
 -                                                               2, -180,  2, -80.5,  2, 19,  3, 19, 3,  -80.5,  3, -180,  2, -180);
 +        assertToGeometryEquals(e, WraparoundMethod.NONE,       2,   89, 2,  19, 3,  19, 3,   89, 2,   89);
 +        assertToGeometryEquals(e, WraparoundMethod.CONTIGUOUS, 2, -271, 2,  19, 3,  19, 3, -271, 2, -271);
 +        assertToGeometryEquals(e, WraparoundMethod.EXPAND,     2, -180, 2, 180, 3, 180, 3, -180, 2, -180);
 +        assertToGeometryEquals(e, WraparoundMethod.SPLIT,      2,   89, 2, 180, 3, 180, 3,   89, 2,   89,
 +                                                               2, -180, 2,  19, 3,  19, 3, -180, 2, -180);
      }
  
++    /**
++     * Tests conversion of a world-wide envelope using all supported wraparound methods.
++     */
+     @Test
 -    public void testWorldWGS84ToGeometry2D() {
++    public void testWorldToGeometry2D() {
++        final EnumSet<WraparoundMethod> methods = EnumSet.allOf(WraparoundMethod.class);
++        assertTrue(methods.remove(WraparoundMethod.NORMALIZE));
+         Envelope2D env2d = new Envelope2D(HardCodedCRS.WGS84, -180, -90, 360, 180);
 -        for (WraparoundMethod method : new WraparoundMethod[] { WraparoundMethod.NONE, WraparoundMethod.CONTIGUOUS, WraparoundMethod.EXPAND, WraparoundMethod.SPLIT}) {
 -            assertToGeometryEquals(env2d, method, -180, -90,  -180, 90,  0, 90,  180, 90,  180, -90,  0, -90,  -180, -90);
++        for (WraparoundMethod method : methods) {
++            assertToGeometryEquals(env2d, method, -180, -90,  -180, 90,  180, 90,  180, -90, -180, -90);
+         }
 -
+         env2d = new Envelope2D(HardCodedCRS.WGS84_LATITUDE_FIRST, -90, -180, 180, 360);
 -        for (WraparoundMethod method : new WraparoundMethod[] { WraparoundMethod.NONE, WraparoundMethod.CONTIGUOUS, WraparoundMethod.EXPAND, WraparoundMethod.SPLIT}) {
 -            assertToGeometryEquals(env2d, method, -90, -180,  -90, 0,  -90, 180,  90, 180,  90, 0,  90, -180,  -90, -180);
++        for (WraparoundMethod method : methods) {
++            assertToGeometryEquals(env2d, method, -90, -180,  -90, 180,  90, 180,  90, -180,  -90, -180);
+         }
+     }
+ 
      /**
       * Verifies that call to {@link Geometries#toGeometry2D(Envelope, WraparoundMethod)}
       * with the given argument values produces a geometry will all expected coordinates.