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&<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.
