sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1812271 [2/3] - in /sis/trunk: ./ core/sis-metadata/src/main/java/org/apache/sis/internal/metadata/ core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/citation/ core/sis-metadata/src/main/java/org/apache/sis/metadata/iso/extent/ c...
Date Mon, 16 Oct 2017 10:14:36 GMT
Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java?rev=1812271&r1=1812270&r2=1812271&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java [UTF-8] Mon Oct 16 10:14:35 2017
@@ -21,14 +21,17 @@ import java.util.List;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.logging.LogRecord;
+import javax.measure.Unit;
 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 +44,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;
@@ -51,6 +56,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;
@@ -58,14 +64,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;
@@ -76,8 +87,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;
-
 
 /**
  * Static methods working on {@linkplain CoordinateReferenceSystem Coordinate Reference Systems}.
@@ -171,7 +180,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>
@@ -180,6 +189,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.
@@ -192,6 +221,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
      */
@@ -760,6 +790,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:
      *
@@ -786,20 +871,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;
     }
 
     /**
@@ -809,8 +912,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.
@@ -818,26 +921,63 @@ 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());
+                    }
+
+                    @Override
+                    public AxisDirection getDirectionReplacement(CoordinateSystemAxis axis, AxisDirection direction) {
+                        return direction;
+                    }
+
+                    @Override
+                    public Unit<?> getUnitReplacement(CoordinateSystemAxis axis, Unit<?> unit) {
+                        return unit;
+                    }
+                });
+                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) {
@@ -849,7 +989,7 @@ public final class CRS extends Static {
                 }
             }
         }
-        return isHorizontalCRS(crs) ? (SingleCRS) crs : null;
+        return null;
     }
 
     /**
@@ -880,6 +1020,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,
@@ -900,19 +1042,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/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java?rev=1812271&r1=1812270&r2=1812271&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java [UTF-8] Mon Oct 16 10:14:35 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;
@@ -29,7 +30,9 @@ import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.ReferenceIdentifier;
+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;
@@ -37,6 +40,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;
@@ -54,7 +58,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
@@ -380,11 +384,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
@@ -412,9 +422,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) {
                     /*
@@ -427,6 +487,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;
@@ -478,7 +541,7 @@ public final class IdentifiedObjects ext
                         return null;
                     }
                 } catch (NumberFormatException e) {
-                    Logging.recoverableException(Logging.getLogger(Modules.REFERENCING), IdentifiedObjects.class, "lookupEPSG", e);
+                    warning("lookupEPSG", e);
                 }
             }
         }
@@ -486,6 +549,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/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java?rev=1812271&r1=1812270&r2=1812271&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java [UTF-8] Mon Oct 16 10:14:35 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/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java?rev=1812271&r1=1812270&r2=1812271&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java [UTF-8] Mon Oct 16 10:14:35 2017
@@ -1136,8 +1136,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>
@@ -1149,19 +1149,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/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java?rev=1812271&r1=1812270&r2=1812271&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java [UTF-8] Mon Oct 16 10:14:35 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;
@@ -49,9 +50,13 @@ import org.apache.sis.internal.util.Abst
 import org.apache.sis.internal.util.DefinitionURI;
 import org.apache.sis.internal.util.CollectionsExt;
 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;
@@ -77,7 +82,7 @@ import org.apache.sis.internal.jdk8.JDK8
  * 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>
@@ -97,6 +102,30 @@ import org.apache.sis.internal.jdk8.JDK8
  * 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
@@ -120,7 +149,7 @@ import org.apache.sis.internal.jdk8.JDK8
  * 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)
  *
@@ -730,21 +759,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;
@@ -1489,6 +1544,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/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CRSPair.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CRSPair.java?rev=1812271&r1=1812270&r2=1812271&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CRSPair.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/CRSPair.java [UTF-8] Mon Oct 16 10:14:35 2017
@@ -122,10 +122,10 @@ final class CRSPair {
     }
 
     /**
-     * Return a string representation of this key.
+     * Returns a string representation of this key.
      */
     @Override
     public String toString() {
-        return label(sourceCRS) + " → " + label(targetCRS);
+        return label(sourceCRS) + " ⟶ " + label(targetCRS);
     }
 }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ProjectionException.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ProjectionException.java?rev=1812271&r1=1812270&r2=1812271&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ProjectionException.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ProjectionException.java [UTF-8] Mon Oct 16 10:14:35 2017
@@ -32,7 +32,7 @@ import org.opengis.referencing.operation
  *   <li>If the expected mathematical value is infinite (for example the Mercator projection at ±90° of latitude),
  *       then the map projection should return a {@link Double#POSITIVE_INFINITY} or {@link Double#NEGATIVE_INFINITY},
  *       depending on the sign of the correct mathematical answer.</li>
- *   <li>If no real number is expected to exist for the input coordinate (for example the root of a negative value),
+ *   <li>If no real number is expected to exist for the input coordinate (for example at a latitude greater than 90°),
  *       then the map projection should return {@link Double#NaN}.</li>
  *   <li>If a real number is expected to exist but the map projection fails to compute it (for example because an
  *       iterative algorithm does not converge), then the projection should throw {@code ProjectionException}.</li>

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/metadata/EllipsoidalHeightCombinerTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/metadata/EllipsoidalHeightCombinerTest.java?rev=1812271&r1=1812270&r2=1812271&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/metadata/EllipsoidalHeightCombinerTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/metadata/EllipsoidalHeightCombinerTest.java [UTF-8] Mon Oct 16 10:14:35 2017
@@ -32,6 +32,7 @@ import org.apache.sis.referencing.crs.Ha
 import org.apache.sis.referencing.factory.GeodeticObjectFactory;
 import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
 import org.apache.sis.referencing.operation.HardCodedConversions;
+import org.apache.sis.metadata.iso.extent.Extents;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
@@ -81,11 +82,11 @@ public final strictfp class EllipsoidalH
     public void testGeographicCRS() throws FactoryException {
         final EllipsoidalHeightCombiner services = create();
         final Map<String,String> properties = Collections.singletonMap(CoordinateReferenceSystem.NAME_KEY, "WGS 84 (4D)");
-        final GeographicCRS horizontal   = HardCodedCRS.WGS84;
-        final GeographicCRS horizontal3D = HardCodedCRS.WGS84_3D;
-        final VerticalCRS   vertical     = HardCodedCRS.ELLIPSOIDAL_HEIGHT;
-        final TemporalCRS   temporal     = HardCodedCRS.TIME;
-        final VerticalCRS   geoidal      = HardCodedCRS.GRAVITY_RELATED_HEIGHT;
+        final GeographicCRS horizontal = HardCodedCRS.WGS84;
+        final GeographicCRS volumetric = HardCodedCRS.WGS84_3D;
+        final VerticalCRS   vertical   = HardCodedCRS.ELLIPSOIDAL_HEIGHT;
+        final TemporalCRS   temporal   = HardCodedCRS.TIME;
+        final VerticalCRS   geoidal    = HardCodedCRS.GRAVITY_RELATED_HEIGHT;
         /*
          * createCompoundCRS(…) should not combine GeographicCRS with non-ellipsoidal height.
          */
@@ -95,12 +96,12 @@ public final strictfp class EllipsoidalH
          * createCompoundCRS(…) should combine GeographicCRS with ellipsoidal height.
          */
         compound = services.createCompoundCRS(properties, horizontal, vertical);
-        assertArrayEqualsIgnoreMetadata(new SingleCRS[] {horizontal3D}, CRS.getSingleComponents(compound).toArray());
+        assertArrayEqualsIgnoreMetadata(new SingleCRS[] {volumetric}, CRS.getSingleComponents(compound).toArray());
         /*
          * createCompoundCRS(…) should combine GeographicCRS with ellipsoidal height and keep time.
          */
         compound = services.createCompoundCRS(properties, horizontal, vertical, temporal);
-        assertArrayEqualsIgnoreMetadata(new SingleCRS[] {horizontal3D, temporal}, CRS.getSingleComponents(compound).toArray());
+        assertArrayEqualsIgnoreMetadata(new SingleCRS[] {volumetric, temporal}, CRS.getSingleComponents(compound).toArray());
         /*
          * Non-standard feature: accept (VerticalCRS + GeodeticCRS) order.
          * The test below use the reverse order for all axes compared to the previous test.
@@ -127,11 +128,11 @@ public final strictfp class EllipsoidalH
         final EllipsoidalHeightCombiner services = create();
         final GeodeticObjectFactory factory = new GeodeticObjectFactory();
         final Map<String,String> properties = Collections.singletonMap(CoordinateReferenceSystem.NAME_KEY, "World Mercator (4D)");
-        final ProjectedCRS horizontal   = factory.createProjectedCRS(properties, HardCodedCRS.WGS84,    HardCodedConversions.MERCATOR, HardCodedCS.PROJECTED);
-        final ProjectedCRS horizontal3D = factory.createProjectedCRS(properties, HardCodedCRS.WGS84_3D, HardCodedConversions.MERCATOR, HardCodedCS.PROJECTED_3D);
-        final VerticalCRS  vertical     = HardCodedCRS.ELLIPSOIDAL_HEIGHT;
-        final TemporalCRS  temporal     = HardCodedCRS.TIME;
-        final VerticalCRS  geoidal      = HardCodedCRS.GRAVITY_RELATED_HEIGHT;
+        final ProjectedCRS horizontal = factory.createProjectedCRS(properties, HardCodedCRS.WGS84,    HardCodedConversions.MERCATOR, HardCodedCS.PROJECTED);
+        final ProjectedCRS volumetric = factory.createProjectedCRS(properties, HardCodedCRS.WGS84_3D, HardCodedConversions.MERCATOR, HardCodedCS.PROJECTED_3D);
+        final VerticalCRS  vertical   = HardCodedCRS.ELLIPSOIDAL_HEIGHT;
+        final TemporalCRS  temporal   = HardCodedCRS.TIME;
+        final VerticalCRS  geoidal    = HardCodedCRS.GRAVITY_RELATED_HEIGHT;
         /*
          * createCompoundCRS(…) should not combine ProjectedCRS with non-ellipsoidal height.
          */
@@ -141,12 +142,12 @@ public final strictfp class EllipsoidalH
          * createCompoundCRS(…) should combine ProjectedCRS with ellipsoidal height.
          */
         compound = services.createCompoundCRS(properties, horizontal, vertical);
-        assertArrayEqualsIgnoreMetadata(new SingleCRS[] {horizontal3D}, CRS.getSingleComponents(compound).toArray());
+        assertArrayEqualsIgnoreMetadata(new SingleCRS[] {volumetric}, CRS.getSingleComponents(compound).toArray());
         /*
          * createCompoundCRS(…) should combine ProjectedCRS with ellipsoidal height and keep time.
          */
         compound = services.createCompoundCRS(properties, horizontal, vertical, temporal);
-        assertArrayEqualsIgnoreMetadata(new SingleCRS[] {horizontal3D, temporal}, CRS.getSingleComponents(compound).toArray());
+        assertArrayEqualsIgnoreMetadata(new SingleCRS[] {volumetric, temporal}, CRS.getSingleComponents(compound).toArray());
         /*
          * Non-standard feature: accept (VerticalCRS + ProjectedCRS) order.
          */
@@ -159,4 +160,15 @@ public final strictfp class EllipsoidalH
                 ((CoordinateReferenceSystem) components[1]).getCoordinateSystem(),
                 AxisDirection.UP, AxisDirection.EAST, AxisDirection.NORTH);
     }
+
+    /**
+     * Tests {@link EllipsoidalHeightCombiner#properties(CoordinateReferenceSystem...)}.
+     */
+    @Test
+    public void testProperties() {
+        final Map<String,?> properties = EllipsoidalHeightCombiner.properties(HardCodedCRS.WGS84, HardCodedCRS.GRAVITY_RELATED_HEIGHT, HardCodedCRS.TIME);
+        assertEquals("WGS 84 + MSL height + Time", properties.remove("name"));
+        assertEquals(Extents.WORLD, properties.remove("domainOfValidity"));
+        assertTrue("No other property expected.", properties.isEmpty());
+    }
 }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java?rev=1812271&r1=1812270&r2=1812271&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java [UTF-8] Mon Oct 16 10:14:35 2017
@@ -93,7 +93,7 @@ public final strictfp class ReferencingU
     }
 
     /**
-     * Tests {@link ReferencingUtilities#getPropertiesForModifiedCRS(IdentifiedObject, String...)}.
+     * Tests {@link ReferencingUtilities#getPropertiesForModifiedCRS(IdentifiedObject)}.
      *
      * @since 0.7
      */

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ServicesForMetadataTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ServicesForMetadataTest.java?rev=1812271&r1=1812270&r2=1812271&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ServicesForMetadataTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ServicesForMetadataTest.java [UTF-8] Mon Oct 16 10:14:35 2017
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.internal.referencing;
 
+import java.util.Date;
 import org.opengis.geometry.Envelope;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.metadata.extent.VerticalExtent;
@@ -23,6 +24,7 @@ import org.opengis.referencing.crs.Coord
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
 import org.apache.sis.metadata.iso.extent.DefaultVerticalExtent;
+import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent;
 import org.apache.sis.metadata.iso.extent.DefaultSpatialTemporalExtent;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.referencing.CommonCRS;
@@ -30,6 +32,8 @@ import org.apache.sis.referencing.crs.Ha
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
+import org.apache.sis.test.TestUtilities;
+import org.junit.Ignore;
 import org.junit.Test;
 
 import static org.apache.sis.test.Assert.*;
@@ -162,4 +166,42 @@ public final strictfp class ServicesForM
         verifySpatialExtent((GeographicBoundingBox) getSingleton(extent.getSpatialExtent()));
         verifyVerticalExtent(CommonCRS.Vertical.MEAN_SEA_LEVEL, extent.getVerticalExtent());
     }
+
+    /**
+     * Tests {@link DefaultVerticalExtent#intersect(VerticalExtent)}.
+     *
+     * @throws TransformException if the transformation failed.
+     *
+     * @since 0.8
+     */
+    @Test
+    public void testVerticalIntersection() throws TransformException {
+        final DefaultVerticalExtent e1 = new DefaultVerticalExtent(1000, 2000, HardCodedCRS.ELLIPSOIDAL_HEIGHT_cm);
+        final DefaultVerticalExtent e2 = new DefaultVerticalExtent(15,   25,   HardCodedCRS.ELLIPSOIDAL_HEIGHT);
+        e1.intersect(e2);
+        assertEquals(new DefaultVerticalExtent(1500, 2000, HardCodedCRS.ELLIPSOIDAL_HEIGHT_cm), e1);
+    }
+
+    /**
+     * Tests {@link DefaultTemporalExtent#intersect(TemporalExtent)}.
+     *
+     * @throws TransformException if the transformation failed.
+     *
+     * @since 0.8
+     */
+    @Test
+    @Ignore("This operation requires the sis-temporal module.")
+    public void testTemporalIntersection() throws TransformException {
+        final DefaultTemporalExtent e1 = new DefaultTemporalExtent();
+        final DefaultTemporalExtent e2 = new DefaultTemporalExtent();
+        final Date t1 = TestUtilities.date("2016-12-05 19:45:20");
+        final Date t2 = TestUtilities.date("2017-02-18 02:12:50");
+        final Date t3 = TestUtilities.date("2017-11-30 23:50:00");
+        final Date t4 = TestUtilities.date("2018-05-20 12:30:45");
+        e1.setBounds(t1, t3);
+        e2.setBounds(t2, t4);
+        e1.intersect(e2);
+        assertEquals("startTime", t2, e1.getStartTime());
+        assertEquals("endTime",   t3, e1.getEndTime());
+    }
 }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/DatumShiftTestCase.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/DatumShiftTestCase.java?rev=1812271&r1=1812270&r2=1812271&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/DatumShiftTestCase.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/provider/DatumShiftTestCase.java [UTF-8] Mon Oct 16 10:14:35 2017
@@ -24,7 +24,7 @@ import java.nio.file.FileSystemNotFoundE
 import org.apache.sis.test.TestCase;
 
 import static org.junit.Assert.*;
-import static org.junit.Assume.*;
+import static org.junit.Assume.assumeFalse;
 
 
 /**

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java?rev=1812271&r1=1812270&r2=1812271&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java [UTF-8] Mon Oct 16 10:14:35 2017
@@ -19,6 +19,7 @@ package org.apache.sis.referencing;
 import java.util.Map;
 import java.util.HashMap;
 import java.util.Arrays;
+import java.util.Collections;
 import org.opengis.util.FactoryException;
 import org.opengis.referencing.NoSuchAuthorityCodeException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
@@ -38,6 +39,7 @@ import org.apache.sis.util.Utilities;
 // Test imports
 import org.apache.sis.referencing.operation.HardCodedConversions;
 import org.apache.sis.referencing.crs.HardCodedCRS;
+import org.apache.sis.referencing.cs.HardCodedCS;
 import org.apache.sis.test.DependsOnMethod;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestCase;
@@ -280,6 +282,26 @@ public final strictfp class CRSTest exte
     }
 
     /**
+     * Tests getting the horizontal and vertical components of a three-dimensional projected CRS.
+     *
+     * @since 0.8
+     */
+    @Test
+    public void testComponentsOfProjectedCRS() {
+        final ProjectedCRS volumetric = new DefaultProjectedCRS(Collections.singletonMap(ProjectedCRS.NAME_KEY, "3D"),
+                HardCodedCRS.WGS84_3D, HardCodedConversions.MERCATOR, HardCodedCS.PROJECTED_3D);
+
+        assertFalse("isHorizontalCRS", CRS.isHorizontalCRS(volumetric));
+        assertNull("getTemporalComponent", CRS.getTemporalComponent(volumetric));
+        assertNull("getVerticalComponent", CRS.getVerticalComponent(volumetric, false));
+        assertEqualsIgnoreMetadata(HardCodedCRS.ELLIPSOIDAL_HEIGHT, CRS.getVerticalComponent(volumetric, true));
+        final SingleCRS horizontal = CRS.getHorizontalComponent(volumetric);
+        assertInstanceOf("getHorizontalComponent", ProjectedCRS.class, horizontal);
+        assertEquals("dimension", 2, horizontal.getCoordinateSystem().getDimension());
+        assertTrue("isHorizontalCRS", CRS.isHorizontalCRS(horizontal));
+    }
+
+    /**
      * Tests {@link CRS#getComponentAt(CoordinateReferenceSystem, int, int)}.
      *
      * @since 0.5
@@ -310,6 +332,26 @@ public final strictfp class CRSTest exte
     }
 
     /**
+     * Tests {@link CRS#compound(CoordinateReferenceSystem...)}.
+     *
+     * @throws FactoryException if an error occurred while creating a compound CRS.
+     *
+     * @since 0.8
+     */
+    @Test
+    public void testCompound() throws FactoryException {
+        try {
+            CRS.compound();
+            fail("Should not accept empty array.");
+        } catch (IllegalArgumentException e) {
+            final String message = e.getMessage();
+            assertTrue(message, message.contains("components"));
+        }
+        assertSame(HardCodedCRS.WGS84, CRS.compound(HardCodedCRS.WGS84));
+        assertEqualsIgnoreMetadata(HardCodedCRS.WGS84_3D, CRS.compound(HardCodedCRS.WGS84, HardCodedCRS.ELLIPSOIDAL_HEIGHT));
+    }
+
+    /**
      * Tests {@link CRS#getComponentAt(CoordinateReferenceSystem, int, int)} on a (x,y,z,t)
      * coordinate reference system having 4 dimensions. All arguments given to this method
      * except the last one are the expected components, which may be {@code null}.

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/CodesTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/CodesTest.java?rev=1812271&r1=1812270&r2=1812271&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/CodesTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/cs/CodesTest.java [UTF-8] Mon Oct 16 10:14:35 2017
@@ -19,19 +19,14 @@ package org.apache.sis.referencing.cs;
 import java.util.Map;
 import javax.measure.Unit;
 import java.lang.reflect.Field;
-import org.opengis.util.FactoryException;
 import org.opengis.referencing.cs.AxisDirection;
 import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.cs.CSAuthorityFactory;
-import org.opengis.referencing.crs.CRSAuthorityFactory;
-import org.apache.sis.referencing.factory.UnavailableFactoryException;
-import org.apache.sis.referencing.factory.sql.EPSGFactory;
-import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.factory.TestFactorySource;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
-import static org.junit.Assume.*;
 
 
 /**
@@ -44,20 +39,6 @@ import static org.junit.Assume.*;
  */
 public final strictfp class CodesTest extends TestCase {
     /**
-     * Returns the EPSG factory, or skips the test if the factory is not available.
-     */
-    private static CSAuthorityFactory factory() throws FactoryException {
-        final CRSAuthorityFactory factory = CRS.getAuthorityFactory("EPSG");
-        assumeTrue("No connection to EPSG dataset.", factory instanceof EPSGFactory);
-        try {
-            assertNotNull(factory.createGeographicCRS("4326"));
-        } catch (UnavailableFactoryException e) {
-            assumeTrue("No connection to EPSG dataset.", false);
-        }
-        return (EPSGFactory) factory;
-    }
-
-    /**
      * Compares the axis directions and units with EPSG definitions.
      *
      * @throws Exception if an error occurred while fetching the codes or querying the database.
@@ -65,7 +46,7 @@ public final strictfp class CodesTest ex
     @Test
     @SuppressWarnings("unchecked")
     public void verify() throws Exception {
-        final CSAuthorityFactory factory = factory();
+        final CSAuthorityFactory factory = TestFactorySource.getSharedFactory();
         final Field field = Codes.class.getDeclaredField("EPSG");
         field.setAccessible(true);
         for (final Codes c : ((Map<Codes,?>) field.get(null)).keySet()) {

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/AuthorityFactoryMock.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/AuthorityFactoryMock.java?rev=1812271&r1=1812270&r2=1812271&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/AuthorityFactoryMock.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/AuthorityFactoryMock.java [UTF-8] Mon Oct 16 10:14:35 2017
@@ -27,6 +27,7 @@ import org.opengis.referencing.crs.CRSAu
 import org.opengis.referencing.crs.GeocentricCRS;
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.crs.VerticalCRS;
+import org.opengis.referencing.cs.EllipsoidalCS;
 import org.opengis.referencing.cs.CSAuthorityFactory;
 import org.opengis.referencing.datum.DatumAuthorityFactory;
 import org.opengis.referencing.datum.GeodeticDatum;
@@ -40,6 +41,7 @@ import org.apache.sis.measure.Units;
 import org.apache.sis.metadata.iso.extent.Extents;
 import org.apache.sis.referencing.datum.HardCodedDatum;
 import org.apache.sis.referencing.crs.HardCodedCRS;
+import org.apache.sis.referencing.cs.HardCodedCS;
 
 import static org.junit.Assert.*;
 
@@ -48,7 +50,7 @@ import static org.junit.Assert.*;
  * A pseudo-authority factory with hard-coded objects.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.7
+ * @version 0.8
  * @since   0.7
  * @module
  */
@@ -114,6 +116,7 @@ public final strictfp class AuthorityFac
         if (type.isAssignableFrom(GeodeticDatum.class)) add(codes, 6326, 6322, 6807, 6301, 6612, 6047);
         if (type.isAssignableFrom(VerticalDatum.class)) add(codes, 5100);
         if (type.isAssignableFrom(VerticalCRS.class))   add(codes, 5714, 9905);
+        if (type.isAssignableFrom(EllipsoidalCS.class)) add(codes, 6422, 6424);
         return codes;
     }
 
@@ -147,6 +150,8 @@ public final strictfp class AuthorityFac
             case 6612: return HardCodedDatum.JGD2000;
             case 6047: return HardCodedDatum.SPHERE;
             case 5100: return HardCodedDatum.MEAN_SEA_LEVEL;
+            case 6422: return HardCodedCS.GEODETIC_φλ;
+            case 6424: return HardCodedCS.GEODETIC_2D;
             default: throw new NoSuchAuthorityCodeException(code, authority.getTitle().toString(), code);
         }
     }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactoryTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactoryTest.java?rev=1812271&r1=1812270&r2=1812271&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactoryTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactoryTest.java [UTF-8] Mon Oct 16 10:14:35 2017
@@ -28,6 +28,8 @@ import org.opengis.referencing.crs.Geoce
 import org.opengis.referencing.crs.GeodeticCRS;
 import org.opengis.referencing.crs.GeographicCRS;
 import org.opengis.referencing.crs.VerticalCRS;
+import org.opengis.referencing.crs.CompoundCRS;
+import org.opengis.referencing.crs.SingleCRS;
 import org.opengis.referencing.datum.Datum;
 import org.opengis.referencing.datum.DatumAuthorityFactory;
 import org.opengis.referencing.datum.GeodeticDatum;
@@ -36,6 +38,7 @@ import org.opengis.referencing.datum.Ver
 import org.opengis.util.FactoryException;
 import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.metadata.iso.extent.Extents;
+import org.apache.sis.referencing.cs.HardCodedCS;
 import org.apache.sis.referencing.crs.HardCodedCRS;
 import org.apache.sis.referencing.datum.HardCodedDatum;
 import org.apache.sis.measure.Units;
@@ -274,6 +277,89 @@ public final strictfp class MultiAuthori
     }
 
     /**
+     * Tests {@code MultiAuthoritiesFactory.createFoo(String)} from codes in the
+     * {@code "urn:ogc:def:type, type₁:authority₁:version₁:code₁, type₂:authority₂:version₂:code₂"} form.
+     *
+     * @throws FactoryException if an authority or a code is not recognized.
+     *
+     * @since 0.8
+     */
+    @Test
+    @DependsOnMethod("testCreateFromURNs")
+    public void testCreateFromCombinedURNs() throws FactoryException {
+        final Set<AuthorityFactoryMock> mock = Collections.singleton(new AuthorityFactoryMock("MOCK", "2.3"));
+        final MultiAuthoritiesFactory factory = new MultiAuthoritiesFactory(mock, mock, mock, null);
+        testCreateFromCombinedURIs(factory, "urn:ogc:def:crs, crs:MOCK::4326, crs:MOCK::5714");
+        /*
+         * Following are more unusual combinations described in OGC 07-092r1 (2007)
+         * "Definition identifier URNs in OGC namespace".
+         */
+        SingleCRS crs = factory.createGeographicCRS("urn:ogc:def:crs, datum:MOCK::6326, cs:MOCK::6424");
+        assertSame("datum", HardCodedDatum.WGS84, crs.getDatum());
+        assertSame("cs", HardCodedCS.GEODETIC_2D, crs.getCoordinateSystem());
+        /*
+         * Verify that invalid combined URIs are rejected.
+         */
+        try {
+            factory.createObject("urn:ogc:def:cs, crs:MOCK::4326, crs:MOCK::5714");
+            fail("Shall not accept to create CoordinateSystem from combined URI.");
+        } catch (FactoryException e) {
+            String message = e.getMessage();
+            assertTrue(message, message.contains("CoordinateSystem"));
+        }
+        try {
+            factory.createObject("urn:ogc:def:crs, datum:MOCK::6326, cs:MOCK::6424, cs:MOCK::6422");
+            fail("Shall not accept to create combined URI with unexpected objects.");
+        } catch (FactoryException e) {
+            assertNotNull(e.getMessage());
+        }
+    }
+
+    /**
+     * Tests {@code MultiAuthoritiesFactory.createFoo(String)} from codes in the
+     * {@code "http://www.opengis.net/def/crs-compound?1=(…)/code₁&2=(…)/code₂"} form.
+     *
+     * @throws FactoryException if an authority or a code is not recognized.
+     *
+     * @since 0.8
+     */
+    @Test
+    @DependsOnMethod("testCreateFromHTTPs")
+    public void testCreateFromCombinedHTTPs() throws FactoryException {
+        final Set<AuthorityFactoryMock> mock = Collections.singleton(new AuthorityFactoryMock("MOCK", "2.3"));
+        final MultiAuthoritiesFactory factory = new MultiAuthoritiesFactory(mock, mock, mock, null);
+        testCreateFromCombinedURIs(factory, "http://www.opengis.net/def/crs-compound?"
+                                        + "1=http://www.opengis.net/def/crs/MOCK/0/4326&"
+                                        + "2=http://www.opengis.net/def/crs/MOCK/0/5714");
+        testCreateFromCombinedURIs(factory, "http://www.opengis.net/def/crs-compound?"
+                                        + "2=http://www.opengis.net/def/crs/MOCK/0/5714&"
+                                        + "1=http://www.opengis.net/def/crs/MOCK/0/4326");
+        /*
+         * Contrarily to URN, the HTTP form shall not accept Datum + CoordinateSystem combination.
+         */
+        try {
+            factory.createObject("http://www.opengis.net/def/crs-compound?"
+                             + "1=http://www.opengis.net/def/datum/MOCK/0/6326&"
+                             + "2=http://www.opengis.net/def/cs/MOCK/0/6424");
+            fail("Shall not accept Datum + CoordinateSystem combination.");
+        } catch (FactoryException e) {
+            assertNotNull(e.getMessage());
+        }
+    }
+
+    /**
+     * Implementation of {@link #testCreateFromCombinedURNs()} and {@link #testCreateFromCombinedHTTPs()}.
+     */
+    private static void testCreateFromCombinedURIs(final MultiAuthoritiesFactory factory, final String heightOnWGS84)
+            throws FactoryException
+    {
+        CompoundCRS crs = factory.createCompoundCRS(heightOnWGS84);
+        assertArrayEquals("WGS 84 + MSL height", new SingleCRS[] {
+            HardCodedCRS.WGS84_φλ, HardCodedCRS.GRAVITY_RELATED_HEIGHT
+        }, crs.getComponents().toArray());
+    }
+
+    /**
      * Tests {@link MultiAuthoritiesFactory#getAuthorityCodes(Class)}.
      *
      * @throws FactoryException if an error occurred while fetching the set of codes.



Mime
View raw message