sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1813531 [1/3] - in /sis/trunk: ./ core/sis-feature/src/main/java/org/apache/sis/feature/builder/ core/sis-feature/src/main/java/org/apache/sis/internal/feature/ core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/ core/sis-r...
Date Fri, 27 Oct 2017 12:58:40 GMT
Author: desruisseaux
Date: Fri Oct 27 12:58:39 2017
New Revision: 1813531

URL: http://svn.apache.org/viewvc?rev=1813531&view=rev
Log:
Merge from JDK7 branch.

Added:
    sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java
      - copied, changed from r1813524, sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftMethod.java
      - copied unchanged from r1813524, sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftMethod.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbridgedMolodenskyTransform2D.java
      - copied unchanged from r1813524, sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbridgedMolodenskyTransform2D.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/CoordinateOperationsTest.java
      - copied unchanged from r1813524, sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/CoordinateOperationsTest.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/AbridgedMolodenskyTransformTest.java
      - copied, changed from r1813524, sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/AbridgedMolodenskyTransformTest.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/
      - copied from r1813524, sis/branches/JDK7/core/sis-utility/src/main/java/org/apache/sis/internal/jdk9/
    sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/StoreFormat.java
      - copied unchanged from r1813524, sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/StoreFormat.java
    sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/Query.java
      - copied, changed from r1813524, sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/Query.java
    sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/UnsupportedQueryException.java
      - copied unchanged from r1813524, sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/storage/UnsupportedQueryException.java
Modified:
    sis/trunk/   (props changed)
    sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java
    sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/ESRI.java
    sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java
    sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java
    sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Java2D.java
    sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultBoundingPolygon.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Shapes2D.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/CoordinateOperations.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineMatrix.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Affine.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationFinder.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CoordinateOperationRegistry.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultConcatenatedOperation.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/DefaultCoordinateOperationFactory.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/MathTransformContext.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MolodenskyTransform.java
    sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/MolodenskyTransform2D.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/geometry/AbstractEnvelopeTest.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/geometry/EnvelopesTest.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/geometry/GeneralEnvelopeTest.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/geometry/Shapes2DTest.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/geometry/TransformTestCase.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/HardCodedConversions.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/MathTransformTestCase.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/test/ReferencingAssert.java
    sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/system/Supervisor.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java
    sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/collection/IntegerList.java
    sis/trunk/core/sis-utility/src/test/java/org/apache/sis/test/Assert.java
    sis/trunk/core/sis-utility/src/test/java/org/apache/sis/util/collection/IntegerListTest.java
    sis/trunk/ide-project/NetBeans/nbproject/genfiles.properties
    sis/trunk/ide-project/NetBeans/nbproject/project.xml
    sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/FeaturesInfo.java
    sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/FeaturesWrapper.java
    sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/AttributeNames.java
    sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
    sis/trunk/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/DecoderTest.java
    sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java
    sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
    sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
    sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
    sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
    sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java
    sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/FeatureSet.java
    sis/trunk/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java

Propchange: sis/trunk/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Fri Oct 27 12:58:39 2017
@@ -1,5 +1,5 @@
 /sis/branches/Android:1430670-1480699
 /sis/branches/JDK6:1394364-1758914
-/sis/branches/JDK7:1394913-1812269
-/sis/branches/JDK8:1584960-1812266
+/sis/branches/JDK7:1394913-1813524
+/sis/branches/JDK8:1584960-1813514
 /sis/branches/JDK9:1773327-1803064

Modified: sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java?rev=1813531&r1=1813530&r2=1813531&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/main/java/org/apache/sis/feature/builder/FeatureTypeBuilder.java [UTF-8] Fri Oct 27 12:58:39 2017
@@ -201,6 +201,10 @@ public class FeatureTypeBuilder extends
 
     /**
      * Creates a new builder instance using the given feature type as a template.
+     * This constructor initializes the list of {@linkplain #properties() properties}, the
+     * {@linkplain #getSuperTypes() super types} and {@link #isAbstract() isAbstract} flag
+     * to values inferred from the given template. The properties list will contain properties
+     * declared explicitely in the given template, not including properties inherited from super types.
      *
      * <div class="warning"><b>Warning:</b>
      * The {@code template} argument type will be changed to {@code FeatureType} if and when such interface
@@ -264,6 +268,7 @@ public class FeatureTypeBuilder extends
     /**
      * Sets all properties of this builder to the values of the given feature type.
      * This builder is {@linkplain #clear() cleared} before the properties of the given type are copied.
+     * The copy is performed as documented in the {@linkplain #FeatureTypeBuilder(DefaultFeatureType) constructor}.
      *
      * <div class="warning"><b>Warning:</b>
      * The {@code template} argument type will be changed to {@code FeatureType} if and when such interface
@@ -609,10 +614,12 @@ public class FeatureTypeBuilder extends
 
     /**
      * Returns a view of all attributes and associations added to the {@code FeatureType} to build.
+     * This list contains only properties declared explicitely to this builder;
+     * it does not include properties inherited from {@linkplain #getSuperTypes() super-types}.
      * The returned list is <cite>live</cite>: changes in this builder are reflected in that list and conversely.
      * However the returned list allows only {@linkplain List#remove(Object) remove} operations;
      * new attributes or associations can be added only by calls to one of the {@code addAttribute(…)}
-     * or {@code addAssociation(…)} methods.
+     * or {@code addAssociation(…)} methods. Removal operations never affect the super-types.
      *
      * @return a live list over the properties declared to this builder.
      *

Modified: sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/ESRI.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/ESRI.java?rev=1813531&r1=1813530&r2=1813531&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/ESRI.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/ESRI.java [UTF-8] Fri Oct 27 12:58:39 2017
@@ -23,6 +23,8 @@ import com.esri.core.geometry.MultiPath;
 import com.esri.core.geometry.Polyline;
 import com.esri.core.geometry.Polygon;
 import com.esri.core.geometry.Point;
+import com.esri.core.geometry.WktImportFlags;
+import com.esri.core.geometry.OperatorImportFromWkt;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.setup.GeometryLibrary;
 import org.apache.sis.math.Vector;
@@ -178,4 +180,12 @@ final class ESRI extends Geometries<Geom
         }
         return path;
     }
+
+    /**
+     * Parses the given WKT.
+     */
+    @Override
+    public Object parseWKT(final String wkt) {
+        return OperatorImportFromWkt.local().execute(WktImportFlags.wktImportDefaults, Geometry.Type.Unknown, wkt, null);
+    }
 }

Modified: sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java?rev=1813531&r1=1813530&r2=1813531&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java [UTF-8] Fri Oct 27 12:58:39 2017
@@ -284,6 +284,14 @@ public abstract class Geometries<G> {
     }
 
     /**
+     * Parses the given WKT.
+     *
+     * @param  wkt  the WKT to parse.
+     * @return the geometry object for the given WKT.
+     */
+    public abstract Object parseWKT(String wkt);
+
+    /**
      * Returns an error message for an unsupported geometry object.
      *
      * @param  dimension  number of dimensions (2 or 3) requested for the geometry object.

Copied: sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java (from r1813524, sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java)
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java?p2=sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java&p1=sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java&r1=1813524&r2=1813531&rev=1813531&view=diff
==============================================================================
--- sis/branches/JDK7/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/GeometryWrapper.java [UTF-8] Fri Oct 27 12:58:39 2017
@@ -16,17 +16,9 @@
  */
 package org.apache.sis.internal.feature;
 
-import java.util.Set;
 import java.util.Objects;
-import org.opengis.geometry.Boundary;
-import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.Geometry;
-import org.opengis.geometry.Precision;
-import org.opengis.geometry.TransfiniteSet;
-import org.opengis.geometry.complex.Complex;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.operation.MathTransform;
 
 
 /**
@@ -60,50 +52,7 @@ public final class GeometryWrapper imple
         this.envelope = envelope;
     }
 
-    /**
-     * Returns the geometry CRS, which is taken from the envelope CRS.
-     *
-     * @return the geometry CRS.
-     */
-    @Override
-    public CoordinateReferenceSystem getCoordinateReferenceSystem() {
-        return envelope.getCoordinateReferenceSystem();
-    }
-
-    /**
-     * Returns the envelope specified at construction time.
-     */
-    @Override public Envelope getEnvelope() {
-        return envelope;
-    }
-
-    @Override public Precision      getPrecision()                            {throw new UnsupportedOperationException();}
-    @Override public Geometry       getMbRegion()                             {throw new UnsupportedOperationException();}
-    @Override public DirectPosition getRepresentativePoint()                  {throw new UnsupportedOperationException();}
-    @Override public Boundary       getBoundary()                             {throw new UnsupportedOperationException();}
-    @Override public Complex        getClosure()                              {throw new UnsupportedOperationException();}
-    @Override public boolean        isSimple()                                {throw new UnsupportedOperationException();}
-    @Override public boolean        isCycle()                                 {throw new UnsupportedOperationException();}
-    @Override public double         distance(Geometry geometry)               {throw new UnsupportedOperationException();}
-    @Override public int            getDimension(DirectPosition point)        {throw new UnsupportedOperationException();}
-    @Override public int            getCoordinateDimension()                  {throw new UnsupportedOperationException();}
-    @Override public Set<Complex>   getMaximalComplex()                       {throw new UnsupportedOperationException();}
-    @Override public DirectPosition getCentroid()                             {throw new UnsupportedOperationException();}
-    @Override public Geometry       getConvexHull()                           {throw new UnsupportedOperationException();}
-    @Override public Geometry       getBuffer(double distance)                {throw new UnsupportedOperationException();}
-    @Override public boolean        isMutable()                               {throw new UnsupportedOperationException();}
-    @Override public Geometry       toImmutable()                             {throw new UnsupportedOperationException();}
     @Override public Geometry       clone() throws CloneNotSupportedException {throw new CloneNotSupportedException();}
-    @Override public boolean        contains(TransfiniteSet pointSet)         {throw new UnsupportedOperationException();}
-    @Override public boolean        contains(DirectPosition point)            {throw new UnsupportedOperationException();}
-    @Override public boolean        intersects(TransfiniteSet pointSet)       {throw new UnsupportedOperationException();}
-    @Override public boolean        equals(TransfiniteSet pointSet)           {throw new UnsupportedOperationException();}
-    @Override public TransfiniteSet union(TransfiniteSet pointSet)            {throw new UnsupportedOperationException();}
-    @Override public TransfiniteSet intersection(TransfiniteSet pointSet)     {throw new UnsupportedOperationException();}
-    @Override public TransfiniteSet difference(TransfiniteSet pointSet)       {throw new UnsupportedOperationException();}
-    @Override public TransfiniteSet symmetricDifference(TransfiniteSet ps)    {throw new UnsupportedOperationException();}
-    @Override public Geometry       transform(CoordinateReferenceSystem crs)  {throw new UnsupportedOperationException();}
-    @Override public Geometry       transform(CoordinateReferenceSystem crs, MathTransform tr) {throw new UnsupportedOperationException();}
 
     @Override
     public boolean equals(final Object obj) {

Modified: sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java?rev=1813531&r1=1813530&r2=1813531&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java [UTF-8] Fri Oct 27 12:58:39 2017
@@ -146,4 +146,12 @@ final class JTS extends Geometries<Objec
     final Object tryMergePolylines(final Object first, final Iterator<?> polylines) {
         throw unsupported(2);   // TODO - see class javadoc
     }
+
+    /**
+     * Parses the given WKT.
+     */
+    @Override
+    public Object parseWKT(final String wkt) {
+        throw unsupported(2);
+    }
 }

Modified: sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Java2D.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Java2D.java?rev=1813531&r1=1813530&r2=1813531&view=diff
==============================================================================
--- sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Java2D.java [UTF-8] (original)
+++ sis/trunk/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Java2D.java [UTF-8] Fri Oct 27 12:58:39 2017
@@ -214,4 +214,12 @@ final class Java2D extends Geometries<Sh
         }
         return ShapeUtilities.toPrimitive(path);
     }
+
+    /**
+     * Parses the given WKT.
+     */
+    @Override
+    public Object parseWKT(final String wkt) {
+        throw unsupported(2);
+    }
 }

Modified: sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultBoundingPolygon.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultBoundingPolygon.java?rev=1813531&r1=1813530&r2=1813531&view=diff
==============================================================================
--- sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultBoundingPolygon.java [UTF-8] (original)
+++ sis/trunk/core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/DefaultBoundingPolygon.java [UTF-8] Fri Oct 27 12:58:39 2017
@@ -47,7 +47,7 @@ import org.opengis.metadata.extent.Bound
  * @author  Touraïvane (IRD)
  * @author  Cédric Briançon (Geomatys)
  * @author  Guilhem Legal (Geomatys)
- * @version 0.3
+ * @version 0.8
  * @since   0.3
  * @module
  */
@@ -77,6 +77,7 @@ public class DefaultBoundingPolygon exte
      * @param polygon  the sets of points defining the bounding polygon.
      */
     public DefaultBoundingPolygon(final Geometry polygon) {
+        super(true);
         polygons = singleton(polygon, Geometry.class);
     }
 

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java?rev=1813531&r1=1813530&r2=1813531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/AbstractEnvelope.java [UTF-8] Fri Oct 27 12:58:39 2017
@@ -45,8 +45,9 @@ import static org.apache.sis.util.Argume
 import static org.apache.sis.util.ArgumentChecks.ensureDimensionMatches;
 import static org.apache.sis.util.StringBuilders.trimFractionalPart;
 import static org.apache.sis.math.MathFunctions.epsilonEqual;
-import static org.apache.sis.math.MathFunctions.isNegative;
 import static org.apache.sis.math.MathFunctions.isPositive;
+import static org.apache.sis.math.MathFunctions.isNegative;
+import static org.apache.sis.math.MathFunctions.isNegativeZero;
 
 
 /**
@@ -819,10 +820,12 @@ public abstract class AbstractEnvelope i
                     // Not the excluded case, go to next dimension.
                     continue;
                 }
-                // If this envelope does not span the anti-meridian but the given envelope
-                // does, we don't contain the given envelope except in the special case
-                // where the envelope spanning is equals or greater than the axis spanning
-                // (including the case where this envelope expands to infinities).
+                /*
+                 * If this envelope does not span the anti-meridian but the given envelope does,
+                 * then this envelope does not contain the given envelope except in the special
+                 * case where the envelope spanning is equals or greater than the axis spanning
+                 * (including the case where this envelope expands to infinities).
+                 */
                 if ((lower0 == Double.NEGATIVE_INFINITY && upper0 == Double.POSITIVE_INFINITY) ||
                     (upper0 - lower0 >= getSpan(getAxis(getCoordinateReferenceSystem(), i))))
                 {
@@ -846,6 +849,14 @@ public abstract class AbstractEnvelope i
                         continue;
                     }
                 }
+            } else if (isNegativeZero(upper0 - lower0)) {
+                /*      !upperCnd
+                 *  ────────┬────────
+                 *        ┌─┼────┐
+                 *        └─┼────┘
+                 *  ────────┴────────
+                 *      !lowerCnd */
+                continue;
             }
             return false;
         }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java?rev=1813531&r1=1813530&r2=1813531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelope2D.java [UTF-8] Fri Oct 27 12:58:39 2017
@@ -32,9 +32,10 @@ import org.apache.sis.util.Emptiable;
 import static java.lang.Double.NaN;
 import static java.lang.Double.isNaN;
 import static java.lang.Double.doubleToLongBits;
+import static org.apache.sis.math.MathFunctions.isSameSign;
 import static org.apache.sis.math.MathFunctions.isPositive;
 import static org.apache.sis.math.MathFunctions.isNegative;
-import static org.apache.sis.math.MathFunctions.isSameSign;
+import static org.apache.sis.math.MathFunctions.isNegativeZero;
 import static org.apache.sis.util.ArgumentChecks.ensureDimensionMatches;
 import static org.apache.sis.internal.referencing.Formulas.isPoleToPole;
 
@@ -768,13 +769,15 @@ public class Envelope2D extends Rectangl
                 if (!isNegativeUnsafe(span1) || isNegativeUnsafe(span0)) {
                     continue;
                 }
-                if (span0 >= AbstractEnvelope.getSpan(getAxis(getCoordinateReferenceSystem(), i))) {
+                if (span0 >= AbstractEnvelope.getSpan(getAxis(crs, i))) {
                     continue;
                 }
             } else if (minCondition != maxCondition) {
                 if (isNegative(span0) && isPositive(span1)) {
                     continue;
                 }
+            } else if (isNegativeZero(span0)) {
+                continue;
             }
             return false;
         }
@@ -879,8 +882,13 @@ public class Envelope2D extends Rectangl
                 min1  = rect.getY();
                 span1 = (env != null) ? env.height : rect.getHeight();
             }
-            final double max0 = min0 + span0;
-            final double max1 = min1 + span1;
+            /*
+             * The purpose for (min != 0) test before addition is to preserve the sign of zero.
+             * In the [0 … -0] range, the span is -0. But computing max = 0 + -0 result in +0,
+             * while we need max = -0 in this case.
+             */
+            final double max0 = (min0 != 0) ? min0 + span0 : span0;
+            final double max1 = (min1 != 0) ? min1 + span1 : span1;
             double min = Math.max(min0, min1);
             double max = Math.min(max0, max1);
             /*
@@ -903,10 +911,10 @@ public class Envelope2D extends Rectangl
                 }
                 if (intersect == 0 || intersect == 3) {
                     final double csSpan = AbstractEnvelope.getSpan(getAxis(crs, i));
-                    if (span1 >= csSpan) {
+                    if (span1 >= csSpan || isNegativeZero(span1)) {
                         min = min0;
                         max = max0;
-                    } else if (span0 >= csSpan) {
+                    } else if (span0 >= csSpan || isNegativeZero(span0)) {
                         min = min1;
                         max = max1;
                     } else {

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java?rev=1813531&r1=1813530&r2=1813531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Envelopes.java [UTF-8] Fri Oct 27 12:58:39 2017
@@ -21,10 +21,10 @@ package org.apache.sis.geometry;
  * support Java2D (e.g. Android),  or applications that do not need it may want to avoid to
  * force installation of the Java2D module (e.g. JavaFX/SWT).
  */
+import java.util.Set;
 import org.opengis.geometry.Envelope;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.referencing.cs.RangeMeaning;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
@@ -40,6 +40,7 @@ import org.apache.sis.util.ComparisonMod
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.operation.AbstractCoordinateOperation;
 import org.apache.sis.referencing.operation.transform.AbstractMathTransform;
 import org.apache.sis.internal.referencing.CoordinateOperations;
 import org.apache.sis.internal.referencing.DirectPositionView;
@@ -86,7 +87,7 @@ import static org.apache.sis.util.String
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @version 0.5
+ * @version 0.8
  *
  * @see org.apache.sis.metadata.iso.extent.Extents
  * @see CRS
@@ -102,15 +103,6 @@ public final class Envelopes extends Sta
     }
 
     /**
-     * Returns {@code true} if the given axis is of kind "Wrap Around".
-     *
-     * @see AbstractEnvelope#isWrapAround(CoordinateSystemAxis)
-     */
-    static boolean isWrapAround(final CoordinateSystemAxis axis) {
-        return RangeMeaning.WRAPAROUND.equals(axis.getRangeMeaning());
-    }
-
-    /**
      * Invoked when a recoverable exception occurred. Those exceptions must be minor enough
      * that they can be silently ignored in most cases.
      */
@@ -446,6 +438,7 @@ public final class Envelopes extends Sta
         if (envelope == null) {
             return null;
         }
+        boolean isOperationComplete = true;
         final CoordinateReferenceSystem sourceCRS = operation.getSourceCRS();
         if (sourceCRS != null) {
             final CoordinateReferenceSystem crs = envelope.getCoordinateReferenceSystem();
@@ -465,6 +458,7 @@ public final class Envelopes extends Sta
                     throw new TransformException(Errors.format(Errors.Keys.CanNotTransformEnvelope), e);
                 }
                 if (!mt.isIdentity()) {
+                    isOperationComplete = false;
                     envelope = transform(mt, envelope);
                 }
             }
@@ -575,7 +569,7 @@ public final class Envelopes extends Sta
         long isWrapAroundAxis = 0;
         long dimensionBitMask = 1;
         final int dimension = targetCS.getDimension();
-        for (int i=0; i<dimension; i++, dimensionBitMask <<= 1) {
+poles:  for (int i=0; i<dimension; i++, dimensionBitMask <<= 1) {
             final CoordinateSystemAxis axis = targetCS.getAxis(i);
             if (axis == null) {                 // Should never be null, but check as a paranoiac safety.
                 continue;
@@ -607,9 +601,9 @@ public final class Envelopes extends Sta
                          * lost dimensions. So we don't log any warning in this case.
                          */
                         if (dimension >= mt.getSourceDimensions()) {
-                            recoverableException(Envelopes.class, exception);
+                            warning = exception;
                         }
-                        return transformed;
+                        break poles;
                     }
                     targetPt = new GeneralDirectPosition(mt.getSourceDimensions());
                     for (int j=0; j<dimension; j++) {
@@ -646,7 +640,7 @@ public final class Envelopes extends Sta
              * 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).
              */
-            if ((includedMinValue & includedMaxValue & dimensionBitMask) == 0 && isWrapAround(axis)) {
+            if ((includedMinValue & includedMaxValue & dimensionBitMask) == 0 && CoordinateOperations.isWrapAround(axis)) {
                 isWrapAroundAxis |= dimensionBitMask;
             }
             // Restore 'targetPt' to its initial state, which is equals to 'centerPt'.
@@ -666,7 +660,7 @@ public final class Envelopes extends Sta
             while (isWrapAroundAxis != 0) {
                 final int wrapAroundDimension = Long.numberOfTrailingZeros(isWrapAroundAxis);
                 dimensionBitMask = 1 << wrapAroundDimension;
-                isWrapAroundAxis &= ~dimensionBitMask; // Clear now the bit, for the next iteration.
+                isWrapAroundAxis &= ~dimensionBitMask;              // Clear now the bit, for the next iteration.
                 final CoordinateSystemAxis wrapAroundAxis = targetCS.getAxis(wrapAroundDimension);
                 final double min = wrapAroundAxis.getMinimumValue();
                 final double max = wrapAroundAxis.getMaximumValue();
@@ -689,8 +683,8 @@ public final class Envelopes extends Sta
                     for (int c=0; c<4; c++) {
                         /*
                          * Set the ordinate value along the axis having the singularity point
-                         * (cases c=0 and c=2). If the envelope did not included that point,
-                         * then skip completly this case and the next one, i.e. skip c={0,1}
+                         * (cases c=0 and c=2).  If the envelope did not included that point,
+                         * then skip completely this case and the next one, i.e. skip c={0,1}
                          * or skip c={2,3}.
                          */
                         double value = max;
@@ -722,6 +716,21 @@ public final class Envelopes extends Sta
                 targetPt.setOrdinate(wrapAroundDimension, centerPt[wrapAroundDimension]);
             }
         }
+        /*
+         * At this point we finished envelope transformation. Verify if some ordinates need to be "wrapped around"
+         * as a result of the coordinate operation.  This is usually the longitude axis where the source CRS uses
+         * the [-180 … +180]° range and the target CRS uses the [0 … 360]° range, or the converse. We do not wrap
+         * around if the source and target axes use the same range (e.g. the longitude stay [-180 … +180]°) in order
+         * to reduce the risk of discontinuities. If the user really wants unconditional wrap around, (s)he can call
+         * GeneralEnvelope.normalize().
+         */
+        final Set<Integer> wrapAroundChanges;
+        if (isOperationComplete && operation instanceof AbstractCoordinateOperation) {
+            wrapAroundChanges = ((AbstractCoordinateOperation) operation).getWrapAroundChanges();
+        } else {
+            wrapAroundChanges = CoordinateOperations.wrapAroundChanges(sourceCRS, targetCS);
+        }
+        transformed.normalize(targetCS, 0, wrapAroundChanges.size(), wrapAroundChanges.iterator());
         if (warning != null) {
             recoverableException(Envelopes.class, warning);
         }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java?rev=1813531&r1=1813530&r2=1813531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/GeneralEnvelope.java [UTF-8] Fri Oct 27 12:58:39 2017
@@ -22,6 +22,7 @@ package org.apache.sis.geometry;
  * force installation of the Java2D module (e.g. JavaFX/SWT).
  */
 import java.util.Arrays;
+import java.util.Iterator;
 import java.io.Serializable;
 import java.lang.reflect.Field;
 import org.opengis.referencing.cs.RangeMeaning;
@@ -35,8 +36,9 @@ import org.opengis.metadata.extent.Geogr
 import org.apache.sis.util.resources.Errors;
 
 import static org.apache.sis.util.ArgumentChecks.*;
-import static org.apache.sis.math.MathFunctions.isNegative;
 import static org.apache.sis.math.MathFunctions.isSameSign;
+import static org.apache.sis.math.MathFunctions.isNegative;
+import static org.apache.sis.math.MathFunctions.isNegativeZero;
 
 
 /**
@@ -110,7 +112,7 @@ import static org.apache.sis.math.MathFu
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @version 0.5
+ * @version 0.8
  *
  * @see Envelope2D
  * @see org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox
@@ -806,10 +808,10 @@ public class GeneralEnvelope extends Arr
                          */
                         final double min, max;
                         final double csSpan = getSpan(getAxis(crs, i));
-                        if (span1 >= csSpan) {
+                        if (span1 >= csSpan || isNegativeZero(span1)) {       // Negative zero if [+0 … -0] range.
                             min = min0;
                             max = max0;
-                        } else if (span0 >= csSpan) {
+                        } else if (span0 >= csSpan || isNegativeZero(span0)) {
                             min = min1;
                             max = max1;
                         } else {
@@ -886,53 +888,68 @@ public class GeneralEnvelope extends Arr
      * @see AbstractDirectPosition#normalize()
      */
     public boolean normalize() {
+        if (crs == null) {
+            return false;
+        }
+        final int beginIndex = beginIndex();
+        return normalize(crs.getCoordinateSystem(), beginIndex, endIndex() - beginIndex, null);
+    }
+
+    /**
+     * Normalizes only the dimensions returned by the given iterator, or all dimensions if the iterator is null.
+     * This is used for normalizing the result of a coordinate operation where a wrap around axis does not
+     * necessarily means that the ordinates need to be normalized along that axis.
+     *
+     * @param  cs          the coordinate system of this envelope CRS (as an argument because sometime already known).
+     * @param  beginIndex  index of the first ordinate value in {@link #ordinates} array. Non-zero for sub-envelopes.
+     * @param  count       number of coordinates, i.e. this envelope dimensions.
+     * @param  dimensions  the dimensions to check for normalization, or {@code null} for all dimensions.
+     * @return {@code true} if this envelope has been modified as a result of this method call.
+     */
+    final boolean normalize(final CoordinateSystem cs, final int beginIndex, final int count, final Iterator<Integer> dimensions) {
         boolean changed = false;
-        if (crs != null) {
-            final int d = ordinates.length >>> 1;
-            final int beginIndex = beginIndex();
-            final int dimension = endIndex() - beginIndex;
-            final CoordinateSystem cs = crs.getCoordinateSystem();
-            for (int i=0; i<dimension; i++) {
-                final int iLower = beginIndex + i;
-                final int iUpper = iLower + d;
-                final CoordinateSystemAxis axis = cs.getAxis(i);
-                final double  minimum = axis.getMinimumValue();
-                final double  maximum = axis.getMaximumValue();
-                final RangeMeaning rm = axis.getRangeMeaning();
-                if (RangeMeaning.EXACT.equals(rm)) {
-                    if (ordinates[iLower] < minimum) {ordinates[iLower] = minimum; changed = true;}
-                    if (ordinates[iUpper] > maximum) {ordinates[iUpper] = maximum; changed = true;}
-                } else if (RangeMeaning.WRAPAROUND.equals(rm)) {
-                    final double csSpan = maximum - minimum;
-                    if (csSpan > 0 && csSpan < Double.POSITIVE_INFINITY) {
-                        double o1 = ordinates[iLower];
-                        double o2 = ordinates[iUpper];
-                        if (Math.abs(o2-o1) >= csSpan) {
-                            /*
-                             * If the range exceed the CS span, then we have to replace it by the
-                             * full span, otherwise the range computed by the "else" block is too
-                             * small. The full range will typically be [-180 … 180]°.  However we
-                             * make a special case if the two bounds are multiple of the CS span,
-                             * typically [0 … 360]°. In this case the [0 … -0]° range matches the
-                             * original values and is understood by GeneralEnvelope as a range
-                             * spanning all the world.
-                             */
-                            if (o1 != minimum || o2 != maximum) {
-                                if ((o1 % csSpan) == 0 && (o2 % csSpan) == 0) {
-                                    ordinates[iLower] = +0.0;
-                                    ordinates[iUpper] = -0.0;
-                                } else {
-                                    ordinates[iLower] = minimum;
-                                    ordinates[iUpper] = maximum;
-                                }
-                                changed = true;
+        final int d = ordinates.length >>> 1;
+        for (int j=0; j<count; j++) {
+            final int i = (dimensions != null) ? dimensions.next() : j;
+            final int iLower = beginIndex + i;
+            final int iUpper = iLower + d;
+            final CoordinateSystemAxis axis = cs.getAxis(i);
+            final double  minimum = axis.getMinimumValue();
+            final double  maximum = axis.getMaximumValue();
+            final RangeMeaning rm = axis.getRangeMeaning();
+            if (RangeMeaning.EXACT.equals(rm)) {
+                if (ordinates[iLower] < minimum) {ordinates[iLower] = minimum; changed = true;}
+                if (ordinates[iUpper] > maximum) {ordinates[iUpper] = maximum; changed = true;}
+            } else if (RangeMeaning.WRAPAROUND.equals(rm)) {
+                final double csSpan = maximum - minimum;
+                if (csSpan > 0 && csSpan < Double.POSITIVE_INFINITY) {
+                    double o1 = ordinates[iLower];
+                    double o2 = ordinates[iUpper];
+                    if (Math.abs(o2-o1) >= csSpan) {
+                        /*
+                         * If the range exceed the CS span, then we have to replace it by the
+                         * full span, otherwise the range computed by the "else" block is too
+                         * small. The full range will typically be [-180 … 180]°.  However we
+                         * make a special case if the two bounds are multiple of the CS span,
+                         * typically [0 … 360]°. In this case the [0 … -0]° range matches the
+                         * original values and is understood by GeneralEnvelope as a range
+                         * spanning all the world.
+                         */
+                        if (o1 != minimum || o2 != maximum) {
+                            if ((o1 % csSpan) == 0 && (o2 % csSpan) == 0) {
+                                ordinates[iLower] = +0.0;
+                                ordinates[iUpper] = -0.0;
+                            } else {
+                                ordinates[iLower] = minimum;
+                                ordinates[iUpper] = maximum;
                             }
-                        } else {
-                            o1 = Math.floor((o1 - minimum) / csSpan) * csSpan;
-                            o2 = Math.floor((o2 - minimum) / csSpan) * csSpan;
-                            if (o1 != 0) {ordinates[iLower] -= o1; changed = true;}
-                            if (o2 != 0) {ordinates[iUpper] -= o2; changed = true;}
+                            changed = true;
                         }
+                    } else {
+                        o1 = Math.floor((o1 - minimum) / csSpan) * csSpan;
+                        o2 = Math.floor((o2 - minimum) / csSpan) * csSpan;
+                        if (o1 != 0) {ordinates[iLower] -= o1; changed = true;}
+                        if (o2 != 0) {ordinates[iUpper] -= o2; changed = true;}
                     }
                 }
             }
@@ -940,8 +957,6 @@ public class GeneralEnvelope extends Arr
         return changed;
     }
 
-    // Note: As of JDK 1.6.0_31, using {@linkplain #getLower(int)} in the first line crash the
-    // Javadoc tools, maybe because getLower/getUpper are defined in a non-public parent class.
     /**
      * Ensures that <var>lower</var> &lt;= <var>upper</var> for every dimensions.
      * If a {@linkplain #getUpper(int) upper ordinate value} is less than a

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Shapes2D.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Shapes2D.java?rev=1813531&r1=1813530&r2=1813531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Shapes2D.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/geometry/Shapes2D.java [UTF-8] Fri Oct 27 12:58:39 2017
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.geometry;
 
+import java.util.Set;
 import java.awt.geom.Point2D;
 import java.awt.geom.Line2D;
 import java.awt.geom.Ellipse2D;
@@ -32,6 +33,9 @@ import org.opengis.referencing.operation
 import org.opengis.referencing.operation.NoninvertibleTransformException;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.internal.referencing.j2d.ShapeUtilities;
+import org.apache.sis.internal.referencing.j2d.IntervalRectangle;
+import org.apache.sis.internal.referencing.CoordinateOperations;
+import org.apache.sis.referencing.operation.AbstractCoordinateOperation;
 import org.apache.sis.referencing.operation.matrix.AffineTransforms2D;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
@@ -351,9 +355,9 @@ public final class Shapes2D extends Stat
             D1=D2;
         }
         if (destination != null) {
-            destination.setRect(xmin, ymin, xmax-xmin, ymax-ymin);
+            destination.setRect(xmin, ymin, xmax - xmin, ymax - ymin);
         } else {
-            destination = new Rectangle2D.Double(xmin, ymin, xmax - xmin, ymax - ymin);
+            destination = new IntervalRectangle(xmin, ymin, xmax, ymax);
         }
         /*
          * Note: a previous version had an "assert" statement here comparing our calculation
@@ -539,8 +543,8 @@ public final class Shapes2D extends Stat
              * Forget any axes that are not of kind "WRAPAROUND". Then get the final
              * bit pattern indicating which points to test. Iterate over that bits.
              */
-            if ((toTest & 0x33333333) != 0 && !Envelopes.isWrapAround(targetCS.getAxis(0))) toTest &= 0xCCCCCCCC;
-            if ((toTest & 0xCCCCCCCC) != 0 && !Envelopes.isWrapAround(targetCS.getAxis(1))) toTest &= 0x33333333;
+            if ((toTest & 0x33333333) != 0 && !CoordinateOperations.isWrapAround(targetCS.getAxis(0))) toTest &= 0xCCCCCCCC;
+            if ((toTest & 0xCCCCCCCC) != 0 && !CoordinateOperations.isWrapAround(targetCS.getAxis(1))) toTest &= 0x33333333;
             while (toTest != 0) {
                 final int border = Integer.numberOfTrailingZeros(toTest);
                 final int bitMask = 1 << border;
@@ -569,6 +573,42 @@ public final class Shapes2D extends Stat
                 }
             }
         }
+        /*
+         * At this point we finished envelope transformation. Verify if some ordinates need to be "wrapped around"
+         * as a result of the coordinate operation.   This is usually the longitude axis where the source CRS uses
+         * the [-180 … +180]° range and the target CRS uses the [0 … 360]° range, or the converse. In such case we
+         * set the rectangle to the full range (we do not use the mechanism documented in Envelope2D) because most
+         * Rectangle2D implementations do not support spanning the anti-meridian. This results in larger rectangle
+         * than what would be possible with GeneralEnvelope or Envelope2D, but we try to limit the situation where
+         * this expansion is applied.
+         */
+        final Set<Integer> wrapAroundChanges;
+        if (operation instanceof AbstractCoordinateOperation) {
+            wrapAroundChanges = ((AbstractCoordinateOperation) operation).getWrapAroundChanges();
+        } else {
+            wrapAroundChanges = CoordinateOperations.wrapAroundChanges(sourceCRS, targetCS);
+        }
+        for (int dim : wrapAroundChanges) {                               // Empty in the vast majority of cases.
+            final CoordinateSystemAxis axis = targetCS.getAxis(dim);
+            final double minimum = axis.getMinimumValue();
+            final double maximum = axis.getMaximumValue();
+            final double o1, o2;
+            if (dim == 0) {
+                o1 = destination.getMinX();
+                o2 = destination.getMaxX();
+            } else {
+                o1 = destination.getMinY();
+                o2 = destination.getMaxY();
+            }
+            if (o1 < minimum || o2 > maximum) {
+                final double span = maximum - minimum;
+                if (dim == 0) {
+                    destination.setRect(minimum, destination.getY(), span, destination.getHeight());
+                } else {
+                    destination.setRect(destination.getX(), minimum, destination.getWidth(), span);
+                }
+            }
+        }
         if (warning != null) {
             Envelopes.recoverableException(Shapes2D.class, warning);
         }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/CoordinateOperations.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/CoordinateOperations.java?rev=1813531&r1=1813530&r2=1813531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/CoordinateOperations.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/CoordinateOperations.java [UTF-8] Fri Oct 27 12:58:39 2017
@@ -16,25 +16,57 @@
  */
 package org.apache.sis.internal.referencing;
 
+import java.util.Set;
+import java.util.Objects;
+import java.util.Collections;
+import javax.measure.UnitConverter;
+import javax.measure.IncommensurableException;
+import org.opengis.referencing.cs.RangeMeaning;
+import org.opengis.referencing.cs.CoordinateSystem;
+import org.opengis.referencing.cs.CoordinateSystemAxis;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.crs.GeneralDerivedCRS;
+import org.opengis.referencing.operation.CoordinateOperation;
 import org.opengis.referencing.operation.CoordinateOperationFactory;
 import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
+import org.apache.sis.referencing.operation.AbstractCoordinateOperation;
+import org.apache.sis.internal.metadata.AxisDirections;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.system.SystemListener;
+import org.apache.sis.internal.util.Numerics;
+import org.apache.sis.internal.jdk9.JDK9;
 
 
 /**
  * The default coordinate operation factory, provided in a separated class for deferring class loading
- * until first needed.
+ * until first needed. Contains also utility methods related to coordinate operations.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
+ * @version 0.8
  * @since   0.7
  * @module
  */
 public final class CoordinateOperations extends SystemListener {
     /**
-     * The factory.
+     * Cached values or {@link #wrapAroundChanges wrapAroundChanges(…)}, created when first needed.
+     * Indices are bit masks computed by {@link #changes changes(…)}. Since the most common "wrap around" axes
+     * are longitude at dimension 0 or 1, and some measurement of time (in climatology) at dimension 2 or 3,
+     * then the most likely values are (binary digits):
+     *
+     * {@preformat text
+     *     0000    0100    1000
+     *     0001    0101    1001
+     *     0010    0110    1010
+     * }
+     *
+     * The last decimal value is 10 (binary {@code 1010}); we don't need to cache more.
+     */
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    private static final Set<Integer>[] CACHE = new Set[11];
+
+    /**
+     * The system-wide default factory.
      */
     private static volatile DefaultCoordinateOperationFactory factory;
 
@@ -73,4 +105,157 @@ public final class CoordinateOperations
         }
         return c;
     }
+
+    /**
+     * Returns {@code true} if the given axis is of kind "Wrap Around".
+     * Defined here because used together with {@link #wrapAroundChanges wrapAroundChanges(…)}.
+     *
+     * @param  axis  the axis to test.
+     * @return {@code true} if the given axis has "wrap around" range meaning.
+     */
+    public static boolean isWrapAround(final CoordinateSystemAxis axis) {
+        return RangeMeaning.WRAPAROUND.equals(axis.getRangeMeaning());
+    }
+
+    /**
+     * Returns indices of target dimensions where "wrap around" may happen as a result of a coordinate operation.
+     * This is usually the longitude axis when the source CRS uses the [-180 … +180]° range and the target CRS
+     * uses the [0 … 360]° range, or the converse.
+     *
+     * @param  op  the coordinate operation for which to get "wrap around" target dimensions.
+     * @return target dimensions where "wrap around" may happen, or an empty set if none.
+     *
+     * @see AbstractCoordinateOperation#getWrapAroundChanges()
+     */
+    public static Set<Integer> wrapAroundChanges(final CoordinateOperation op) {
+        if (op instanceof AbstractCoordinateOperation) {
+            return ((AbstractCoordinateOperation) op).getWrapAroundChanges();
+        } else if (op != null) {
+            final CoordinateReferenceSystem source, target;
+            if ((source = op.getSourceCRS()) != null &&
+                (target = op.getTargetCRS()) != null)
+            {
+                return wrapAroundChanges(source, target.getCoordinateSystem());
+            }
+        }
+        return Collections.emptySet();
+    }
+
+    /**
+     * Computes indices of target dimensions where "wrap around" may happen as a result of a coordinate operation.
+     * This is usually the longitude axis when the source CRS uses the [-180 … +180]° range and the target CRS uses
+     * the [0 … 360]° range, or the converse.
+     *
+     * @param  source  the source of the coordinate operation.
+     * @param  target  the target of the coordinate operation.
+     * @return target dimensions where "wrap around" may happen, or an empty set if none.
+     */
+    public static Set<Integer> wrapAroundChanges(CoordinateReferenceSystem source, final CoordinateSystem target) {
+        long changes = changes(source.getCoordinateSystem(), target);
+        while (source instanceof GeneralDerivedCRS) {
+            source = ((GeneralDerivedCRS) source).getBaseCRS();
+            changes |= changes(source.getCoordinateSystem(), target);
+        }
+        final boolean useCache = (changes >= 0 && changes < CACHE.length);
+        if (useCache) {
+            /*
+             * In most cases we have an existing instance. Since WrapAroundAxes are immutable, if we got a reference,
+             * the object should be okay ("published" in memory model terminology) even if we did not synchronized.
+             * The reference however may not be visible in the array, but we will check later in a synchronized block.
+             */
+            final Set<Integer> existing = CACHE[(int) changes];
+            if (existing != null) {
+                return existing;
+            }
+        }
+        /*
+         * No existing instance found. Expand the 'changes' bit mask in an array of integers in order to create an
+         * unmodifiable List<Integer>. The list is for public API; internally, Apache SIS will use toBitMask(…).
+         */
+        long r = changes;
+        final Integer[] indices = new Integer[Long.bitCount(r)];
+        for (int i=0; i<indices.length; i++) {
+            final int dim = Long.numberOfTrailingZeros(r);
+            indices[i] = dim;
+            r &= ~(1L << dim);
+        }
+        final Set<Integer> dimensions = JDK9.setOf(indices);
+        if (useCache) {
+            synchronized (CACHE) {
+                final Set<Integer> existing = CACHE[(int) changes];
+                if (existing != null) {
+                    return existing;
+                }
+                CACHE[(int) changes] = dimensions;
+            }
+        }
+        return dimensions;
+    }
+
+    /**
+     * Returns the packed indices of target dimensions where ordinate values may need to be wrapped around.
+     * This method matches target coordinate system axes having {@link RangeMeaning#WRAPAROUND} with source
+     * axes, then verifies if the range of values changed (taking unit conversions in account). A target
+     * dimension {@code i} may need to "wrap around" the coordinate values if the {@code 1 << i} bit is set.
+     * If there is no change, then the value is zero.
+     */
+    private static long changes(final CoordinateSystem source, final CoordinateSystem target) {
+        long changes = 0;
+        if (source != target) {                                 // Optimization for a common case.
+            /*
+             * Get the dimensions of all axes in the source coordinate system as bit fields.
+             * We create this list first because we may iterate more than once on those axes
+             * and we want to clear the axes that we already matched. We use a bitmask for
+             * efficiency, with the bits of dimensions to consider set to 1.
+             *
+             * Note: a previous version was creating a list of "wraparound" axes only. We removed that filter
+             * because a target wraparound axis may match a source infinite axis. For example when converting
+             * dates on a temporal axis (with infinite span toward past and future) to months on a climatology
+             * axis (January to December months without year), the same cycle is repeated after every 12 months
+             * even if the source axis had no cycle.
+             */
+            long isWrapAroundAxis = (1 << source.getDimension()) - 1;
+            /*
+             * For each "wrap around" axis in the target CRS, search a matching axis in the source CRS
+             * which is also "wrap around", is colinear and uses compatible unit of measurement. There
+             * is usually at most one "wrap around" axis, but this code is nevertheless generic enough
+             * for an arbitrary amount of axes.
+             */
+            final int dim = Math.min(Long.SIZE, target.getDimension());
+compare:    for (int i=0; i<dim; i++) {
+                final CoordinateSystemAxis axis = target.getAxis(i);
+                if (isWrapAround(axis)) {
+                    long candidates = isWrapAroundAxis;
+                    do {
+                        final long mask = Long.lowestOneBit(candidates);
+                        final CoordinateSystemAxis src = source.getAxis(Long.numberOfTrailingZeros(mask));
+                        if (Objects.equals(AxisDirections.absolute(src .getDirection()),
+                                           AxisDirections.absolute(axis.getDirection())))
+                        {
+                            try {
+                                final UnitConverter c  = src.getUnit().getConverterToAny(axis.getUnit());
+                                final double minimum   = axis.getMinimumValue();
+                                final double maximum   = axis.getMaximumValue();
+                                final double tolerance = (maximum - minimum) * Numerics.COMPARISON_THRESHOLD;
+                                if (!Numerics.epsilonEqual(c.convert(src.getMinimumValue()), minimum, tolerance) ||
+                                    !Numerics.epsilonEqual(c.convert(src.getMaximumValue()), maximum, tolerance))
+                                {
+                                    changes |= (1 << i);
+                                }
+                                isWrapAroundAxis &= ~mask;      // We are done with this source axis.
+                                if (isWrapAroundAxis == 0) {
+                                    break compare;              // Useless to continue if there is no more source axis.
+                                }
+                                continue compare;               // Match next pair of wrap around axes.
+                            } catch (IncommensurableException e) {
+                                // Ignore (should not happen often). We will try to match another pair of axes.
+                            }
+                        }
+                        candidates &= ~mask;        // Unable to use that axis. Check if we can use another one.
+                    } while (candidates != 0);
+                }
+            }
+        }
+        return changes;
+    }
 }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineMatrix.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineMatrix.java?rev=1813531&r1=1813530&r2=1813531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineMatrix.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/AffineMatrix.java [UTF-8] Fri Oct 27 12:58:39 2017
@@ -31,7 +31,7 @@ import org.apache.sis.util.ArgumentCheck
  * used in double-double arithmetic.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.5
+ * @version 0.8
  * @since   0.5
  * @module
  */
@@ -181,6 +181,26 @@ final class AffineMatrix implements Exte
     }
 
     /**
+     * Compares this matrix with the given object for equality, including error terms (if any).
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof AffineMatrix) {
+            final AffineMatrix other = (AffineMatrix) obj;
+            return transform.equals(other.transform) && Arrays.equals(errors, other.errors);
+        }
+        return false;
+    }
+
+    /**
+     * Returns a hash code value for this matrix.
+     */
+    @Override
+    public int hashCode() {
+        return (transform.hashCode() * 31 + Arrays.hashCode(errors)) ^ (int) serialVersionUID;
+    }
+
+    /**
      * Returns a string representation of this matrix.
      *
      * @return a string representation of this matrix.

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Affine.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Affine.java?rev=1813531&r1=1813530&r2=1813531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Affine.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/Affine.java [UTF-8] Fri Oct 27 12:58:39 2017
@@ -57,7 +57,7 @@ import org.apache.sis.referencing.operat
  * </table>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.6
+ * @version 0.8
  * @since   0.5
  * @module
  */
@@ -283,6 +283,23 @@ public final class Affine extends Abstra
     }
 
     /**
+     * Returns parameter values for an identity transform of the given input and output dimensions.
+     * Callers can modify the returned parameters if desired.
+     *
+     * @param  dimension  the number of source and target dimensions.
+     * @return parameters for an identity transform of the given dimensions.
+     *
+     * @since 0.8
+     */
+    public static ParameterValueGroup identity(int dimension) {
+        final ParameterValueGroup values = TensorParameters.WKT1.createValueGroup(
+                Collections.singletonMap(NAME_KEY, Constants.AFFINE));
+        values.parameter(Constants.NUM_COL).setValue(++dimension);
+        values.parameter(Constants.NUM_ROW).setValue(  dimension);
+        return values;
+    }
+
+    /**
      * Returns the parameter values for the given matrix. This method is invoked by implementations of
      * {@link org.apache.sis.referencing.operation.transform.AbstractMathTransform#getParameterValues()}.
      *

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java?rev=1813531&r1=1813530&r2=1813531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java [UTF-8] Fri Oct 27 12:58:39 2017
@@ -226,7 +226,8 @@ public abstract class GeocentricAffine e
 
     /**
      * Returns the parameters for creating a datum shift operation.
-     * The operation method will be one of the {@code GeocentricAffine} subclasses.
+     * The operation method will be one of the {@code GeocentricAffine} subclasses,
+     * unless the specified {@code method} argument is {@link DatumShiftMethod#NONE}.
      * If no single operation method can be used, then this method returns {@code null}.
      *
      * <p>This method does <strong>not</strong> change the coordinate system type.
@@ -235,19 +236,18 @@ public abstract class GeocentricAffine e
      * will cause this method to return {@code null}. In such case, it is caller's responsibility to apply
      * the datum shift itself in Cartesian geocentric coordinates.</p>
      *
-     * @param sourceCS       the source coordinate system. Only the type and number of dimensions is checked.
-     * @param targetCS       the target coordinate system. Only the type and number of dimensions is checked.
-     * @param datumShift     the datum shift as a matrix.
-     * @param useMolodensky  {@code true} for allowing the use of Molodensky approximation, or {@code false}
-     *                       for using the transformation in geocentric space (which should be more accurate).
+     * @param  sourceCS    the source coordinate system. Only the type and number of dimensions is checked.
+     * @param  targetCS    the target coordinate system. Only the type and number of dimensions is checked.
+     * @param  datumShift  the datum shift as a matrix, or {@code null} if there is no datum shift information.
+     * @param  method      the preferred datum shift method. Note that {@code createParameters(…)} may overwrite.
      * @return the parameter values, or {@code null} if no single operation method can be found.
      */
     public static ParameterValueGroup createParameters(final CoordinateSystem sourceCS,
-            final CoordinateSystem targetCS, final Matrix datumShift, boolean useMolodensky)
+            final CoordinateSystem targetCS, final Matrix datumShift, DatumShiftMethod method)
     {
         final boolean isEllipsoidal = (sourceCS instanceof EllipsoidalCS);
-        if (!(isEllipsoidal ? targetCS instanceof EllipsoidalCS
-                            : targetCS instanceof CartesianCS && sourceCS instanceof CartesianCS))
+        if (!(isEllipsoidal ? (targetCS instanceof EllipsoidalCS)
+                            : (targetCS instanceof CartesianCS && sourceCS instanceof CartesianCS)))
         {
             return null;        // Coordinate systems are not two EllipsoidalCS or two CartesianCS.
         }
@@ -256,43 +256,93 @@ public abstract class GeocentricAffine e
         if (dimension != targetCS.getDimension()) {
             dimension  = 4;     // Any value greater than 3 means "mismatched dimensions" for this method.
         }
+        if (method == DatumShiftMethod.NONE) {
+            if (dimension <= 3) {
+                return Affine.identity(dimension);
+            } else if (isEllipsoidal) {
+                final ParameterDescriptorGroup descriptor;
+                switch (sourceCS.getDimension()) {
+                    case 2: descriptor = Geographic2Dto3D.PARAMETERS; break;
+                    case 3: descriptor = Geographic3Dto2D.PARAMETERS; break;
+                    default: return null;
+                }
+                return descriptor.createValue();
+            } else {
+                return null;
+            }
+        }
         /*
          * Try to convert the matrix into (tX, tY, tZ, rX, rY, rZ, dS) parameters.
-         * The matrix may not be convertible, in which case we will let the callers
+         * The matrix may not be convertible, in which case we will let the caller
          * uses the matrix directly in Cartesian geocentric coordinates.
          */
         final BursaWolfParameters parameters = new BursaWolfParameters(null, null);
-        try {
+        if (datumShift != null) try {
             parameters.setPositionVectorTransformation(datumShift, BURSAWOLF_TOLERANCE);
         } catch (IllegalArgumentException e) {
             log(Loggers.COORDINATE_OPERATION, "createParameters", e);
             return null;
+        } else {
+            /*
+             * If there is no datum shift parameters (not to be confused with identity), then those parameters
+             * are assumed unknown. Using the most accurate methods would give a false impression of accuracy,
+             * so we use the fastest method instead. Since all parameter values are zero, Apache SIS should use
+             * the AbridgedMolodenskyTransform2D optimization.
+             */
+            method = DatumShiftMethod.ABRIDGED_MOLODENSKY;
         }
         final boolean isTranslation = parameters.isTranslation();
         final ParameterDescriptorGroup descriptor;
         /*
-         * Following "if" blocks are ordered from more accurate to less accurate datum shift method
-         * supported by GeocentricAffine subclasses.
+         * Following "if" blocks are ordered from most accurate to less accurate datum shift method
+         * supported by GeocentricAffine subclasses (except NONE which has already been handled).
+         * Special cases:
+         *
+         *   - If the datum shift is applied between geocentric CRS, then the Molodensky approximations do not apply
+         *     as they are designed for transformations between geographic CRS only. User preference is then ignored.
+         *
+         *   - Molodensky methods are approximations for datum shifts having only translation terms in their Bursa-Wolf
+         *     parameters. If there is also a scale or rotation terms, then we can not use Molodensky methods. The user
+         *     preference is then ignored.
          */
         if (!isEllipsoidal) {
-            useMolodensky = false;
+            method = DatumShiftMethod.GEOCENTRIC_DOMAIN;
             descriptor = isTranslation ? GeocentricTranslation.PARAMETERS
                                        : PositionVector7Param .PARAMETERS;
-        } else {
-            if (!isTranslation) {
-                useMolodensky = false;
-                descriptor = (dimension >= 3) ? PositionVector7Param3D.PARAMETERS
-                                              : PositionVector7Param2D.PARAMETERS;
-            } else if (!useMolodensky) {
+        } else if (!isTranslation) {
+            method = DatumShiftMethod.GEOCENTRIC_DOMAIN;
+            descriptor = (dimension >= 3) ? PositionVector7Param3D.PARAMETERS
+                                          : PositionVector7Param2D.PARAMETERS;
+        } else switch (method) {
+            case GEOCENTRIC_DOMAIN: {
                 descriptor = (dimension >= 3) ? GeocentricTranslation3D.PARAMETERS
                                               : GeocentricTranslation2D.PARAMETERS;
-            } else {
+                break;
+            }
+            case MOLODENSKY: {
                 descriptor = Molodensky.PARAMETERS;
+                break;
+            }
+            case ABRIDGED_MOLODENSKY: {
+                descriptor = AbridgedMolodensky.PARAMETERS;
+                break;
             }
+            default: throw new AssertionError(method);
         }
+        /*
+         * Following lines will set all Bursa-Wolf parameter values (scale, translation
+         * and rotation terms). In the particular case of Molodensky method, we have an
+         * additional parameter for the number of source and target dimensions (2 or 3).
+         */
         final Parameters values = createParameters(descriptor, parameters, isTranslation);
-        if (useMolodensky && dimension <= 3) {
-            values.getOrCreate(Molodensky.DIMENSION).setValue(dimension);
+        switch (method) {
+            case MOLODENSKY:
+            case ABRIDGED_MOLODENSKY: {
+                if (dimension <= 3) {
+                    values.getOrCreate(Molodensky.DIMENSION).setValue(dimension);
+                }
+                break;
+            }
         }
         return values;
     }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java?rev=1813531&r1=1813530&r2=1813531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java [UTF-8] Fri Oct 27 12:58:39 2017
@@ -17,9 +17,12 @@
 package org.apache.sis.referencing.operation;
 
 import java.util.Map;
+import java.util.Set;
 import java.util.Objects;
 import java.util.Collection;
 import java.util.Collections;
+import java.io.IOException;
+import java.io.ObjectInputStream;
 import javax.xml.bind.Unmarshaller;
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlSeeAlso;
@@ -55,6 +58,7 @@ import org.apache.sis.referencing.Abstra
 import org.apache.sis.referencing.operation.transform.MathTransforms;
 import org.apache.sis.referencing.operation.transform.PassThroughTransform;
 import org.apache.sis.internal.referencing.PositionalAccuracyConstant;
+import org.apache.sis.internal.referencing.CoordinateOperations;
 import org.apache.sis.internal.referencing.ReferencingUtilities;
 import org.apache.sis.internal.referencing.Resources;
 import org.apache.sis.internal.referencing.WKTUtilities;
@@ -96,7 +100,7 @@ import static org.apache.sis.util.Utilit
  * synchronization.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.7
+ * @version 0.8
  * @since   0.6
  * @module
  */
@@ -207,6 +211,16 @@ public class AbstractCoordinateOperation
     MathTransform transform;
 
     /**
+     * Indices of target dimensions where "wrap around" may happen as a result of this coordinate operation.
+     * This is usually the longitude axis when the source CRS uses the [-180 … +180]° range and the target
+     * CRS uses the [0 … 360]° range, or the converse. If there is no change, then this is an empty set.
+     *
+     * @see #getWrapAroundChanges()
+     * @see #computeTransientFields()
+     */
+    private transient Set<Integer> wrapAroundChanges;
+
+    /**
      * Creates a new coordinate operation initialized from the given properties.
      * It is caller's responsibility to:
      *
@@ -369,6 +383,30 @@ check:      for (int isTarget=0; ; isTar
                 }
             }
         }
+        computeTransientFields();
+    }
+
+    /**
+     * Computes the {@link #wrapAroundChanges} field after we verified that the coordinate operation is valid.
+     */
+    final void computeTransientFields() {
+        if (sourceCRS != null && targetCRS != null) {
+            wrapAroundChanges = CoordinateOperations.wrapAroundChanges(sourceCRS, targetCRS.getCoordinateSystem());
+        } else {
+            wrapAroundChanges = Collections.emptySet();
+        }
+    }
+
+    /**
+     * Computes transient fields after deserialization.
+     *
+     * @param  in  the input stream from which to deserialize a coordinate operation.
+     * @throws IOException if an I/O error occurred while reading or if the stream contains invalid data.
+     * @throws ClassNotFoundException if the class serialized on the stream is not on the classpath.
+     */
+    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+        computeTransientFields();
     }
 
     /**
@@ -392,6 +430,11 @@ check:      for (int isTarget=0; ; isTar
         domainOfValidity            = operation.getDomainOfValidity();
         scope                       = operation.getScope();
         transform                   = operation.getMathTransform();
+        if (operation instanceof AbstractCoordinateOperation) {
+            wrapAroundChanges = ((AbstractCoordinateOperation) operation).wrapAroundChanges;
+        } else {
+            computeTransientFields();
+        }
     }
 
     /**
@@ -727,6 +770,40 @@ check:      for (int isTarget=0; ; isTar
     }
 
     /**
+     * Returns the indices of target dimensions where "wrap around" may happen as a result of this coordinate operation.
+     * If such change exists, then this is usually the longitude axis when the source CRS uses the [-180 … +180]° range
+     * and the target CRS uses the [0 … 360]° range, or the converse. If there is no change, then this is an empty set.
+     *
+     * <div class="note"><b>Inverse relationship:</b>
+     * sometime the target dimensions returned by this method can be mapped directly to wraparound axes in source CRS,
+     * but this is not always the case. For example consider the following operation chain:
+     *
+     * <center>source projected CRS ⟶ base CRS ⟶ target geographic CRS</center>
+     *
+     * In this example, a wraparound axis in the target CRS (the longitude) can be mapped to a wraparound axis in
+     * the {@linkplain org.apache.sis.referencing.crs.DefaultProjectedCRS#getBaseCRS() base CRS}. But there is no
+     * corresponding wraparound axis in the source CRS because the <em>easting</em> axis in projected CRS does not
+     * have a wraparound range meaning. We could argue that
+     * {@linkplain org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis#getDirection() axis directions} match,
+     * but such matching is not guaranteed to exist since {@code ProjectedCRS} is a special case of
+     * {@code GeneralDerivedCRS} and derived CRS can have rotations.</div>
+     *
+     * <p>The default implementation infers this set by inspecting the source and target coordinate system axes.
+     * It returns the indices of all target axes having {@link org.opengis.referencing.cs.RangeMeaning#WRAPAROUND}
+     * and for which the following condition holds: a colinear source axis exists with compatible unit of measurement,
+     * and the range (taking unit conversions in account) or range meaning of those source and target axes are not
+     * the same.</p>
+     *
+     * @return indices of target dimensions where "wrap around" may happen as a result of this coordinate operation.
+     *
+     * @since 0.8
+     */
+    @SuppressWarnings("ReturnOfCollectionOrArrayField")
+    public Set<Integer> getWrapAroundChanges() {
+        return wrapAroundChanges;
+    }
+
+    /**
      * Compares this coordinate operation with the specified object for equality. If the {@code mode} argument
      * is {@link ComparisonMode#STRICT} or {@link ComparisonMode#BY_CONTRACT BY_CONTRACT}, then all available
      * properties are compared including the {@linkplain #getDomainOfValidity() domain of validity} and the
@@ -974,7 +1051,7 @@ check:      for (int isTarget=0; ; isTar
     private void setSource(final CoordinateReferenceSystem crs) {
         if (sourceCRS == null) {
             sourceCRS = crs;
-        } else {
+        } else if (!sourceCRS.equals(crs)) {                    // Could be defined by ConcatenatedOperation.
             MetadataUtilities.propertyAlreadySet(AbstractCoordinateOperation.class, "setSource", "sourceCRS");
         }
     }
@@ -993,7 +1070,7 @@ check:      for (int isTarget=0; ; isTar
     private void setTarget(final CoordinateReferenceSystem crs) {
         if (targetCRS == null) {
             targetCRS = crs;
-        } else {
+        } else if (!targetCRS.equals(crs)) {                    // Could be defined by ConcatenatedOperation.
             MetadataUtilities.propertyAlreadySet(AbstractCoordinateOperation.class, "setTarget", "targetCRS");
         }
     }
@@ -1057,4 +1134,11 @@ check:      for (int isTarget=0; ; isTar
             MetadataUtilities.propertyAlreadySet(AbstractCoordinateOperation.class, "setScope", "scope");
         }
     }
+
+    /**
+     * Invoked by JAXB after unmarshalling.
+     */
+    void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
+        computeTransientFields();
+    }
 }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java?rev=1813531&r1=1813530&r2=1813531&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java [UTF-8] Fri Oct 27 12:58:39 2017
@@ -70,7 +70,7 @@ import static org.apache.sis.util.Utilit
  * {@link DefaultPassThroughOperation}.</p>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.7
+ * @version 0.8
  * @since   0.6
  * @module
  */
@@ -173,10 +173,8 @@ class AbstractSingleOperation extends Ab
      * In the particular case of a {@linkplain PassThroughTransform pass through transform} with more dimensions
      * than what we would expect from the given method, the check will rather be performed against the
      * {@linkplain PassThroughTransform#getSubTransform() sub transform}.
-     *
-     * <p>The intend is to allow creation of a three-dimensional {@code ProjectedCRS} with a two-dimensional
-     * {@code OperationMethod}, where the third-dimension just pass through. This is not a recommended approach
-     * and we do not document that as a supported feature, but we do not prevent it neither.</p>
+     * The intend is to allow creation of a three-dimensional {@code ProjectedCRS} with a two-dimensional
+     * {@code OperationMethod}, where the third-dimension just pass through.
      *
      * <p>This method tries to locates what seems to be the "core" of the given math transform. The definition
      * of "core" is imprecise and may be adjusted in future SIS versions. The current algorithm is as below:</p>
@@ -513,7 +511,9 @@ class AbstractSingleOperation extends Ab
      *
      * @see <a href="http://issues.apache.org/jira/browse/SIS-291">SIS-291</a>
      */
-    private void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
+    @Override
+    final void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
+        super.afterUnmarshal(unmarshaller, parent);
         final CoordinateReferenceSystem sourceCRS = super.getSourceCRS();
         final CoordinateReferenceSystem targetCRS = super.getTargetCRS();
         if (transform == null && sourceCRS != null && targetCRS != null && parameters != null) try {



Mime
View raw message