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 2017/10/30 10:25:10 UTC

svn commit: r1813747 [5/13] - in /sis/branches/JDK9: ./ application/ application/sis-console/ application/sis-console/src/main/artifact/ application/sis-console/src/main/artifact/lib/ application/sis-console/src/main/artifact/lib/darwin/ application/si...

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -25,10 +25,12 @@ import org.opengis.util.FactoryException
 import org.opengis.geometry.Envelope;
 import org.opengis.referencing.NoSuchAuthorityCodeException;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.cs.CartesianCS;
 import org.opengis.referencing.cs.EllipsoidalCS;
 import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
+import org.opengis.referencing.cs.CSFactory;
 import org.opengis.referencing.crs.CRSFactory;
 import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.crs.CompoundCRS;
@@ -41,6 +43,8 @@ import org.opengis.referencing.crs.Proje
 import org.opengis.referencing.crs.TemporalCRS;
 import org.opengis.referencing.crs.VerticalCRS;
 import org.opengis.referencing.crs.EngineeringCRS;
+import org.opengis.referencing.operation.Conversion;
+import org.opengis.referencing.operation.CoordinateOperationFactory;
 import org.opengis.referencing.operation.OperationNotFoundException;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.extent.Extent;
@@ -53,6 +57,7 @@ import org.apache.sis.measure.Units;
 import org.apache.sis.geometry.Envelopes;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.internal.metadata.AxisDirections;
+import org.apache.sis.internal.metadata.EllipsoidalHeightCombiner;
 import org.apache.sis.internal.referencing.PositionalAccuracyConstant;
 import org.apache.sis.internal.referencing.CoordinateOperations;
 import org.apache.sis.internal.referencing.ReferencingUtilities;
@@ -60,14 +65,19 @@ import org.apache.sis.internal.referenci
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.system.Loggers;
+import org.apache.sis.referencing.cs.AxisFilter;
+import org.apache.sis.referencing.cs.CoordinateSystems;
 import org.apache.sis.referencing.cs.DefaultVerticalCS;
-import org.apache.sis.referencing.cs.DefaultEllipsoidalCS;
 import org.apache.sis.referencing.crs.DefaultGeographicCRS;
+import org.apache.sis.referencing.crs.DefaultProjectedCRS;
 import org.apache.sis.referencing.crs.DefaultVerticalCRS;
 import org.apache.sis.referencing.crs.DefaultCompoundCRS;
+import org.apache.sis.referencing.crs.DefaultEngineeringCRS;
 import org.apache.sis.referencing.operation.AbstractCoordinateOperation;
 import org.apache.sis.referencing.operation.CoordinateOperationContext;
 import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
+import org.apache.sis.referencing.operation.DefaultConversion;
+import org.apache.sis.referencing.factory.GeodeticObjectFactory;
 import org.apache.sis.referencing.factory.UnavailableFactoryException;
 import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
 import org.apache.sis.metadata.iso.extent.Extents;
@@ -78,8 +88,6 @@ import org.apache.sis.util.ArgumentCheck
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.Static;
 
-import static java.util.Collections.singletonMap;
-
 // Branch-dependent imports
 import org.opengis.geometry.Geometry;
 
@@ -176,7 +184,7 @@ public final class CRS extends Static {
      *   <tr><td>EPSG:5714</td> <td>{@link CommonCRS.Vertical#MEAN_SEA_LEVEL MEAN_SEA_LEVEL}</td> <td>Vertical</td> <td>Mean Sea Level height</td></tr>
      * </table></blockquote>
      *
-     * This method accepts also the URN and URL syntax.
+     * This method accepts also the URN and URL syntaxes.
      * For example the following codes are considered equivalent to {@code "EPSG:4326"}:
      * <ul>
      *   <li>{@code "EPSG::4326"}</li>
@@ -185,6 +193,26 @@ public final class CRS extends Static {
      *   <li>{@code "http://www.opengis.net/gml/srs/epsg.xml#4326"}</li>
      * </ul>
      *
+     * URIs can be combined for creating larger objects. For example the following URIs combine a
+     * two-dimensional WGS84 reference system (EPSG:4326) with a Mean Sea Level height (EPSG:5714).
+     * The result is a three-dimensional {@linkplain org.apache.sis.referencing.crs.DefaultCompoundCRS
+     * compound coordinate reference system}:
+     *
+     * <ul>
+     *   <li>{@code "urn:ogc:def:crs,crs:EPSG::4326,crs:EPSG::5714"}</li>
+     *   <li><code>"http://www.opengis.net/def/crs-compound?<br>
+     *            1=http://www.opengis.net/def/crs/epsg/0/4326&amp;<br>
+     *            2=http://www.opengis.net/def/crs/epsg/0/5714"</code></li>
+     * </ul>
+     *
+     * <p>URNs (but not URLs) can also combine a
+     * {@linkplain org.apache.sis.referencing.datum.DefaultGeodeticDatum geodetic datum} with an
+     * {@linkplain org.apache.sis.referencing.cs.DefaultEllipsoidalCS ellipsoidal coordinate system} for creating a new
+     * {@linkplain org.apache.sis.referencing.crs.DefaultGeographicCRS geographic CRS}, or a base geographic CRS with a
+     * {@linkplain org.apache.sis.referencing.operation.DefaultConversion conversion} and a
+     * {@linkplain org.apache.sis.referencing.cs.DefaultCartesianCS Cartesian coordinate system} for creating a new
+     * {@linkplain org.apache.sis.referencing.crs.DefaultProjectedCRS projected coordinate reference system}.</p>
+     *
      * Note that the {@link IdentifiedObjects#lookupURN(IdentifiedObject, Citation)}
      * method can be seen as a converse of this method.
      * More codes may also be supported depending on which extension modules are available.
@@ -197,6 +225,7 @@ public final class CRS extends Static {
      *
      * @see #getAuthorityFactory(String)
      * @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory
+     * @see <a href="http://epsg-registry.org/">EPSG Geodetic Registry</a>
      *
      * @category factory
      */
@@ -805,6 +834,61 @@ public final class CRS extends Static {
     }
 
     /**
+     * Creates a compound coordinate reference system from an ordered list of CRS components.
+     * A CRS is inferred from the given components and the domain of validity is set to the
+     * {@linkplain org.apache.sis.metadata.iso.extent.DefaultExtent#intersect intersection}
+     * of the domain of validity of all components.
+     *
+     * <div class="section">Ellipsoidal height</div>
+     * If a two-dimensional geographic or projected CRS if followed or preceded by a vertical CRS with ellipsoidal
+     * {@linkplain org.apache.sis.referencing.datum.DefaultVerticalDatum#getVerticalDatumType() datum type}, then
+     * this method combines them in a single three-dimensional geographic or projected CRS.  Note that standalone
+     * ellipsoidal heights are not allowed according ISO 19111. But if such situation is nevertheless found, then
+     * the action described here fixes the issue. This is the reverse of <code>{@linkplain #getVerticalComponent
+     * getVerticalComponent}(crs, true)</code>.
+     *
+     * <div class="section">Components order</div>
+     * Apache SIS is permissive on the order of components that can be used in a compound CRS.
+     * However for better inter-operability, users are encouraged to follow the order mandated by ISO 19162:
+     *
+     * <ol>
+     *   <li>A mandatory horizontal CRS (only one of two-dimensional {@code GeographicCRS} or {@code ProjectedCRS} or {@code EngineeringCRS}).</li>
+     *   <li>Optionally followed by a {@code VerticalCRS} or a {@code ParametricCRS} (but not both).</li>
+     *   <li>Optionally followed by a {@code TemporalCRS}.</li>
+     * </ol>
+     *
+     * @param  components  the sequence of coordinate reference systems making the compound CRS.
+     * @return the compound CRS, or {@code components[0]} if the given array contains only one component.
+     * @throws IllegalArgumentException if the given array is empty or if the array contains incompatible components.
+     * @throws FactoryException if the geodetic factory failed to create the compound CRS.
+     *
+     * @since 0.8
+     *
+     * @see GeodeticObjectFactory#createCompoundCRS(Map, CoordinateReferenceSystem...)
+     */
+    public static CoordinateReferenceSystem compound(final CoordinateReferenceSystem... components) throws FactoryException {
+        ArgumentChecks.ensureNonNull("components", components);
+        switch (components.length) {
+            case 0: {
+                throw new IllegalArgumentException(Errors.format(Errors.Keys.EmptyArgument_1, "components"));
+            }
+            case 1: {
+                final CoordinateReferenceSystem crs = components[0];
+                if (crs != null) return crs;
+                break;
+            }
+        }
+        final Map<String,?> properties = EllipsoidalHeightCombiner.properties(components);
+        return new EllipsoidalHeightCombiner() {
+            @Override public void initialize(final int factoryTypes) {
+                if ((factoryTypes & CRS)       != 0) crsFactory = DefaultFactories.forBuildin(CRSFactory.class);
+                if ((factoryTypes & CS)        != 0) csFactory  = DefaultFactories.forBuildin(CSFactory.class);
+                if ((factoryTypes & OPERATION) != 0) opFactory  = DefaultFactories.forBuildin(CoordinateOperationFactory.class);
+            }
+        }.createCompoundCRS(properties, components);
+    }
+
+    /**
      * Returns {@code true} if the given CRS is horizontal. The current implementation considers a
      * CRS as horizontal if it is two-dimensional and comply with one of the following conditions:
      *
@@ -831,20 +915,38 @@ public final class CRS extends Static {
      * @category information
      */
     public static boolean isHorizontalCRS(final CoordinateReferenceSystem crs) {
+        return horizontalCode(crs) == 2;
+    }
+
+    /**
+     * If the given CRS would quality as horizontal except for its number of dimensions, returns that number.
+     * Otherwise returns 0. The number of dimensions can only be 2 or 3.
+     */
+    private static int horizontalCode(final CoordinateReferenceSystem crs) {
         /*
          * In order to determine if the CRS is geographic, checking the CoordinateSystem type is more reliable
          * then checking if the CRS implements the GeographicCRS interface.  This is because the GeographicCRS
-         * interface is GeoAPI-specific, so a CRS may be OGC-compliant without implementing that interface.
+         * type did not existed in ISO 19111:2007, so a CRS could be standard-compliant without implementing
+         * the GeographicCRS interface.
          */
+        boolean isEngineering = false;
         final boolean isGeodetic = (crs instanceof GeodeticCRS);
-        if (isGeodetic || crs instanceof ProjectedCRS || crs instanceof EngineeringCRS) {
-            @SuppressWarnings("null")
+        if (isGeodetic || crs instanceof ProjectedCRS || (isEngineering = (crs instanceof EngineeringCRS))) {
             final CoordinateSystem cs = crs.getCoordinateSystem();
-            if (cs.getDimension() == 2) {
-                return !isGeodetic || (cs instanceof EllipsoidalCS);
+            final int dim = cs.getDimension();
+            if ((dim & ~1) == 2 && (!isGeodetic || (cs instanceof EllipsoidalCS))) {
+                if (isEngineering) {
+                    int n = 0;
+                    for (int i=0; i<dim; i++) {
+                        if (AxisDirections.isCompass(cs.getAxis(i).getDirection())) n++;
+                    }
+                    // If we don't have exactly 2 east, north, etc. directions, consider as non-horizontal.
+                    if (n != 2) return 0;
+                }
+                return dim;
             }
         }
-        return false;
+        return 0;
     }
 
     /**
@@ -854,8 +956,8 @@ public final class CRS extends Static {
      * first horizontal component in the order of the {@linkplain #getSingleComponents(CoordinateReferenceSystem)
      * single components list}.
      *
-     * <p>In the special case where a three-dimensional geographic CRS is found, this method will create a
-     * two-dimensional geographic CRS without the vertical axis.</p>
+     * <p>In the special case where a three-dimensional geographic or projected CRS is found, this method
+     * will create a two-dimensional geographic or projected CRS without the vertical axis.</p>
      *
      * @param  crs  the coordinate reference system, or {@code null}.
      * @return the first horizontal CRS, or {@code null} if none.
@@ -863,26 +965,53 @@ public final class CRS extends Static {
      * @category information
      */
     public static SingleCRS getHorizontalComponent(final CoordinateReferenceSystem crs) {
-        if (crs instanceof GeodeticCRS) {
-            CoordinateSystem cs = crs.getCoordinateSystem();
-            if (cs instanceof EllipsoidalCS) {                          // See comment in isHorizontalCRS(…) method.
-                final int i = AxisDirections.indexOfColinear(cs, AxisDirection.UP);
-                if (i < 0) {
-                    return (SingleCRS) crs;
-                }
-                final CoordinateSystemAxis xAxis = cs.getAxis(i > 0 ? 0 : 1);
-                final CoordinateSystemAxis yAxis = cs.getAxis(i > 1 ? 1 : 2);
-                cs = CommonCRS.DEFAULT.geographic().getCoordinateSystem();
-                if (!Utilities.equalsIgnoreMetadata(cs.getAxis(0), xAxis) ||
-                    !Utilities.equalsIgnoreMetadata(cs.getAxis(1), yAxis))
-                {
-                    // We can not reuse the name of the existing CS, because it typically
-                    // contains text about axes including the axis that we just dropped.
-                    cs = new DefaultEllipsoidalCS(singletonMap(EllipsoidalCS.NAME_KEY, "Ellipsoidal 2D"), xAxis, yAxis);
-                }
-                return new DefaultGeographicCRS(
-                        ReferencingUtilities.getPropertiesForModifiedCRS(crs, CoordinateReferenceSystem.IDENTIFIERS_KEY),
-                        ((GeodeticCRS) crs).getDatum(), (EllipsoidalCS) cs);
+        switch (horizontalCode(crs)) {
+            /*
+             * If the CRS is already two-dimensional and horizontal, return as-is.
+             * We don't need to check if crs is an instance of SingleCRS since all
+             * CRS accepted by horizontalCode(…) are SingleCRS.
+             */
+            case 2: {
+                return (SingleCRS) crs;
+            }
+            case 3: {
+                /*
+                 * The CRS would be horizontal if we can remove the vertical axis. CoordinateSystems.replaceAxes(…)
+                 * will do this task for us. We can verify if the operation has been successful by checking that
+                 * the number of dimensions has been reduced by 1 (from 3 to 2).
+                 */
+                final CoordinateSystem cs = CoordinateSystems.replaceAxes(crs.getCoordinateSystem(), new AxisFilter() {
+                    @Override public boolean accept(final CoordinateSystemAxis axis) {
+                        return !AxisDirections.isVertical(axis.getDirection());
+                    }
+                });
+                if (cs.getDimension() != 2) break;
+                /*
+                 * Most of the time, the CRS to rebuild will be geodetic. In such case we known that the
+                 * coordinate system is ellipsoidal because (i.e. the CRS is geographic) because it was
+                 * a condition verified by horizontalCode(…). A ClassCastException would be a bug.
+                 */
+                final Map<String, ?> properties = ReferencingUtilities.getPropertiesForModifiedCRS(crs);
+                if (crs instanceof GeodeticCRS) {
+                    return new DefaultGeographicCRS(properties, ((GeodeticCRS) crs).getDatum(), (EllipsoidalCS) cs);
+                }
+                /*
+                 * In Apache SIS implementation, the Conversion contains the source and target CRS together with
+                 * a MathTransform.   We need to recreate the same conversion, but without CRS and MathTransform
+                 * for letting SIS create or associate new ones, which will be two-dimensional now.
+                 */
+                if (crs instanceof ProjectedCRS) {
+                    final ProjectedCRS  proj = (ProjectedCRS) crs;
+                    final GeographicCRS base = (GeographicCRS) getHorizontalComponent(proj.getBaseCRS());
+                    Conversion fromBase = proj.getConversionFromBase();
+                    fromBase = new DefaultConversion(IdentifiedObjects.getProperties(fromBase),
+                            fromBase.getMethod(), null, fromBase.getParameterValues());
+                    return new DefaultProjectedCRS(properties, base, fromBase, (CartesianCS) cs);
+                }
+                /*
+                 * If the CRS is neither geographic or projected, then it is engineering.
+                 */
+                return new DefaultEngineeringCRS(properties, ((EngineeringCRS) crs).getDatum(), cs);
             }
         }
         if (crs instanceof CompoundCRS) {
@@ -894,7 +1023,7 @@ public final class CRS extends Static {
                 }
             }
         }
-        return isHorizontalCRS(crs) ? (SingleCRS) crs : null;
+        return null;
     }
 
     /**
@@ -925,6 +1054,8 @@ public final class CRS extends Static {
      *         The recommended value is {@code false}.
      * @return the first vertical CRS, or {@code null} if none.
      *
+     * @see #compound(CoordinateReferenceSystem...)
+     *
      * @category information
      */
     public static VerticalCRS getVerticalComponent(final CoordinateReferenceSystem crs,
@@ -945,19 +1076,17 @@ public final class CRS extends Static {
                 }
             } while ((a = !a) == allowCreateEllipsoidal);
         }
-        if (allowCreateEllipsoidal && crs instanceof GeodeticCRS) {
+        if (allowCreateEllipsoidal && horizontalCode(crs) == 3) {
             final CoordinateSystem cs = crs.getCoordinateSystem();
-            if (cs instanceof EllipsoidalCS) {                          // See comment in isHorizontalCRS(…) method.
-                final int i = AxisDirections.indexOfColinear(cs, AxisDirection.UP);
-                if (i >= 0) {
-                    final CoordinateSystemAxis axis = cs.getAxis(i);
-                    VerticalCRS c = CommonCRS.Vertical.ELLIPSOIDAL.crs();
-                    if (!c.getCoordinateSystem().getAxis(0).equals(axis)) {
-                        final Map<String,?> properties = IdentifiedObjects.getProperties(c);
-                        c = new DefaultVerticalCRS(properties, c.getDatum(), new DefaultVerticalCS(properties, axis));
-                    }
-                    return c;
+            final int i = AxisDirections.indexOfColinear(cs, AxisDirection.UP);
+            if (i >= 0) {
+                final CoordinateSystemAxis axis = cs.getAxis(i);
+                VerticalCRS c = CommonCRS.Vertical.ELLIPSOIDAL.crs();
+                if (!c.getCoordinateSystem().getAxis(0).equals(axis)) {
+                    final Map<String,?> properties = IdentifiedObjects.getProperties(c);
+                    c = new DefaultVerticalCRS(properties, c.getDatum(), new DefaultVerticalCS(properties, axis));
                 }
+                return c;
             }
         }
         return null;

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -39,8 +39,8 @@ import org.apache.sis.metadata.iso.citat
 import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
 import org.apache.sis.internal.referencing.provider.TransverseMercator;
 import org.apache.sis.internal.referencing.Resources;
+import org.apache.sis.internal.referencing.Fallback;
 import org.apache.sis.internal.util.Constants;
-import org.apache.sis.internal.util.Fallback;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.iso.DefaultNameSpace;
 import org.apache.sis.util.CharSequences;

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -18,6 +18,7 @@ package org.apache.sis.referencing;
 
 import java.util.Map;
 import java.util.Set;
+import java.util.List;
 import java.util.LinkedHashSet;
 import java.util.Iterator;
 import java.util.Collection;
@@ -28,7 +29,9 @@ import org.opengis.util.FactoryException
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.crs.CompoundCRS;
 import org.opengis.referencing.operation.CoordinateOperation;
+import org.opengis.referencing.operation.ConcatenatedOperation;
 
 import org.apache.sis.util.Static;
 import org.apache.sis.util.CharSequences;
@@ -36,6 +39,7 @@ import org.apache.sis.util.ArgumentCheck
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.iso.DefaultNameSpace;
 import org.apache.sis.internal.util.Constants;
+import org.apache.sis.internal.util.DefinitionURI;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.metadata.NameMeaning;
 import org.apache.sis.internal.metadata.NameToIdentifier;
@@ -53,7 +57,7 @@ import static org.apache.sis.internal.ut
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Guilhem Legal (Geomatys)
- * @version 0.7
+ * @version 0.8
  *
  * @see CRS
  * @see org.apache.sis.geometry.Envelopes
@@ -379,11 +383,17 @@ public final class IdentifiedObjects ext
     }
 
     /**
-     * Looks up a URN, such as {@code "urn:ogc:def:crs:EPSG:8.2:4326"}, of the specified object.
+     * Looks up a URN, such as {@code "urn:ogc:def:crs:EPSG:9.1:4326"}, of the specified object.
      * This method searches in all {@linkplain org.apache.sis.referencing.factory.GeodeticAuthorityFactory geodetic
      * authority factories} known to SIS for an object {@linkplain org.apache.sis.util.ComparisonMode#APPROXIMATIVE
-     * approximatively equals} to the specified object. If such an object is found, then the URN for the given
-     * authority is returned. Otherwise or if there is ambiguity, this method returns {@code null}.
+     * approximatively equals} to the specified object. Then there is a choice:
+     *
+     * <ul>
+     *   <li>If a single matching object is found in the specified authority factory, then its URN is returned.</li>
+     *   <li>Otherwise if the given object is a {@link CompoundCRS} or {@link ConcatenatedOperation}
+     *       and all components have an URN, then this method returns a combined URN.</li>
+     *   <li>Otherwise this method returns {@code null}.</li>
+     * </ul>
      *
      * <p><strong>Note that this method checks the identifier validity.</strong>
      * If the given object declares explicitly an identifier, then this method will instantiate an object from the
@@ -411,9 +421,59 @@ public final class IdentifiedObjects ext
      * @since 0.7
      */
     public static String lookupURN(final IdentifiedObject object, final Citation authority) throws FactoryException {
+        if (object == null) {
+            return null;
+        }
+        IdentifiedObjectFinder finder;
+        try {
+            finder = newFinder(Citations.getCodeSpace(authority));
+        } catch (NoSuchAuthorityFactoryException e) {
+            warning("lookupURN", e);
+            finder = newFinder(null);
+        }
+        String urn = lookupURN(object, authority, finder);
+        if (urn != null) {
+            return urn;
+        }
+        /*
+         * If we didn't found a URN but the given object is made of smaller components, build a combined URN.
+         * Example: "urn:ogc:def:crs, crs:EPSG::27700, crs:EPSG::5701" (without spaces actually).
+         */
+        final List<? extends IdentifiedObject> components;
+        if (object instanceof CompoundCRS) {
+            components = CRS.getSingleComponents((CompoundCRS) object);
+        } else if (object instanceof ConcatenatedOperation) {
+            components = ((ConcatenatedOperation) object).getOperations();
+        } else {
+            return null;
+        }
+        StringBuilder buffer = null;
+        for (final IdentifiedObject component : components) {
+            urn = lookupURN(component, authority, finder);
+            if (urn == null) {
+                return null;
+            }
+            assert urn.startsWith(DefinitionURI.PREFIX) : urn;
+            if (buffer == null) {
+                buffer = new StringBuilder(40).append(DefinitionURI.PREFIX).append(DefinitionURI.SEPARATOR)
+                                              .append(NameMeaning.toObjectType(object.getClass()));
+            }
+            buffer.append(DefinitionURI.COMPONENT_SEPARATOR)
+                  .append(urn, DefinitionURI.PREFIX.length() + 1, urn.length());
+        }
+        return (buffer != null) ? buffer.toString() : null;
+    }
+
+    /**
+     * Implementation of {@link #lookupURN(IdentifiedObject, Citation)}, possibly invoked many times
+     * if the identified object is a {@link CompoundCRS} or {@link ConcatenatedOperation}.
+     */
+    private static String lookupURN(final IdentifiedObject object, final Citation authority,
+                                    final IdentifiedObjectFinder finder) throws FactoryException
+    {
         String urn = null;
         if (object != null) {
-            for (final IdentifiedObject candidate : newFinder(null).find(object)) {
+            for (final IdentifiedObject candidate : finder.find(object)) {
                 String c = toURN(candidate.getClass(), getIdentifier(candidate, authority));
                 if (c == null && authority == null) {
                     /*
@@ -426,6 +486,9 @@ public final class IdentifiedObjects ext
                         if (c != null) break;
                     }
                 }
+                /*
+                 * We should find at most one URN. But if we find many, verify that all of them are consistent.
+                 */
                 if (c != null) {
                     if (urn != null && !urn.equals(c)) {
                         return null;
@@ -477,7 +540,7 @@ public final class IdentifiedObjects ext
                         return null;
                     }
                 } catch (NumberFormatException e) {
-                    Logging.recoverableException(Logging.getLogger(Modules.REFERENCING), IdentifiedObjects.class, "lookupEPSG", e);
+                    warning("lookupEPSG", e);
                 }
             }
         }
@@ -485,6 +548,13 @@ public final class IdentifiedObjects ext
     }
 
     /**
+     * Logs a warning for a non-critical error. The callers should have a fallback.
+     */
+    private static void warning(final String method, final Exception e) {
+        Logging.recoverableException(Logging.getLogger(Modules.REFERENCING), IdentifiedObjects.class, method, e);
+    }
+
+    /**
      * Creates a finder which can be used for looking up unidentified objects.
      * This method is an alternative to {@code lookup(…)} methods when more control are desired.
      *

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -35,7 +35,7 @@ import org.opengis.metadata.Identifier;
 import org.opengis.parameter.InvalidParameterValueException;
 import org.apache.sis.internal.metadata.NameToIdentifier;
 import org.apache.sis.internal.system.DefaultFactories;
-import org.apache.sis.internal.util.Citations;
+import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.metadata.iso.ImmutableIdentifier;
 import org.apache.sis.util.ArgumentChecks;
 

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -35,6 +35,7 @@ import org.opengis.referencing.crs.Proje
 import org.opengis.referencing.crs.EngineeringCRS;
 import org.opengis.referencing.crs.VerticalCRS;
 import org.opengis.referencing.crs.TemporalCRS;
+import org.opengis.referencing.crs.ParametricCRS;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.apache.sis.referencing.cs.AxesConvention;
@@ -42,6 +43,7 @@ import org.apache.sis.referencing.cs.Def
 import org.apache.sis.referencing.AbstractReferenceSystem;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.internal.metadata.WKTKeywords;
+import org.apache.sis.internal.referencing.Resources;
 import org.apache.sis.internal.referencing.WKTUtilities;
 import org.apache.sis.internal.referencing.ReferencingUtilities;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
@@ -107,7 +109,7 @@ import org.apache.sis.io.wkt.Convention;
  * SIS factories and static constants.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.7
+ * @version 0.8
  *
  * @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createCompoundCRS(String)
  *
@@ -184,13 +186,58 @@ public class DefaultCompoundCRS extends
      *
      * @param  properties  the properties to be given to the coordinate reference system.
      * @param  components  the sequence of coordinate reference systems making this compound CRS.
+     * @throws IllegalArgumentException if the given array does not contain at least two components,
+     *         or if two consecutive components are a geographic CRS with an ellipsoidal height.
      *
      * @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createCompoundCRS(Map, CoordinateReferenceSystem...)
      */
     public DefaultCompoundCRS(final Map<String,?> properties, final CoordinateReferenceSystem... components) {
         super(properties, createCoordinateSystem(properties, components));
         setComponents(Arrays.asList(components));
-        // 'singles' is computed by the above method call.
+        /*
+         * 'singles' is computed by the above method call. Now verify that we do not have an ellipsoidal
+         * height with a geographic or projected CRS (see https://issues.apache.org/jira/browse/SIS-303).
+         * Note that this is already be done if the given array does not contain nested CompoundCRS.
+         */
+        if (singles != this.components) {
+            verify(properties, singles.toArray(new SingleCRS[singles.size()]));
+        }
+    }
+
+    /**
+     * Verifies that the given array does not contain duplicated horizontal or vertical components.
+     * Verifies also that if there is an horizontal component, then there is no ellipsoidal height
+     * defined separately.
+     *
+     * @param  properties  the user-specified properties, for determining the locale of error messages.
+     * @param  components  the components to verify.
+     */
+    private static void verify(final Map<String,?> properties, final CoordinateReferenceSystem[] components) {
+        int allTypes = 0;
+        int isProjected = 0;                            // 0 for false, 1 for true.
+        boolean isEllipsoidalHeight = false;
+        for (final CoordinateReferenceSystem component : components) {
+            final int type;
+            if (component instanceof GeodeticCRS) {
+                type = 1;   // Must match the number used in Resources.Keys.DuplicatedSpatialComponents_1.
+            } else if (component instanceof ProjectedCRS) {
+                isProjected = 1;
+                type = 1;   // Intentionally same number than for GeographicCRS case.
+            } else if (component instanceof VerticalCRS) {
+                isEllipsoidalHeight = ReferencingUtilities.isEllipsoidalHeight(((VerticalCRS) component).getDatum());
+                type = 2;   // Must match the number used in Resources.Keys.DuplicatedSpatialComponents_1.
+            } else {
+                continue;   // Skip other types. In particular, we allow 2 temporal CRS (used in meteorology).
+            }
+            if (allTypes == (allTypes |= type)) {
+                throw new IllegalArgumentException(Resources.forProperties(properties)
+                        .getString(Resources.Keys.DuplicatedSpatialComponents_1, type));
+            }
+        }
+        if (isEllipsoidalHeight && ((allTypes & 1) != 0)) {
+            throw new IllegalArgumentException(Resources.forProperties(properties)
+                    .getString(Resources.Keys.EllipsoidalHeightNotAllowed_1, isProjected));
+        }
     }
 
     /**
@@ -204,6 +251,7 @@ public class DefaultCompoundCRS extends
             final CoordinateReferenceSystem[] components)
     {
         ArgumentChecks.ensureNonNull("components", components);
+        verify(properties, components);
         if (components.length < 2) {
             throw new IllegalArgumentException(Errors.getResources(properties).getString(
                     Errors.Keys.TooFewArguments_2, 2, components.length));
@@ -296,7 +344,6 @@ public class DefaultCompoundCRS extends
      *
      * @see #getComponents()
      */
-    @SuppressWarnings("SuspiciousToArrayCall")
     private void setComponents(final List<? extends CoordinateReferenceSystem> crs) {
         if (setSingleComponents(crs)) {
             components = singles;                           // Shares the same list.
@@ -407,7 +454,7 @@ public class DefaultCompoundCRS extends
                     return false;
                 }
                 case 1: {
-                    if (crs instanceof VerticalCRS) {   // TODO: accept also ParametricCRS here.
+                    if (crs instanceof VerticalCRS || crs instanceof ParametricCRS) {
                         state = 2; continue;    // Next CRS can only be temporal.
                     }
                     // Fallthrough (the current CRS may be temporal)

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -88,13 +88,13 @@ import org.apache.sis.measure.Units;
  * and for some polar projections among others.
  *
  * <p>Recent OGC standards mandate the use of axis order as defined by the authority. Oldest OGC standards used the
- * (<var>x</var>,<var>y</var>) axis order instead, ignoring any authority specification. Many softwares still use the
- * old (<var>x</var>,<var>y</var>) axis order, because it is easier to implement. Apache SIS supports both conventions.
+ * (<var>x</var>,<var>y</var>) axis order instead, ignoring any authority specification. Many software products still use
+ * the old (<var>x</var>,<var>y</var>) axis order, because it is easier to implement. Apache SIS supports both conventions.
  * By default, SIS creates CRS with axis order as defined by the authority. Those CRS are created by calls to the
  * {@link org.apache.sis.referencing.CRS#forCode(String)} method. The actual axis order can be verified after the CRS
  * creation with {@code System.out.println(crs)}. If (<var>x</var>,<var>y</var>) axis order is wanted for compatibility
- * with older OGC specifications or other softwares, CRS forced to "longitude first" axis order can be created using the
- * {@link #CONVENTIONALLY_ORIENTED} or {@link #NORMALIZED} enumeration value.</p>
+ * with older OGC specifications or other software products, CRS forced to "longitude first" axis order can be created
+ * using the {@link #CONVENTIONALLY_ORIENTED} or {@link #NORMALIZED} enumeration value.</p>
  *
  * <div class="section">Range of longitude values</div>
  * Most geographic CRS have a longitude axis defined in the [-180 … +180]° range. All map projections in Apache SIS are

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -34,9 +34,9 @@ import org.apache.sis.referencing.Identi
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.ComparisonMode;
-import org.apache.sis.internal.util.Citations;
 import org.apache.sis.internal.metadata.NameToIdentifier;
 import org.apache.sis.internal.metadata.MetadataUtilities;
+import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.io.wkt.ElementKind;
 import org.apache.sis.io.wkt.Formatter;
 
@@ -427,7 +427,7 @@ public class AbstractDatum extends Abstr
                  * and parameters. We extend this rule to datum as well.
                  */
                 final Datum that = (Datum) object;
-                final Boolean match = Citations.hasCommonIdentifier(getIdentifiers(), that.getIdentifiers());
+                final Boolean match = org.apache.sis.internal.util.Citations.hasCommonIdentifier(getIdentifiers(), that.getIdentifiers());
                 if (match != null) {
                     return match;
                 }

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -326,8 +326,8 @@ public class DefaultPrimeMeridian extend
      * If we were not formatting a base CRS, we would have many lines between {@code PrimeMeridian[…]} and
      * {@code AngleUnit[…]} in the above example, which would make less obvious that the angle unit applies
      * also to the prime meridian. It does not bring any ambiguity from an ISO 19162 standard point of view,
-     * but historically some other softwares interpreted the {@code PRIMEM[…]} units wrongly, which is why
-     * we try to find a compromise between keeping the WKT simple and avoiding an historical ambiguity.
+     * but historically some other software products interpreted the {@code PRIMEM[…]} units wrongly, which
+     * is why we try to find a compromise between keeping the WKT simple and avoiding an historical ambiguity.
      *
      * @see org.apache.sis.referencing.crs.AbstractCRS#isBaseCRS(Formatter)
      */

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -569,8 +569,9 @@ abstract class AuthorityFactoryProxy<T>
      */
     private static final Map<String, AuthorityFactoryProxy<?>> BY_URN_TYPE;
     static {
-        final Map<String, AuthorityFactoryProxy<?>> map = new HashMap<>(14);
+        final Map<String, AuthorityFactoryProxy<?>> map = new HashMap<>(16);
         map.put("crs",                  CRS);
+        map.put("crs-compound",         CRS);
         map.put("datum",                DATUM);
         map.put("ellipsoid",            ELLIPSOID);
         map.put("meridian",             PRIME_MERIDIAN);
@@ -610,7 +611,7 @@ abstract class AuthorityFactoryProxy<T>
      * The proxy to use for a given type declared in a URN.
      * For example in the {@code "urn:ogc:def:crs:EPSG::4326"} URN, the proxy to use is {@link #CRS}.
      *
-     * @param  typeName  the URN type.
+     * @param  typeName  the name of URN type.
      * @return the proxy for the given type, or {@code null} if the given type is illegal.
      */
     @SuppressWarnings("unchecked")

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -138,7 +138,7 @@ public abstract class GeodeticAuthorityF
      * @return the namespaces recognized by this factory, or an empty set if none.
      */
     public Set<String> getCodeSpaces() {
-        final String authority = Citations.getCodeSpace(getAuthority());
+        final String authority = org.apache.sis.metadata.iso.citation.Citations.getCodeSpace(getAuthority());
         return (authority != null) ? Collections.singleton(authority) : Collections.emptySet();
     }
 

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -1128,8 +1128,8 @@ public class GeodeticObjectFactory exten
     }
 
     /**
-     * Creates a compound coordinate reference system from an ordered list of {@code CoordinateReferenceSystem} objects.
-     * Apache SIS puts no restriction on the components that can be used in a compound CRS.
+     * Creates a compound coordinate reference system from an ordered list of CRS components.
+     * Apache SIS is permissive on the order of components that can be used in a compound CRS.
      * However for better inter-operability, users are encouraged to follow the order mandated by ISO 19162:
      *
      * <ol>
@@ -1141,19 +1141,20 @@ public class GeodeticObjectFactory exten
      * The default implementation creates a {@link DefaultCompoundCRS} instance.
      *
      * @param  properties  name and other properties to give to the new object.
-     * @param  elements   Ordered array of {@code CoordinateReferenceSystem} objects.
+     * @param  components  the sequence of coordinate reference systems making the compound CRS.
      * @throws FactoryException if the object creation failed.
      *
      * @see DefaultCompoundCRS#DefaultCompoundCRS(Map, CoordinateReferenceSystem...)
      * @see GeodeticAuthorityFactory#createCompoundCRS(String)
+     * @see org.apache.sis.referencing.CRS#compound(CoordinateReferenceSystem...)
      */
     @Override
     public CompoundCRS createCompoundCRS(final Map<String,?> properties,
-            final CoordinateReferenceSystem... elements) throws FactoryException
+            final CoordinateReferenceSystem... components) throws FactoryException
     {
         final DefaultCompoundCRS crs;
         try {
-            crs = new DefaultCompoundCRS(complete(properties), elements);
+            crs = new DefaultCompoundCRS(complete(properties), components);
         } catch (IllegalArgumentException exception) {
             throw new InvalidGeodeticParameterException(exception);
         }

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -20,6 +20,7 @@ import java.util.ServiceLoader;
 import java.util.Collections;
 import java.util.Collection;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.Set;
 import java.util.Map;
@@ -46,13 +47,16 @@ import org.opengis.util.FactoryException
 import org.opengis.util.InternationalString;
 import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.internal.util.AbstractIterator;
-import org.apache.sis.internal.util.Citations;
 import org.apache.sis.internal.util.DefinitionURI;
 import org.apache.sis.internal.util.CollectionsExt;
-import org.apache.sis.internal.util.LazySet;
-import org.apache.sis.internal.util.LazySynchronizedIterator;
 import org.apache.sis.internal.util.SetOfUnknownSize;
+import org.apache.sis.internal.metadata.NameMeaning;
+import org.apache.sis.internal.referencing.LazySet;
 import org.apache.sis.internal.referencing.Resources;
+import org.apache.sis.internal.system.DefaultFactories;
+import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.ArgumentChecks;
@@ -75,7 +79,7 @@ import org.apache.sis.util.collection.Ba
  * then the work is delegated to that factory. Otherwise a {@link NoSuchAuthorityFactoryException} is thrown.</p>
  *
  * <div class="section">URI syntax</div>
- * This factory can also parse URNs of the following forms:
+ * This factory can also parse URNs or URLs of the following forms:
  *
  * <ul>
  *   <li>{@code "urn:ogc:def:}<var>type</var>{@code :}<var>authority</var>{@code :}<var>version</var>{@code :}<var>code</var>{@code "}</li>
@@ -95,6 +99,30 @@ import org.apache.sis.util.collection.Ba
  * instead of {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess#createObject(String)} because of the
  * {@code "crs"} part in the URN. The more specific method gives better performances and avoid ambiguities.</div>
  *
+ * This class accepts also combined URIs of the following forms
+ * (only two components shown, but arbitrary number of components is allowed):
+ *
+ * <ul>
+ *   <li>{@code "urn:ogc:def:}<var>type</var>{@code ,}
+ *       <var>type₁</var>{@code :}<var>authority₁</var>{@code :}<var>version₁</var>{@code :}<var>code₁</var>{@code ,}
+ *       <var>type₂</var>{@code :}<var>authority₂</var>{@code :}<var>version₂</var>{@code :}<var>code₂</var>{@code "}</li>
+ *   <li>{@code  "http://www.opengis.net/def/crs-compound?}<br>
+ *       {@code 1=http://www.opengis.net/def/crs/}<var>authority₁</var>{@code /}<var>version₁</var>{@code /}<var>code₁</var>{@code &}<br>
+ *       {@code 2=http://www.opengis.net/def/crs/}<var>authority₂</var>{@code /}<var>version₂</var>{@code /}<var>code₂</var>{@code "}</li>
+ * </ul>
+ *
+ * Given such URIs, {@code MultiAuthoritiesFactory} invokes {@link #createObject(String)} for each component
+ * and combines the result as described by the {@link CRS#compound(CoordinateReferenceSystem...)} method.
+ * URNs (but not URLs) can also combine a
+ * {@linkplain org.apache.sis.referencing.datum.DefaultGeodeticDatum geodetic datum} with an
+ * {@linkplain org.apache.sis.referencing.cs.DefaultEllipsoidalCS ellipsoidal coordinate system} for creating a new
+ * {@linkplain org.apache.sis.referencing.crs.DefaultGeographicCRS geographic CRS}, or a base geographic CRS with a
+ * {@linkplain org.apache.sis.referencing.operation.DefaultConversion conversion} and a
+ * {@linkplain org.apache.sis.referencing.cs.DefaultCartesianCS Cartesian coordinate system} for creating a new
+ * {@linkplain org.apache.sis.referencing.crs.DefaultProjectedCRS projected coordinate reference system}, or
+ * {@linkplain org.apache.sis.referencing.operation.AbstractCoordinateOperation coordinate operations}
+ * for creating a concatenated operation.
+ *
  * <div class="section">Multiple versions for the same authority</div>
  * {@code MultiAuthoritiesFactory} accepts an arbitrary amount of factories for the same authority, provided that
  * those factories have different version numbers. If a {@code createFoo(String)} method is invoked with a URN
@@ -118,7 +146,7 @@ import org.apache.sis.util.collection.Ba
  * do not need to be thread-safe. See constructor Javadoc for more information.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.7
+ * @version 0.8
  *
  * @see org.apache.sis.referencing.CRS#getAuthorityFactory(String)
  *
@@ -728,21 +756,47 @@ public class MultiAuthoritiesFactory ext
      * @return the object from one of the authority factory specified at construction time.
      * @throws FactoryException if an error occurred while creating the object.
      */
-    final <T> T create(AuthorityFactoryProxy<? extends T> proxy, String code) throws FactoryException {
+    private <T> T create(AuthorityFactoryProxy<? extends T> proxy, String code) throws FactoryException {
         ArgumentChecks.ensureNonNull("code", code);
         final String authority, version;
         final String[] parameters;
         final DefinitionURI uri = DefinitionURI.parse(code);
         if (uri != null) {
+            Class<? extends T> type = proxy.type;
+            proxy = proxy.specialize(uri.type);
+            /*
+             * If the URN or URL contains combined references for compound coordinate reference systems,
+             * create the components. First we verify that all component references have been parsed
+             * before to start creating any object.
+             */
+            if (uri.code == null) {
+                final DefinitionURI[] components = uri.components;
+                if (components != null) {
+                    for (int i=0; i < components.length; i++) {
+                        if (components[i] == null) {
+                            throw new NoSuchAuthorityCodeException(Resources.format(
+                                    Resources.Keys.CanNotParseCombinedReference_2, i+1, uri.isHTTP ? 1 : 0),
+                                    uri.authority, null, uri.toString());
+                        }
+                    }
+                    if (proxy != null) type = proxy.type;       // Use the more specific type declared in the URN.
+                    return combine(type, components, uri.isHTTP);
+                }
+            }
+            /*
+             * At this point we determined that the URN or URL references a single instance (not combined references).
+             * Example: "urn:ogc:def:crs:EPSG:9.1:4326". Verifies that the object type is recognized and that a code
+             * is present. The remainder steps are the same as if the user gave a simple code (e.g. "EPSG:4326").
+             */
             if (uri.authority == null) {
-                throw new NoSuchAuthorityCodeException(Resources.format(Resources.Keys.MissingAuthority_1, code), null, uri.code, code);
+                // We want this check before the 'code' value is modified below.
+                throw new NoSuchAuthorityCodeException(
+                        Resources.format(Resources.Keys.MissingAuthority_1, code), null, uri.code, code);
             }
-            final Class<? extends T> type = proxy.type;
             authority  = uri.authority;
             version    = uri.version;
             code       = uri.code;
             parameters = uri.parameters;
-            proxy      = proxy.specialize(uri.type);
             if (code == null || proxy == null) {
                 final String s = uri.toString();
                 final String message;
@@ -771,7 +825,7 @@ public class MultiAuthoritiesFactory ext
              * Separate the version from the rest of the code. The version is optional. The code may have no room
              * for version (e.g. "EPSG:4326"), or specify an empty version (e.g. "EPSG::4326"). If the version is
              * equals to an empty string or to the "0" string, it will be considered as no version. Usage of 0 as
-             * a pseudo-version is a practice commonly found in other softwares.
+             * a pseudo-version is a practice commonly found in other software products.
              */
             int afterVersion = code.indexOf(DefaultNameSpace.DEFAULT_SEPARATOR, ++afterAuthority);
             start = CharSequences.skipLeadingWhitespaces(code, afterAuthority, afterVersion);
@@ -1487,6 +1541,195 @@ public class MultiAuthoritiesFactory ext
     }
 
     /**
+     * Invoked when a {@code createFoo(…)} method is given a combined URI.
+     * A combined URI is a URN or URL referencing other components. For example if the given URI
+     * is {@code "urn:ogc:def:crs, crs:EPSG::27700, crs:EPSG::5701"}, then the components are:
+     * <ol>
+     *   <li>{@code "urn:ogc:def:crs:EPSG:9.1:27700"}</li>
+     *   <li>{@code "urn:ogc:def:crs:EPSG:9.1:5701"}</li>
+     * </ol>
+     *
+     * We do not require the components to be instance of CRS, since the "Definition identifier URNs in
+     * OGC namespace" best practice paper allows other kinds of combination (e.g. of coordinate operations).
+     *
+     * @param  <T>         compile-time value of {@code type} argument.
+     * @param  type        type of object to create.
+     * @param  references  parsed URI of the components.
+     * @param  isHTTP      whether the user URI is an URL (i.e. {@code "http://something"}) instead than a URN.
+     * @return the combined object.
+     * @throws FactoryException if an error occurred while creating the combined object.
+     */
+    private <T> T combine(final Class<T> type, final DefinitionURI[] references, final boolean isHTTP) throws FactoryException {
+        /*
+         * Identify the type requested by the user and create all components with the assumption that they will
+         * be of that type. This is the most common case. If during iteration we find an object of another kind,
+         * then the array type will be downgraded to IdentifiedObject[]. The 'componentType' variable will keep
+         * its non-null value only if the array stay of the expected sub-type.
+         */
+        final byte requestedType;
+        IdentifiedObject[] components;
+        Class<? extends IdentifiedObject> componentType;
+        if (CoordinateReferenceSystem.class.isAssignableFrom(type)) {
+            requestedType = AuthorityFactoryIdentifier.CRS;
+            componentType = CoordinateReferenceSystem.class;
+            components    = new CoordinateReferenceSystem[references.length];       // Intentional covariance.
+        } else if (CoordinateOperation.class.isAssignableFrom(type)) {
+            requestedType = AuthorityFactoryIdentifier.OPERATION;
+            componentType = CoordinateOperation.class;
+            components    = new CoordinateOperation[references.length];             // Intentional covariance.
+        } else {
+            throw new FactoryException(Resources.format(Resources.Keys.CanNotCombineUriAsType_1, type));
+        }
+        final String expected = NameMeaning.toObjectType(componentType);    // Note: "compound-crs" ⟶ "crs".
+        for (int i=0; i<references.length; i++) {
+            final DefinitionURI ref = references[i];
+            final IdentifiedObject component = createObject(ref.toString());
+            if (componentType != null && (!componentType.isInstance(component) || !expected.equalsIgnoreCase(ref.type))) {
+                componentType = null;
+                components = Arrays.copyOf(components, components.length, IdentifiedObject[].class);
+            }
+            components[i] = component;
+        }
+        /*
+         * At this point we have successfully created all components. The way to interpret those components
+         * depends mostly on the type of object requested by the user. For a given requested type, different
+         * rules apply depending on the type of components. Those rules are described in OGC 07-092r1 (2007):
+         * "Definition identifier URNs in OGC namespace".
+         */
+        IdentifiedObject combined = null;
+        switch (requestedType) {
+            case AuthorityFactoryIdentifier.OPERATION: {
+                if (componentType != null) {
+                    /*
+                     * URN combined references for concatenated operations. We build an operation name from
+                     * the operation identifiers (rather than CRS identifiers) because this is what the user
+                     * gave to us, and because source/target CRS are not guaranteed to be defined. We do not
+                     * yet support swapping roles of source and target CRS if an implied-reverse coordinate
+                     * operation is included.
+                     */
+                    final CoordinateOperation[] ops = (CoordinateOperation[]) components;
+                    String name = IdentifiedObjects.getIdentifierOrName(ops[0]) + " ⟶ "
+                                + IdentifiedObjects.getIdentifierOrName(ops[ops.length - 1]);
+                    combined = DefaultFactories.forBuildin(CoordinateOperationFactory.class)
+                            .createConcatenatedOperation(Collections.singletonMap(CoordinateOperation.NAME_KEY, name), ops);
+                }
+                break;
+            }
+            case AuthorityFactoryIdentifier.CRS: {
+                if (componentType != null) {
+                    /*
+                     * URN combined references for compound coordinate reference systems.
+                     * The URNs of the individual well-known CRSs are listed in the same order in which the
+                     * individual coordinate tuples are combined to form the CompoundCRS coordinate tuple.
+                     */
+                    combined = CRS.compound((CoordinateReferenceSystem[]) components);
+                } else if (!isHTTP) {
+                    final CoordinateSystem cs = remove(references, components, CoordinateSystem.class);
+                    if (cs != null) {
+                        final Datum datum = remove(references, components, Datum.class);
+                        if (datum != null) {
+                            /*
+                             * URN combined references for datum and coordinate system. In this case, the URN shall
+                             * concatenate the URNs of one well-known datum and one well-known coordinate system.
+                             */
+                            if (ArraysExt.allEquals(references, null)) {
+                                combined = combine((GeodeticDatum) datum, cs);
+                            }
+                        } else {
+                            /*
+                             * URN combined references for projected or derived CRSs. In this case, the URN shall
+                             * concatenate the URNs of the one well-known CRS, one well-known Conversion, and one
+                             * well-known CartesianCS. Similar action can be taken for derived CRS.
+                             */
+                            CoordinateReferenceSystem baseCRS = remove(references, components, CoordinateReferenceSystem.class);
+                            CoordinateOperation op = remove(references, components, CoordinateOperation.class);
+                            if (ArraysExt.allEquals(references, null) && op instanceof Conversion) {
+                                combined = combine(baseCRS, (Conversion) op, cs);
+                            }
+                        }
+                    }
+                }
+                break;
+            }
+        }
+        /*
+         * At this point the combined object has been created if we know how to create it.
+         * Maybe the result matches the definition of an existing object in the database,
+         * in which case we will use the existing definition for better metadata.
+         */
+        if (combined == null) {
+            throw new FactoryException(Resources.format(Resources.Keys.UnexpectedComponentInURI));
+        }
+        final IdentifiedObject existing = newIdentifiedObjectFinder().findSingleton(combined);
+        return type.cast(existing != null ? existing : combined);
+    }
+
+    /**
+     * If the given {@code type} is found in the given {@code references}, sets that reference element to {@code null}
+     * and returns the corresponding {@code components} element. Otherwise returns {@code null}. This is equivalent to
+     * {@link Map#remove(Object, Object)} where {@code references} are the keys and {@code components} are the values.
+     * We do not bother building that map because the arrays are very short (2 or 3 elements).
+     */
+    private static <T> T remove(final DefinitionURI[] references, final IdentifiedObject[] components, final Class<T> type) {
+        final String expected = NameMeaning.toObjectType(type);
+        for (int i=0; i<references.length; i++) {
+            final DefinitionURI ref = references[i];
+            if (ref != null && expected.equalsIgnoreCase(ref.type)) {
+                references[i] = null;
+                return type.cast(components[i]);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Invoked when a {@code createFoo(…)} method is given a combined URI containing a datum and a coordinate system.
+     * If the given information are not sufficient or not applicable, then this method returns {@code null}.
+     *
+     * @param  datum  the datum, or {@code null} if missing.
+     * @param  cs     the coordinate system (never null).
+     * @return the combined CRS, or {@code null} if the given information are not sufficient.
+     * @throws FactoryException if an error occurred while creating the combined CRS.
+     */
+    private static GeodeticCRS combine(final GeodeticDatum datum, final CoordinateSystem cs) throws FactoryException {
+        final Map<String,?> properties = IdentifiedObjects.getProperties(datum, Datum.IDENTIFIERS_KEY);
+        final CRSFactory factory = DefaultFactories.forBuildin(CRSFactory.class);
+        if (datum instanceof GeodeticDatum) {
+            if (cs instanceof EllipsoidalCS) {
+                return factory.createGeographicCRS(properties, datum, (EllipsoidalCS) cs);
+            } else if (cs instanceof SphericalCS) {
+                return factory.createGeocentricCRS(properties, datum, (SphericalCS) cs);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Invoked when a {@code createFoo(…)} method is given a combined URI containing a conversion and a coordinate
+     * system. If the given information are not sufficient or not applicable, then this method returns {@code null}.
+     *
+     * @param  baseCRS   the CRS on which the derived CRS will be based on, or {@code null} if missing.
+     * @param  fromBase  the conversion from {@code baseCRS} to the CRS to be created by this method.
+     * @param  cs        the coordinate system (never null).
+     * @return the combined CRS, or {@code null} if the given information are not sufficient.
+     * @throws FactoryException if an error occurred while creating the combined CRS.
+     */
+    private static GeneralDerivedCRS combine(final CoordinateReferenceSystem baseCRS, final Conversion fromBase,
+            final CoordinateSystem cs) throws FactoryException
+    {
+        if (baseCRS != null && fromBase.getSourceCRS() == null && fromBase.getTargetCRS() == null) {
+            final Map<String,?> properties = IdentifiedObjects.getProperties(fromBase, Datum.IDENTIFIERS_KEY);
+            final CRSFactory factory = DefaultFactories.forBuildin(CRSFactory.class);
+            if (baseCRS instanceof GeographicCRS && cs instanceof CartesianCS) {
+                return factory.createProjectedCRS(properties, (GeographicCRS) baseCRS, fromBase, (CartesianCS) cs);
+            } else {
+                return factory.createDerivedCRS(properties, baseCRS, fromBase, cs);
+            }
+        }
+        return null;
+    }
+
+    /**
      * Creates a finder which can be used for looking up unidentified objects.
      * The default implementation delegates the lookups to the underlying factories.
      *

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -151,8 +151,8 @@ import org.apache.sis.internal.util.Stan
  *
  * <div class="section">SQL dialects</div>
  * Because the primary distribution format for the EPSG dataset is MS-Access, this class uses SQL statements formatted
- * for the MS-Access dialect. For usage with other database softwares like PostgreSQL or Derby, a {@link SQLTranslator}
- * instance is provided to the constructor.
+ * for the MS-Access dialect. For usage with other database software products like PostgreSQL or Derby,
+ * a {@link SQLTranslator} instance is provided to the constructor.
  *
  * @author  Yann Cézard (IRD)
  * @author  Martin Desruisseaux (IRD, Geomatys)

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGFactory.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGFactory.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGFactory.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGFactory.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -26,6 +26,7 @@ import java.sql.DatabaseMetaData;
 import java.sql.SQLException;
 import javax.sql.DataSource;
 import java.io.IOException;
+import java.io.FileNotFoundException;
 import org.opengis.util.NameFactory;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.crs.CRSFactory;
@@ -366,7 +367,9 @@ public class EPSGFactory extends Concurr
      * See <a href="https://issues.apache.org/jira/browse/LEGAL-183">LEGAL-183</a> for more information.</p>
      *
      * @param  connection  connection to the database where to create the EPSG schema.
-     * @throws IOException if the SQL script can not be found or an I/O error occurred while reading them.
+     * @throws FileNotFoundException if a SQL script has not been found,
+     *         typically because a required resource is not on the classpath.
+     * @throws IOException  if an I/O error occurred while reading a SQL script.
      * @throws SQLException if an error occurred while writing to the database.
      *
      * @see InstallationScriptProvider

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGInstaller.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGInstaller.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGInstaller.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -18,6 +18,7 @@ package org.apache.sis.referencing.facto
 
 import java.util.Locale;
 import java.io.IOException;
+import java.io.FileNotFoundException;
 import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.SQLException;
@@ -31,7 +32,7 @@ import org.apache.sis.internal.metadata.
 import org.apache.sis.internal.metadata.sql.SQLUtilities;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.internal.util.StandardDateFormat;
-import org.apache.sis.internal.util.Fallback;
+import org.apache.sis.internal.referencing.Fallback;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.resources.Messages;
 import org.apache.sis.util.logging.PerformanceLevel;
@@ -230,7 +231,9 @@ final class EPSGInstaller extends Script
      * Processes to the creation of the EPSG database using the SQL scripts from the given provider.
      *
      * @param  scriptProvider  user-provided scripts, or {@code null} for automatic lookup.
-     * @throws IOException  if an error occurred while reading an input.
+     * @param  locale          the locale for information or warning messages, if any.
+     * @throws FileNotFoundException if a SQL script has not been found.
+     * @throws IOException  if another error occurred while reading an input.
      * @throws SQLException if an error occurred while executing a SQL statement.
      */
     public void run(InstallationResources scriptProvider, final Locale locale) throws SQLException, IOException {
@@ -255,12 +258,14 @@ final class EPSGInstaller extends Script
 
     /**
      * Searches for a SQL script provider on the classpath before to fallback on the default provider.
+     *
+     * @param  locale  the locale for information or warning messages, if any.
      */
     private static InstallationResources lookupProvider(final Locale locale) throws IOException {
         InstallationResources fallback = null;
         for (final InstallationResources provider : DefaultFactories.createServiceLoader(InstallationResources.class)) {
             if (provider.getAuthorities().contains(EPSG)) {
-                if (provider.getClass().isAnnotationPresent(Fallback.class)) {
+                if (!provider.getClass().isAnnotationPresent(Fallback.class)) {
                     return provider;
                 }
                 fallback = provider;

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/InstallationScriptProvider.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/InstallationScriptProvider.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/InstallationScriptProvider.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/InstallationScriptProvider.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -38,6 +38,7 @@ import org.apache.sis.util.logging.Loggi
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.setup.InstallationResources;
 import org.apache.sis.internal.referencing.Resources;
+import org.apache.sis.internal.referencing.Fallback;
 import org.apache.sis.internal.system.DataDirectory;
 import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.internal.util.CollectionsExt;
@@ -205,6 +206,7 @@ public abstract class InstallationScript
      * @return a reader for the content of SQL script to execute.
      * @throws IllegalArgumentException if the given {@code authority} argument is not the expected value.
      * @throws IndexOutOfBoundsException if the given {@code resource} argument is out of bounds.
+     * @throws FileNotFoundException if the SQL script of the given name has not been found.
      * @throws IOException if an error occurred while creating the reader.
      */
     @Override
@@ -294,6 +296,7 @@ public abstract class InstallationScript
      * @since   0.7
      * @module
      */
+    @Fallback
     static final class Default extends InstallationScriptProvider {
         /**
          * The directory containing the scripts, or {@code null} if it does not exist.
@@ -308,6 +311,8 @@ public abstract class InstallationScript
 
         /**
          * Creates a default provider.
+         *
+         * @param locale  the locale for warning messages, if any.
          */
         Default(final Locale locale) throws IOException {
             super(Constants.EPSG,

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/package-info.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/package-info.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/package-info.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/package-info.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -35,6 +35,8 @@
  *       a JDBC connection for the {@code "jdbc:derby:$SIS_DATA/Databases/SpatialMetadata"} URL.</li>
  *   <li>If the {@code "derby.system.home"} {@linkplain java.lang.System#getProperty(String) property} is defined,
  *       a JDBC connection for the {@code "jdbc:derby:SpatialMetadata"} URL.</li>
+ *   <li>If the {@code org.apache.sis.non-free:sis-embedded-data} module is present on the classpath,
+ *       a read-only connection to the database in the JAR file.</li>
  * </ol>
  *
  * In choice 1, the JDBC driver must be provided by the application container (e.g. Apache Tomcat).

Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java [UTF-8] Mon Oct 30 10:25:08 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/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java [UTF-8] Mon Oct 30 10:25:08 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 {