sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1817597 [6/19] - in /sis/branches/ISO-19115-3: ./ 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/ applica...
Date Sat, 09 Dec 2017 10:57:47 GMT
Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/parameter/MapProjectionDescriptor.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/parameter/MapProjectionDescriptor.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/parameter/MapProjectionDescriptor.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/parameter/MapProjectionDescriptor.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -43,12 +43,12 @@ import org.apache.sis.util.Workaround;
  *       {@code "standard_parallel_1"} and {@code "standard_parallel_2"}</li>
  * </ul>
  *
- * The main purpose of this class is to support transparently the NetCDF ways to express some parameter values.
+ * The main purpose of this class is to support transparently the netCDF ways to express some parameter values.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.6
  *
- * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/reference/StandardCoordinateTransforms.html">NetCDF projection parameters</a>
+ * @see <a href="http://www.unidata.ucar.edu/software/thredds/current/netcdf-java/reference/StandardCoordinateTransforms.html">NetCDF projection parameters</a>
  *
  * @since 0.6
  * @module

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/parameter/MapProjectionParameters.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/parameter/MapProjectionParameters.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/parameter/MapProjectionParameters.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/parameter/MapProjectionParameters.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -164,7 +164,7 @@ final class MapProjectionParameters exte
 
         /**
          * All names known to Apache SIS for the Earth radius parameter.
-         * This is used in some NetCDF files instead of {@code SEMI_MAJOR} and {@code SEMI_MINOR}.
+         * This is used in some netCDF files instead of {@code SEMI_MAJOR} and {@code SEMI_MINOR}.
          * This is not a standard parameter.
          */
         static final ParameterDescriptor<Double> DESCRIPTOR = new DefaultParameterDescriptor<>(
@@ -249,7 +249,7 @@ final class MapProjectionParameters exte
 
         /**
          * All names known to Apache SIS for the inverse flattening parameter.
-         * This is used in some NetCDF files instead of {@code SEMI_MINOR}.
+         * This is used in some netCDF files instead of {@code SEMI_MINOR}.
          * This is not a standard parameter.
          */
         static final ParameterDescriptor<Double> DESCRIPTOR = new DefaultParameterDescriptor<>(
@@ -433,7 +433,7 @@ final class MapProjectionParameters exte
 
         /**
          * All names known to Apache SIS for the standard parallels parameter, as an array of 1 or 2 elements.
-         * This is used in some NetCDF files instead of {@value Constants#STANDARD_PARALLEL_1} and
+         * This is used in some netCDF files instead of {@value Constants#STANDARD_PARALLEL_1} and
          * {@value Constants#STANDARD_PARALLEL_2}. This is not a standard parameter.
          */
         static final ParameterDescriptor<double[]> DESCRIPTOR = new DefaultParameterDescriptor<>(

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterBuilder.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterBuilder.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterBuilder.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterBuilder.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -63,7 +63,7 @@ import static org.apache.sis.util.Argume
  * centered by default on (0°,0°) with no scale factor and no false easting/northing.
  * The projection is valid from 80°S to 84°N and on all the longitude range (±180°).
  * In this example, the <cite>"Longitude of natural origin"</cite> parameter is giving different aliases
- * for illustrating the case of different softwares or standards using different conventions.
+ * for illustrating the case of different software libraries or standards using different conventions.
  *
  * {@preformat java
  *   ParameterBuilder builder = new ParameterBuilder();

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/AuthorityFactories.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/AuthorityFactories.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/AuthorityFactories.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/AuthorityFactories.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.referencing;
 
+import java.util.Iterator;
 import java.util.ServiceLoader;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
@@ -26,10 +27,11 @@ import org.opengis.referencing.cs.CSAuth
 import org.opengis.referencing.crs.CRSAuthorityFactory;
 import org.opengis.referencing.datum.DatumAuthorityFactory;
 import org.opengis.referencing.operation.CoordinateOperationAuthorityFactory;
-import org.apache.sis.internal.util.LazySet;
+import org.apache.sis.internal.referencing.LazySet;
 import org.apache.sis.internal.system.Loggers;
 import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.system.SystemListener;
+import org.apache.sis.internal.referencing.EPSGFactoryProxy;
 import org.apache.sis.referencing.factory.MultiAuthoritiesFactory;
 import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
 import org.apache.sis.referencing.factory.UnavailableFactoryException;
@@ -58,7 +60,8 @@ final class AuthorityFactories<T extends
 
     /**
      * The unique system-wide authority factory instance that contains all factories found on the classpath,
-     * plus the EPSG factory.
+     * plus the EPSG factory. The {@link EPSGFactoryProxy} most be excluded from this list, since the EPSG
+     * factory is handled in a special way.
      */
     static final MultiAuthoritiesFactory ALL = new MultiAuthoritiesFactory(
             new AuthorityFactories<>(CRSAuthorityFactory.class),
@@ -191,8 +194,10 @@ final class AuthorityFactories<T extends
             message = e.toString();
         }
         final LogRecord record = new LogRecord(isWarning ? Level.WARNING : Level.CONFIG, message);
+        if (isWarning && !(e instanceof UnavailableFactoryException)) {
+            record.setThrown(e);
+        }
         record.setLoggerName(Loggers.CRS_FACTORY);
-        if (isWarning) record.setThrown(e);
         Logging.log(CRS.class, "getAuthorityFactory", record);
     }
 
@@ -211,4 +216,18 @@ final class AuthorityFactories<T extends
         EPSG();                         // Force EPSGFactory instantiation if not already done.
         return (T[]) EPSG;
     }
+
+    /**
+     * Invoked by {@link LazySet} for fetching the next element from the given iterator.
+     * Skips the {@link EPSGFactoryProxy} if possible, or returns {@code null} otherwise.
+     * Note that {@link MultiAuthoritiesFactory} is safe to null values.
+     */
+    @Override
+    protected T next(final Iterator<? extends T> it) {
+        T e = it.next();
+        if (e instanceof EPSGFactoryProxy) {
+            e = it.hasNext() ? it.next() : null;
+        }
+        return e;
+    }
 }

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/Builder.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/Builder.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/Builder.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/Builder.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -32,7 +32,7 @@ import org.opengis.metadata.citation.Cit
 import org.opengis.referencing.IdentifiedObject;
 import org.apache.sis.metadata.iso.ImmutableIdentifier;
 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.internal.referencing.DeprecatedCode;
 import org.apache.sis.internal.referencing.DeprecatedName;
 import org.apache.sis.util.iso.Types;

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java [UTF-8] Sat Dec  9 10:57:44 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
      */
@@ -554,6 +583,9 @@ public final class CRS extends Static {
      * Finds a mathematical operation that transforms or converts coordinates from the given source to the
      * given target coordinate reference system. If an estimation of the geographic area containing the points
      * to transform is known, it can be specified for helping this method to find a better suited operation.
+     * If no area of interest is specified, then the current default is the widest
+     * {@linkplain AbstractCoordinateOperation#getDomainOfValidity() domain of validity}.
+     * A future Apache SIS version may also take the country of current locale in account.
      *
      * <div class="note"><b>Note:</b>
      * the area of interest is just one aspect that may affect the coordinate operation.
@@ -667,6 +699,10 @@ public final class CRS extends Static {
      * associated with the given operation. If more than one geographic bounding box is found, then this method
      * computes their {@linkplain DefaultGeographicBoundingBox#add(GeographicBoundingBox) union}.
      *
+     * <p><b>Fallback:</b> if the given operation does not declare explicitely a domain of validity, then this
+     * method computes the intersection of the domain of validity declared by source and target CRS. If no CRS
+     * declare a domain of validity, then this method returns {@code null}.</p>
+     *
      * @param  operation  the coordinate operation for which to get the domain of validity, or {@code null}.
      * @return the geographic area where the operation is valid, or {@code null} if unspecified.
      *
@@ -678,7 +714,15 @@ public final class CRS extends Static {
      * @since 0.7
      */
     public static GeographicBoundingBox getGeographicBoundingBox(final CoordinateOperation operation) {
-        return (operation != null) ? Extents.getGeographicBoundingBox(operation.getDomainOfValidity()) : null;
+        if (operation == null) {
+            return null;
+        }
+        GeographicBoundingBox bbox = Extents.getGeographicBoundingBox(operation.getDomainOfValidity());
+        if (bbox == null) {
+            bbox = Extents.intersection(getGeographicBoundingBox(operation.getSourceCRS()),
+                                        getGeographicBoundingBox(operation.getTargetCRS()));
+        }
+        return bbox;
     }
 
     /**
@@ -790,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:
      *
@@ -816,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;
     }
 
     /**
@@ -839,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.
@@ -848,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) {
@@ -879,7 +1023,7 @@ public final class CRS extends Static {
                 }
             }
         }
-        return isHorizontalCRS(crs) ? (SingleCRS) crs : null;
+        return null;
     }
 
     /**
@@ -910,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,
@@ -930,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/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -973,24 +973,6 @@ public enum CommonCRS {
      */
 
     /**
-     * Returns a Universal Transverse Mercator (UTM) projection for the zone containing the given point.
-     * There is a total of 120 UTM zones, with 60 zones in the North hemisphere and 60 zones in the South hemisphere.
-     *
-     * @param  latitude  a latitude in the desired UTM projection zone.
-     * @param  longitude a longitude in the desired UTM projection zone.
-     * @return a Universal Transverse Mercator projection for the zone containing the given point.
-     *
-     * @since 0.7
-     *
-     * @deprecated Generalized by {@link #universal(double, double)},
-     *             which can also return a UPS projection when appropriate.
-     */
-    @Deprecated
-    public ProjectedCRS UTM(final double latitude, final double longitude) {
-        return universal(Math.signum(latitude), longitude);
-    }
-
-    /**
      * Returns a Universal Transverse Mercator (UTM) or a Universal Polar Stereographic (UPS) projection
      * for the zone containing the given point.
      * There is a total of 120 UTM zones, with 60 zones in the North hemisphere and 60 zones in the South hemisphere.

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java [UTF-8] Sat Dec  9 10:57:44 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/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java [UTF-8] Sat Dec  9 10:57:44 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/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java [UTF-8] Sat Dec  9 10:57:44 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/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java [UTF-8] Sat Dec  9 10:57:44 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/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java [UTF-8] Sat Dec  9 10:57:44 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/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java [UTF-8] Sat Dec  9 10:57:44 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/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java [UTF-8] Sat Dec  9 10:57:44 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/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java [UTF-8] Sat Dec  9 10:57:44 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/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/ConcurrentAuthorityFactory.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -28,6 +28,7 @@ import java.util.IdentityHashMap;
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
 import java.util.logging.LogRecord;
+import java.util.logging.Level;
 import java.lang.ref.WeakReference;
 import java.lang.ref.PhantomReference;
 import java.io.PrintWriter;
@@ -50,6 +51,7 @@ import org.apache.sis.util.Disposable;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.collection.Cache;
+import org.apache.sis.internal.simple.SimpleCitation;
 import org.apache.sis.internal.system.ReferenceQueueConsumer;
 import org.apache.sis.internal.system.DelayedExecutor;
 import org.apache.sis.internal.system.DelayedRunnable;
@@ -111,6 +113,12 @@ public abstract class ConcurrentAuthorit
     private static final long DURATION_FOR_LOGGING = 10_000_000L;       // 10 milliseconds.
 
     /**
+     * Sentinel value when {@link #authority} can not be determined because the data access object
+     * can not be constructed.
+     */
+    private static final Citation UNAVAILABLE = new SimpleCitation("unavailable");
+
+    /**
      * The authority, cached after first requested.
      */
     private transient volatile Citation authority;
@@ -700,7 +708,7 @@ public abstract class ConcurrentAuthorit
     @Override
     public Citation getAuthority() {
         Citation c = authority;
-        if (c == null) try {
+        if (c == null || c == UNAVAILABLE) try {
             final DAO factory = getDataAccess();
             try {
                 /*
@@ -712,8 +720,19 @@ public abstract class ConcurrentAuthorit
                 release("getAuthority", Citation.class, null);
             }
         } catch (FactoryException e) {
-            Logging.unexpectedException(Logging.getLogger(Loggers.CRS_FACTORY),
-                    ConcurrentAuthorityFactory.class, "getAuthority", e);
+            authority = UNAVAILABLE;
+            /*
+             * Use the warning level only on the first failure, then the fine level on all subsequent failures.
+             * Do not log the stack trace if we failed because of UnavailableFactoryException since it may be
+             * normal (the EPSG geodetic dataset is optional, even if strongly recommended).
+             */
+            final LogRecord record = new LogRecord(c == null ? Level.WARNING : Level.FINE, e.getLocalizedMessage());
+            if (!(e instanceof UnavailableFactoryException)) {
+                record.setThrown(e);
+            }
+            record.setLoggerName(Loggers.CRS_FACTORY);
+            Logging.log(ConcurrentAuthorityFactory.class, "getAuthority", record);
+            c = null;
         }
         return c;
     }

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java [UTF-8] Sat Dec  9 10:57:44 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/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java [UTF-8] Sat Dec  9 10:57:44 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/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/IdentifiedObjectFinder.java [UTF-8] Sat Dec  9 10:57:44 2017
@@ -58,7 +58,7 @@ import org.apache.sis.util.Utilities;
  * is thread-safe. If concurrent searches are desired, then a new instance should be created for each thread.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.7
+ * @version 0.8
  *
  * @see GeodeticAuthorityFactory#newIdentifiedObjectFinder()
  * @see IdentifiedObjects#newFinder(String)
@@ -302,7 +302,7 @@ public class IdentifiedObjectFinder {
             final AuthorityFactoryProxy<?> previous = proxy;
             proxy = AuthorityFactoryProxy.getInstance(object.getClass());
             try {
-                if (!ignoreIdentifiers) {
+                if (!ignoreIdentifiers && !ignoreAxes) {
                     /*
                      * First check if one of the identifiers can be used to find directly an identified object.
                      * Verify that the object that we found is actually equal to given one; we do not blindly

Modified: sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java [UTF-8] Sat Dec  9 10:57:44 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/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
URL: http://svn.apache.org/viewvc/sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java?rev=1817597&r1=1817596&r2=1817597&view=diff
==============================================================================
--- sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java [UTF-8] (original)
+++ sis/branches/ISO-19115-3/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java [UTF-8] Sat Dec  9 10:57:44 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)



Mime
View raw message