You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2017/10/30 10:25:10 UTC
svn commit: r1813747 [5/13] - in /sis/branches/JDK9: ./ application/
application/sis-console/ application/sis-console/src/main/artifact/
application/sis-console/src/main/artifact/lib/
application/sis-console/src/main/artifact/lib/darwin/ application/si...
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -25,10 +25,12 @@ import org.opengis.util.FactoryException
import org.opengis.geometry.Envelope;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.cs.CartesianCS;
import org.opengis.referencing.cs.EllipsoidalCS;
import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.cs.CoordinateSystemAxis;
+import org.opengis.referencing.cs.CSFactory;
import org.opengis.referencing.crs.CRSFactory;
import org.opengis.referencing.crs.SingleCRS;
import org.opengis.referencing.crs.CompoundCRS;
@@ -41,6 +43,8 @@ import org.opengis.referencing.crs.Proje
import org.opengis.referencing.crs.TemporalCRS;
import org.opengis.referencing.crs.VerticalCRS;
import org.opengis.referencing.crs.EngineeringCRS;
+import org.opengis.referencing.operation.Conversion;
+import org.opengis.referencing.operation.CoordinateOperationFactory;
import org.opengis.referencing.operation.OperationNotFoundException;
import org.opengis.metadata.citation.Citation;
import org.opengis.metadata.extent.Extent;
@@ -53,6 +57,7 @@ import org.apache.sis.measure.Units;
import org.apache.sis.geometry.Envelopes;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.internal.metadata.AxisDirections;
+import org.apache.sis.internal.metadata.EllipsoidalHeightCombiner;
import org.apache.sis.internal.referencing.PositionalAccuracyConstant;
import org.apache.sis.internal.referencing.CoordinateOperations;
import org.apache.sis.internal.referencing.ReferencingUtilities;
@@ -60,14 +65,19 @@ import org.apache.sis.internal.referenci
import org.apache.sis.internal.system.DefaultFactories;
import org.apache.sis.internal.system.Modules;
import org.apache.sis.internal.system.Loggers;
+import org.apache.sis.referencing.cs.AxisFilter;
+import org.apache.sis.referencing.cs.CoordinateSystems;
import org.apache.sis.referencing.cs.DefaultVerticalCS;
-import org.apache.sis.referencing.cs.DefaultEllipsoidalCS;
import org.apache.sis.referencing.crs.DefaultGeographicCRS;
+import org.apache.sis.referencing.crs.DefaultProjectedCRS;
import org.apache.sis.referencing.crs.DefaultVerticalCRS;
import org.apache.sis.referencing.crs.DefaultCompoundCRS;
+import org.apache.sis.referencing.crs.DefaultEngineeringCRS;
import org.apache.sis.referencing.operation.AbstractCoordinateOperation;
import org.apache.sis.referencing.operation.CoordinateOperationContext;
import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
+import org.apache.sis.referencing.operation.DefaultConversion;
+import org.apache.sis.referencing.factory.GeodeticObjectFactory;
import org.apache.sis.referencing.factory.UnavailableFactoryException;
import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
import org.apache.sis.metadata.iso.extent.Extents;
@@ -78,8 +88,6 @@ import org.apache.sis.util.ArgumentCheck
import org.apache.sis.util.Utilities;
import org.apache.sis.util.Static;
-import static java.util.Collections.singletonMap;
-
// Branch-dependent imports
import org.opengis.geometry.Geometry;
@@ -176,7 +184,7 @@ public final class CRS extends Static {
* <tr><td>EPSG:5714</td> <td>{@link CommonCRS.Vertical#MEAN_SEA_LEVEL MEAN_SEA_LEVEL}</td> <td>Vertical</td> <td>Mean Sea Level height</td></tr>
* </table></blockquote>
*
- * This method accepts also the URN and URL syntax.
+ * This method accepts also the URN and URL syntaxes.
* For example the following codes are considered equivalent to {@code "EPSG:4326"}:
* <ul>
* <li>{@code "EPSG::4326"}</li>
@@ -185,6 +193,26 @@ public final class CRS extends Static {
* <li>{@code "http://www.opengis.net/gml/srs/epsg.xml#4326"}</li>
* </ul>
*
+ * URIs can be combined for creating larger objects. For example the following URIs combine a
+ * two-dimensional WGS84 reference system (EPSG:4326) with a Mean Sea Level height (EPSG:5714).
+ * The result is a three-dimensional {@linkplain org.apache.sis.referencing.crs.DefaultCompoundCRS
+ * compound coordinate reference system}:
+ *
+ * <ul>
+ * <li>{@code "urn:ogc:def:crs,crs:EPSG::4326,crs:EPSG::5714"}</li>
+ * <li><code>"http://www.opengis.net/def/crs-compound?<br>
+ * 1=http://www.opengis.net/def/crs/epsg/0/4326&<br>
+ * 2=http://www.opengis.net/def/crs/epsg/0/5714"</code></li>
+ * </ul>
+ *
+ * <p>URNs (but not URLs) can also combine a
+ * {@linkplain org.apache.sis.referencing.datum.DefaultGeodeticDatum geodetic datum} with an
+ * {@linkplain org.apache.sis.referencing.cs.DefaultEllipsoidalCS ellipsoidal coordinate system} for creating a new
+ * {@linkplain org.apache.sis.referencing.crs.DefaultGeographicCRS geographic CRS}, or a base geographic CRS with a
+ * {@linkplain org.apache.sis.referencing.operation.DefaultConversion conversion} and a
+ * {@linkplain org.apache.sis.referencing.cs.DefaultCartesianCS Cartesian coordinate system} for creating a new
+ * {@linkplain org.apache.sis.referencing.crs.DefaultProjectedCRS projected coordinate reference system}.</p>
+ *
* Note that the {@link IdentifiedObjects#lookupURN(IdentifiedObject, Citation)}
* method can be seen as a converse of this method.
* More codes may also be supported depending on which extension modules are available.
@@ -197,6 +225,7 @@ public final class CRS extends Static {
*
* @see #getAuthorityFactory(String)
* @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory
+ * @see <a href="http://epsg-registry.org/">EPSG Geodetic Registry</a>
*
* @category factory
*/
@@ -805,6 +834,61 @@ public final class CRS extends Static {
}
/**
+ * Creates a compound coordinate reference system from an ordered list of CRS components.
+ * A CRS is inferred from the given components and the domain of validity is set to the
+ * {@linkplain org.apache.sis.metadata.iso.extent.DefaultExtent#intersect intersection}
+ * of the domain of validity of all components.
+ *
+ * <div class="section">Ellipsoidal height</div>
+ * If a two-dimensional geographic or projected CRS if followed or preceded by a vertical CRS with ellipsoidal
+ * {@linkplain org.apache.sis.referencing.datum.DefaultVerticalDatum#getVerticalDatumType() datum type}, then
+ * this method combines them in a single three-dimensional geographic or projected CRS. Note that standalone
+ * ellipsoidal heights are not allowed according ISO 19111. But if such situation is nevertheless found, then
+ * the action described here fixes the issue. This is the reverse of <code>{@linkplain #getVerticalComponent
+ * getVerticalComponent}(crs, true)</code>.
+ *
+ * <div class="section">Components order</div>
+ * Apache SIS is permissive on the order of components that can be used in a compound CRS.
+ * However for better inter-operability, users are encouraged to follow the order mandated by ISO 19162:
+ *
+ * <ol>
+ * <li>A mandatory horizontal CRS (only one of two-dimensional {@code GeographicCRS} or {@code ProjectedCRS} or {@code EngineeringCRS}).</li>
+ * <li>Optionally followed by a {@code VerticalCRS} or a {@code ParametricCRS} (but not both).</li>
+ * <li>Optionally followed by a {@code TemporalCRS}.</li>
+ * </ol>
+ *
+ * @param components the sequence of coordinate reference systems making the compound CRS.
+ * @return the compound CRS, or {@code components[0]} if the given array contains only one component.
+ * @throws IllegalArgumentException if the given array is empty or if the array contains incompatible components.
+ * @throws FactoryException if the geodetic factory failed to create the compound CRS.
+ *
+ * @since 0.8
+ *
+ * @see GeodeticObjectFactory#createCompoundCRS(Map, CoordinateReferenceSystem...)
+ */
+ public static CoordinateReferenceSystem compound(final CoordinateReferenceSystem... components) throws FactoryException {
+ ArgumentChecks.ensureNonNull("components", components);
+ switch (components.length) {
+ case 0: {
+ throw new IllegalArgumentException(Errors.format(Errors.Keys.EmptyArgument_1, "components"));
+ }
+ case 1: {
+ final CoordinateReferenceSystem crs = components[0];
+ if (crs != null) return crs;
+ break;
+ }
+ }
+ final Map<String,?> properties = EllipsoidalHeightCombiner.properties(components);
+ return new EllipsoidalHeightCombiner() {
+ @Override public void initialize(final int factoryTypes) {
+ if ((factoryTypes & CRS) != 0) crsFactory = DefaultFactories.forBuildin(CRSFactory.class);
+ if ((factoryTypes & CS) != 0) csFactory = DefaultFactories.forBuildin(CSFactory.class);
+ if ((factoryTypes & OPERATION) != 0) opFactory = DefaultFactories.forBuildin(CoordinateOperationFactory.class);
+ }
+ }.createCompoundCRS(properties, components);
+ }
+
+ /**
* Returns {@code true} if the given CRS is horizontal. The current implementation considers a
* CRS as horizontal if it is two-dimensional and comply with one of the following conditions:
*
@@ -831,20 +915,38 @@ public final class CRS extends Static {
* @category information
*/
public static boolean isHorizontalCRS(final CoordinateReferenceSystem crs) {
+ return horizontalCode(crs) == 2;
+ }
+
+ /**
+ * If the given CRS would quality as horizontal except for its number of dimensions, returns that number.
+ * Otherwise returns 0. The number of dimensions can only be 2 or 3.
+ */
+ private static int horizontalCode(final CoordinateReferenceSystem crs) {
/*
* In order to determine if the CRS is geographic, checking the CoordinateSystem type is more reliable
* then checking if the CRS implements the GeographicCRS interface. This is because the GeographicCRS
- * interface is GeoAPI-specific, so a CRS may be OGC-compliant without implementing that interface.
+ * type did not existed in ISO 19111:2007, so a CRS could be standard-compliant without implementing
+ * the GeographicCRS interface.
*/
+ boolean isEngineering = false;
final boolean isGeodetic = (crs instanceof GeodeticCRS);
- if (isGeodetic || crs instanceof ProjectedCRS || crs instanceof EngineeringCRS) {
- @SuppressWarnings("null")
+ if (isGeodetic || crs instanceof ProjectedCRS || (isEngineering = (crs instanceof EngineeringCRS))) {
final CoordinateSystem cs = crs.getCoordinateSystem();
- if (cs.getDimension() == 2) {
- return !isGeodetic || (cs instanceof EllipsoidalCS);
+ final int dim = cs.getDimension();
+ if ((dim & ~1) == 2 && (!isGeodetic || (cs instanceof EllipsoidalCS))) {
+ if (isEngineering) {
+ int n = 0;
+ for (int i=0; i<dim; i++) {
+ if (AxisDirections.isCompass(cs.getAxis(i).getDirection())) n++;
+ }
+ // If we don't have exactly 2 east, north, etc. directions, consider as non-horizontal.
+ if (n != 2) return 0;
+ }
+ return dim;
}
}
- return false;
+ return 0;
}
/**
@@ -854,8 +956,8 @@ public final class CRS extends Static {
* first horizontal component in the order of the {@linkplain #getSingleComponents(CoordinateReferenceSystem)
* single components list}.
*
- * <p>In the special case where a three-dimensional geographic CRS is found, this method will create a
- * two-dimensional geographic CRS without the vertical axis.</p>
+ * <p>In the special case where a three-dimensional geographic or projected CRS is found, this method
+ * will create a two-dimensional geographic or projected CRS without the vertical axis.</p>
*
* @param crs the coordinate reference system, or {@code null}.
* @return the first horizontal CRS, or {@code null} if none.
@@ -863,26 +965,53 @@ public final class CRS extends Static {
* @category information
*/
public static SingleCRS getHorizontalComponent(final CoordinateReferenceSystem crs) {
- if (crs instanceof GeodeticCRS) {
- CoordinateSystem cs = crs.getCoordinateSystem();
- if (cs instanceof EllipsoidalCS) { // See comment in isHorizontalCRS(…) method.
- final int i = AxisDirections.indexOfColinear(cs, AxisDirection.UP);
- if (i < 0) {
- return (SingleCRS) crs;
- }
- final CoordinateSystemAxis xAxis = cs.getAxis(i > 0 ? 0 : 1);
- final CoordinateSystemAxis yAxis = cs.getAxis(i > 1 ? 1 : 2);
- cs = CommonCRS.DEFAULT.geographic().getCoordinateSystem();
- if (!Utilities.equalsIgnoreMetadata(cs.getAxis(0), xAxis) ||
- !Utilities.equalsIgnoreMetadata(cs.getAxis(1), yAxis))
- {
- // We can not reuse the name of the existing CS, because it typically
- // contains text about axes including the axis that we just dropped.
- cs = new DefaultEllipsoidalCS(singletonMap(EllipsoidalCS.NAME_KEY, "Ellipsoidal 2D"), xAxis, yAxis);
- }
- return new DefaultGeographicCRS(
- ReferencingUtilities.getPropertiesForModifiedCRS(crs, CoordinateReferenceSystem.IDENTIFIERS_KEY),
- ((GeodeticCRS) crs).getDatum(), (EllipsoidalCS) cs);
+ switch (horizontalCode(crs)) {
+ /*
+ * If the CRS is already two-dimensional and horizontal, return as-is.
+ * We don't need to check if crs is an instance of SingleCRS since all
+ * CRS accepted by horizontalCode(…) are SingleCRS.
+ */
+ case 2: {
+ return (SingleCRS) crs;
+ }
+ case 3: {
+ /*
+ * The CRS would be horizontal if we can remove the vertical axis. CoordinateSystems.replaceAxes(…)
+ * will do this task for us. We can verify if the operation has been successful by checking that
+ * the number of dimensions has been reduced by 1 (from 3 to 2).
+ */
+ final CoordinateSystem cs = CoordinateSystems.replaceAxes(crs.getCoordinateSystem(), new AxisFilter() {
+ @Override public boolean accept(final CoordinateSystemAxis axis) {
+ return !AxisDirections.isVertical(axis.getDirection());
+ }
+ });
+ if (cs.getDimension() != 2) break;
+ /*
+ * Most of the time, the CRS to rebuild will be geodetic. In such case we known that the
+ * coordinate system is ellipsoidal because (i.e. the CRS is geographic) because it was
+ * a condition verified by horizontalCode(…). A ClassCastException would be a bug.
+ */
+ final Map<String, ?> properties = ReferencingUtilities.getPropertiesForModifiedCRS(crs);
+ if (crs instanceof GeodeticCRS) {
+ return new DefaultGeographicCRS(properties, ((GeodeticCRS) crs).getDatum(), (EllipsoidalCS) cs);
+ }
+ /*
+ * In Apache SIS implementation, the Conversion contains the source and target CRS together with
+ * a MathTransform. We need to recreate the same conversion, but without CRS and MathTransform
+ * for letting SIS create or associate new ones, which will be two-dimensional now.
+ */
+ if (crs instanceof ProjectedCRS) {
+ final ProjectedCRS proj = (ProjectedCRS) crs;
+ final GeographicCRS base = (GeographicCRS) getHorizontalComponent(proj.getBaseCRS());
+ Conversion fromBase = proj.getConversionFromBase();
+ fromBase = new DefaultConversion(IdentifiedObjects.getProperties(fromBase),
+ fromBase.getMethod(), null, fromBase.getParameterValues());
+ return new DefaultProjectedCRS(properties, base, fromBase, (CartesianCS) cs);
+ }
+ /*
+ * If the CRS is neither geographic or projected, then it is engineering.
+ */
+ return new DefaultEngineeringCRS(properties, ((EngineeringCRS) crs).getDatum(), cs);
}
}
if (crs instanceof CompoundCRS) {
@@ -894,7 +1023,7 @@ public final class CRS extends Static {
}
}
}
- return isHorizontalCRS(crs) ? (SingleCRS) crs : null;
+ return null;
}
/**
@@ -925,6 +1054,8 @@ public final class CRS extends Static {
* The recommended value is {@code false}.
* @return the first vertical CRS, or {@code null} if none.
*
+ * @see #compound(CoordinateReferenceSystem...)
+ *
* @category information
*/
public static VerticalCRS getVerticalComponent(final CoordinateReferenceSystem crs,
@@ -945,19 +1076,17 @@ public final class CRS extends Static {
}
} while ((a = !a) == allowCreateEllipsoidal);
}
- if (allowCreateEllipsoidal && crs instanceof GeodeticCRS) {
+ if (allowCreateEllipsoidal && horizontalCode(crs) == 3) {
final CoordinateSystem cs = crs.getCoordinateSystem();
- if (cs instanceof EllipsoidalCS) { // See comment in isHorizontalCRS(…) method.
- final int i = AxisDirections.indexOfColinear(cs, AxisDirection.UP);
- if (i >= 0) {
- final CoordinateSystemAxis axis = cs.getAxis(i);
- VerticalCRS c = CommonCRS.Vertical.ELLIPSOIDAL.crs();
- if (!c.getCoordinateSystem().getAxis(0).equals(axis)) {
- final Map<String,?> properties = IdentifiedObjects.getProperties(c);
- c = new DefaultVerticalCRS(properties, c.getDatum(), new DefaultVerticalCS(properties, axis));
- }
- return c;
+ final int i = AxisDirections.indexOfColinear(cs, AxisDirection.UP);
+ if (i >= 0) {
+ final CoordinateSystemAxis axis = cs.getAxis(i);
+ VerticalCRS c = CommonCRS.Vertical.ELLIPSOIDAL.crs();
+ if (!c.getCoordinateSystem().getAxis(0).equals(axis)) {
+ final Map<String,?> properties = IdentifiedObjects.getProperties(c);
+ c = new DefaultVerticalCRS(properties, c.getDatum(), new DefaultVerticalCS(properties, axis));
}
+ return c;
}
}
return null;
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/EPSGFactoryFallback.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -39,8 +39,8 @@ import org.apache.sis.metadata.iso.citat
import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
import org.apache.sis.internal.referencing.provider.TransverseMercator;
import org.apache.sis.internal.referencing.Resources;
+import org.apache.sis.internal.referencing.Fallback;
import org.apache.sis.internal.util.Constants;
-import org.apache.sis.internal.util.Fallback;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.util.iso.DefaultNameSpace;
import org.apache.sis.util.CharSequences;
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/IdentifiedObjects.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -18,6 +18,7 @@ package org.apache.sis.referencing;
import java.util.Map;
import java.util.Set;
+import java.util.List;
import java.util.LinkedHashSet;
import java.util.Iterator;
import java.util.Collection;
@@ -28,7 +29,9 @@ import org.opengis.util.FactoryException
import org.opengis.metadata.Identifier;
import org.opengis.metadata.citation.Citation;
import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.crs.CompoundCRS;
import org.opengis.referencing.operation.CoordinateOperation;
+import org.opengis.referencing.operation.ConcatenatedOperation;
import org.apache.sis.util.Static;
import org.apache.sis.util.CharSequences;
@@ -36,6 +39,7 @@ import org.apache.sis.util.ArgumentCheck
import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.iso.DefaultNameSpace;
import org.apache.sis.internal.util.Constants;
+import org.apache.sis.internal.util.DefinitionURI;
import org.apache.sis.internal.system.Modules;
import org.apache.sis.internal.metadata.NameMeaning;
import org.apache.sis.internal.metadata.NameToIdentifier;
@@ -53,7 +57,7 @@ import static org.apache.sis.internal.ut
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @author Guilhem Legal (Geomatys)
- * @version 0.7
+ * @version 0.8
*
* @see CRS
* @see org.apache.sis.geometry.Envelopes
@@ -379,11 +383,17 @@ public final class IdentifiedObjects ext
}
/**
- * Looks up a URN, such as {@code "urn:ogc:def:crs:EPSG:8.2:4326"}, of the specified object.
+ * Looks up a URN, such as {@code "urn:ogc:def:crs:EPSG:9.1:4326"}, of the specified object.
* This method searches in all {@linkplain org.apache.sis.referencing.factory.GeodeticAuthorityFactory geodetic
* authority factories} known to SIS for an object {@linkplain org.apache.sis.util.ComparisonMode#APPROXIMATIVE
- * approximatively equals} to the specified object. If such an object is found, then the URN for the given
- * authority is returned. Otherwise or if there is ambiguity, this method returns {@code null}.
+ * approximatively equals} to the specified object. Then there is a choice:
+ *
+ * <ul>
+ * <li>If a single matching object is found in the specified authority factory, then its URN is returned.</li>
+ * <li>Otherwise if the given object is a {@link CompoundCRS} or {@link ConcatenatedOperation}
+ * and all components have an URN, then this method returns a combined URN.</li>
+ * <li>Otherwise this method returns {@code null}.</li>
+ * </ul>
*
* <p><strong>Note that this method checks the identifier validity.</strong>
* If the given object declares explicitly an identifier, then this method will instantiate an object from the
@@ -411,9 +421,59 @@ public final class IdentifiedObjects ext
* @since 0.7
*/
public static String lookupURN(final IdentifiedObject object, final Citation authority) throws FactoryException {
+ if (object == null) {
+ return null;
+ }
+ IdentifiedObjectFinder finder;
+ try {
+ finder = newFinder(Citations.getCodeSpace(authority));
+ } catch (NoSuchAuthorityFactoryException e) {
+ warning("lookupURN", e);
+ finder = newFinder(null);
+ }
+ String urn = lookupURN(object, authority, finder);
+ if (urn != null) {
+ return urn;
+ }
+ /*
+ * If we didn't found a URN but the given object is made of smaller components, build a combined URN.
+ * Example: "urn:ogc:def:crs, crs:EPSG::27700, crs:EPSG::5701" (without spaces actually).
+ */
+ final List<? extends IdentifiedObject> components;
+ if (object instanceof CompoundCRS) {
+ components = CRS.getSingleComponents((CompoundCRS) object);
+ } else if (object instanceof ConcatenatedOperation) {
+ components = ((ConcatenatedOperation) object).getOperations();
+ } else {
+ return null;
+ }
+ StringBuilder buffer = null;
+ for (final IdentifiedObject component : components) {
+ urn = lookupURN(component, authority, finder);
+ if (urn == null) {
+ return null;
+ }
+ assert urn.startsWith(DefinitionURI.PREFIX) : urn;
+ if (buffer == null) {
+ buffer = new StringBuilder(40).append(DefinitionURI.PREFIX).append(DefinitionURI.SEPARATOR)
+ .append(NameMeaning.toObjectType(object.getClass()));
+ }
+ buffer.append(DefinitionURI.COMPONENT_SEPARATOR)
+ .append(urn, DefinitionURI.PREFIX.length() + 1, urn.length());
+ }
+ return (buffer != null) ? buffer.toString() : null;
+ }
+
+ /**
+ * Implementation of {@link #lookupURN(IdentifiedObject, Citation)}, possibly invoked many times
+ * if the identified object is a {@link CompoundCRS} or {@link ConcatenatedOperation}.
+ */
+ private static String lookupURN(final IdentifiedObject object, final Citation authority,
+ final IdentifiedObjectFinder finder) throws FactoryException
+ {
String urn = null;
if (object != null) {
- for (final IdentifiedObject candidate : newFinder(null).find(object)) {
+ for (final IdentifiedObject candidate : finder.find(object)) {
String c = toURN(candidate.getClass(), getIdentifier(candidate, authority));
if (c == null && authority == null) {
/*
@@ -426,6 +486,9 @@ public final class IdentifiedObjects ext
if (c != null) break;
}
}
+ /*
+ * We should find at most one URN. But if we find many, verify that all of them are consistent.
+ */
if (c != null) {
if (urn != null && !urn.equals(c)) {
return null;
@@ -477,7 +540,7 @@ public final class IdentifiedObjects ext
return null;
}
} catch (NumberFormatException e) {
- Logging.recoverableException(Logging.getLogger(Modules.REFERENCING), IdentifiedObjects.class, "lookupEPSG", e);
+ warning("lookupEPSG", e);
}
}
}
@@ -485,6 +548,13 @@ public final class IdentifiedObjects ext
}
/**
+ * Logs a warning for a non-critical error. The callers should have a fallback.
+ */
+ private static void warning(final String method, final Exception e) {
+ Logging.recoverableException(Logging.getLogger(Modules.REFERENCING), IdentifiedObjects.class, method, e);
+ }
+
+ /**
* Creates a finder which can be used for looking up unidentified objects.
* This method is an alternative to {@code lookup(…)} methods when more control are desired.
*
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -35,7 +35,7 @@ import org.opengis.metadata.Identifier;
import org.opengis.parameter.InvalidParameterValueException;
import org.apache.sis.internal.metadata.NameToIdentifier;
import org.apache.sis.internal.system.DefaultFactories;
-import org.apache.sis.internal.util.Citations;
+import org.apache.sis.metadata.iso.citation.Citations;
import org.apache.sis.metadata.iso.ImmutableIdentifier;
import org.apache.sis.util.ArgumentChecks;
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/DefaultCompoundCRS.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -35,6 +35,7 @@ import org.opengis.referencing.crs.Proje
import org.opengis.referencing.crs.EngineeringCRS;
import org.opengis.referencing.crs.VerticalCRS;
import org.opengis.referencing.crs.TemporalCRS;
+import org.opengis.referencing.crs.ParametricCRS;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.cs.CoordinateSystem;
import org.apache.sis.referencing.cs.AxesConvention;
@@ -42,6 +43,7 @@ import org.apache.sis.referencing.cs.Def
import org.apache.sis.referencing.AbstractReferenceSystem;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.internal.metadata.WKTKeywords;
+import org.apache.sis.internal.referencing.Resources;
import org.apache.sis.internal.referencing.WKTUtilities;
import org.apache.sis.internal.referencing.ReferencingUtilities;
import org.apache.sis.internal.util.UnmodifiableArrayList;
@@ -107,7 +109,7 @@ import org.apache.sis.io.wkt.Convention;
* SIS factories and static constants.
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 0.7
+ * @version 0.8
*
* @see org.apache.sis.referencing.factory.GeodeticAuthorityFactory#createCompoundCRS(String)
*
@@ -184,13 +186,58 @@ public class DefaultCompoundCRS extends
*
* @param properties the properties to be given to the coordinate reference system.
* @param components the sequence of coordinate reference systems making this compound CRS.
+ * @throws IllegalArgumentException if the given array does not contain at least two components,
+ * or if two consecutive components are a geographic CRS with an ellipsoidal height.
*
* @see org.apache.sis.referencing.factory.GeodeticObjectFactory#createCompoundCRS(Map, CoordinateReferenceSystem...)
*/
public DefaultCompoundCRS(final Map<String,?> properties, final CoordinateReferenceSystem... components) {
super(properties, createCoordinateSystem(properties, components));
setComponents(Arrays.asList(components));
- // 'singles' is computed by the above method call.
+ /*
+ * 'singles' is computed by the above method call. Now verify that we do not have an ellipsoidal
+ * height with a geographic or projected CRS (see https://issues.apache.org/jira/browse/SIS-303).
+ * Note that this is already be done if the given array does not contain nested CompoundCRS.
+ */
+ if (singles != this.components) {
+ verify(properties, singles.toArray(new SingleCRS[singles.size()]));
+ }
+ }
+
+ /**
+ * Verifies that the given array does not contain duplicated horizontal or vertical components.
+ * Verifies also that if there is an horizontal component, then there is no ellipsoidal height
+ * defined separately.
+ *
+ * @param properties the user-specified properties, for determining the locale of error messages.
+ * @param components the components to verify.
+ */
+ private static void verify(final Map<String,?> properties, final CoordinateReferenceSystem[] components) {
+ int allTypes = 0;
+ int isProjected = 0; // 0 for false, 1 for true.
+ boolean isEllipsoidalHeight = false;
+ for (final CoordinateReferenceSystem component : components) {
+ final int type;
+ if (component instanceof GeodeticCRS) {
+ type = 1; // Must match the number used in Resources.Keys.DuplicatedSpatialComponents_1.
+ } else if (component instanceof ProjectedCRS) {
+ isProjected = 1;
+ type = 1; // Intentionally same number than for GeographicCRS case.
+ } else if (component instanceof VerticalCRS) {
+ isEllipsoidalHeight = ReferencingUtilities.isEllipsoidalHeight(((VerticalCRS) component).getDatum());
+ type = 2; // Must match the number used in Resources.Keys.DuplicatedSpatialComponents_1.
+ } else {
+ continue; // Skip other types. In particular, we allow 2 temporal CRS (used in meteorology).
+ }
+ if (allTypes == (allTypes |= type)) {
+ throw new IllegalArgumentException(Resources.forProperties(properties)
+ .getString(Resources.Keys.DuplicatedSpatialComponents_1, type));
+ }
+ }
+ if (isEllipsoidalHeight && ((allTypes & 1) != 0)) {
+ throw new IllegalArgumentException(Resources.forProperties(properties)
+ .getString(Resources.Keys.EllipsoidalHeightNotAllowed_1, isProjected));
+ }
}
/**
@@ -204,6 +251,7 @@ public class DefaultCompoundCRS extends
final CoordinateReferenceSystem[] components)
{
ArgumentChecks.ensureNonNull("components", components);
+ verify(properties, components);
if (components.length < 2) {
throw new IllegalArgumentException(Errors.getResources(properties).getString(
Errors.Keys.TooFewArguments_2, 2, components.length));
@@ -296,7 +344,6 @@ public class DefaultCompoundCRS extends
*
* @see #getComponents()
*/
- @SuppressWarnings("SuspiciousToArrayCall")
private void setComponents(final List<? extends CoordinateReferenceSystem> crs) {
if (setSingleComponents(crs)) {
components = singles; // Shares the same list.
@@ -407,7 +454,7 @@ public class DefaultCompoundCRS extends
return false;
}
case 1: {
- if (crs instanceof VerticalCRS) { // TODO: accept also ParametricCRS here.
+ if (crs instanceof VerticalCRS || crs instanceof ParametricCRS) {
state = 2; continue; // Next CRS can only be temporal.
}
// Fallthrough (the current CRS may be temporal)
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AxesConvention.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -88,13 +88,13 @@ import org.apache.sis.measure.Units;
* and for some polar projections among others.
*
* <p>Recent OGC standards mandate the use of axis order as defined by the authority. Oldest OGC standards used the
- * (<var>x</var>,<var>y</var>) axis order instead, ignoring any authority specification. Many softwares still use the
- * old (<var>x</var>,<var>y</var>) axis order, because it is easier to implement. Apache SIS supports both conventions.
+ * (<var>x</var>,<var>y</var>) axis order instead, ignoring any authority specification. Many software products still use
+ * the old (<var>x</var>,<var>y</var>) axis order, because it is easier to implement. Apache SIS supports both conventions.
* By default, SIS creates CRS with axis order as defined by the authority. Those CRS are created by calls to the
* {@link org.apache.sis.referencing.CRS#forCode(String)} method. The actual axis order can be verified after the CRS
* creation with {@code System.out.println(crs)}. If (<var>x</var>,<var>y</var>) axis order is wanted for compatibility
- * with older OGC specifications or other softwares, CRS forced to "longitude first" axis order can be created using the
- * {@link #CONVENTIONALLY_ORIENTED} or {@link #NORMALIZED} enumeration value.</p>
+ * with older OGC specifications or other software products, CRS forced to "longitude first" axis order can be created
+ * using the {@link #CONVENTIONALLY_ORIENTED} or {@link #NORMALIZED} enumeration value.</p>
*
* <div class="section">Range of longitude values</div>
* Most geographic CRS have a longitude axis defined in the [-180 … +180]° range. All map projections in Apache SIS are
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -34,9 +34,9 @@ import org.apache.sis.referencing.Identi
import org.apache.sis.util.iso.Types;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.ComparisonMode;
-import org.apache.sis.internal.util.Citations;
import org.apache.sis.internal.metadata.NameToIdentifier;
import org.apache.sis.internal.metadata.MetadataUtilities;
+import org.apache.sis.metadata.iso.citation.Citations;
import org.apache.sis.io.wkt.ElementKind;
import org.apache.sis.io.wkt.Formatter;
@@ -427,7 +427,7 @@ public class AbstractDatum extends Abstr
* and parameters. We extend this rule to datum as well.
*/
final Datum that = (Datum) object;
- final Boolean match = Citations.hasCommonIdentifier(getIdentifiers(), that.getIdentifiers());
+ final Boolean match = org.apache.sis.internal.util.Citations.hasCommonIdentifier(getIdentifiers(), that.getIdentifiers());
if (match != null) {
return match;
}
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -326,8 +326,8 @@ public class DefaultPrimeMeridian extend
* If we were not formatting a base CRS, we would have many lines between {@code PrimeMeridian[…]} and
* {@code AngleUnit[…]} in the above example, which would make less obvious that the angle unit applies
* also to the prime meridian. It does not bring any ambiguity from an ISO 19162 standard point of view,
- * but historically some other softwares interpreted the {@code PRIMEM[…]} units wrongly, which is why
- * we try to find a compromise between keeping the WKT simple and avoiding an historical ambiguity.
+ * but historically some other software products interpreted the {@code PRIMEM[…]} units wrongly, which
+ * is why we try to find a compromise between keeping the WKT simple and avoiding an historical ambiguity.
*
* @see org.apache.sis.referencing.crs.AbstractCRS#isBaseCRS(Formatter)
*/
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/AuthorityFactoryProxy.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -569,8 +569,9 @@ abstract class AuthorityFactoryProxy<T>
*/
private static final Map<String, AuthorityFactoryProxy<?>> BY_URN_TYPE;
static {
- final Map<String, AuthorityFactoryProxy<?>> map = new HashMap<>(14);
+ final Map<String, AuthorityFactoryProxy<?>> map = new HashMap<>(16);
map.put("crs", CRS);
+ map.put("crs-compound", CRS);
map.put("datum", DATUM);
map.put("ellipsoid", ELLIPSOID);
map.put("meridian", PRIME_MERIDIAN);
@@ -610,7 +611,7 @@ abstract class AuthorityFactoryProxy<T>
* The proxy to use for a given type declared in a URN.
* For example in the {@code "urn:ogc:def:crs:EPSG::4326"} URN, the proxy to use is {@link #CRS}.
*
- * @param typeName the URN type.
+ * @param typeName the name of URN type.
* @return the proxy for the given type, or {@code null} if the given type is illegal.
*/
@SuppressWarnings("unchecked")
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -138,7 +138,7 @@ public abstract class GeodeticAuthorityF
* @return the namespaces recognized by this factory, or an empty set if none.
*/
public Set<String> getCodeSpaces() {
- final String authority = Citations.getCodeSpace(getAuthority());
+ final String authority = org.apache.sis.metadata.iso.citation.Citations.getCodeSpace(getAuthority());
return (authority != null) ? Collections.singleton(authority) : Collections.emptySet();
}
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticObjectFactory.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -1128,8 +1128,8 @@ public class GeodeticObjectFactory exten
}
/**
- * Creates a compound coordinate reference system from an ordered list of {@code CoordinateReferenceSystem} objects.
- * Apache SIS puts no restriction on the components that can be used in a compound CRS.
+ * Creates a compound coordinate reference system from an ordered list of CRS components.
+ * Apache SIS is permissive on the order of components that can be used in a compound CRS.
* However for better inter-operability, users are encouraged to follow the order mandated by ISO 19162:
*
* <ol>
@@ -1141,19 +1141,20 @@ public class GeodeticObjectFactory exten
* The default implementation creates a {@link DefaultCompoundCRS} instance.
*
* @param properties name and other properties to give to the new object.
- * @param elements Ordered array of {@code CoordinateReferenceSystem} objects.
+ * @param components the sequence of coordinate reference systems making the compound CRS.
* @throws FactoryException if the object creation failed.
*
* @see DefaultCompoundCRS#DefaultCompoundCRS(Map, CoordinateReferenceSystem...)
* @see GeodeticAuthorityFactory#createCompoundCRS(String)
+ * @see org.apache.sis.referencing.CRS#compound(CoordinateReferenceSystem...)
*/
@Override
public CompoundCRS createCompoundCRS(final Map<String,?> properties,
- final CoordinateReferenceSystem... elements) throws FactoryException
+ final CoordinateReferenceSystem... components) throws FactoryException
{
final DefaultCompoundCRS crs;
try {
- crs = new DefaultCompoundCRS(complete(properties), elements);
+ crs = new DefaultCompoundCRS(complete(properties), components);
} catch (IllegalArgumentException exception) {
throw new InvalidGeodeticParameterException(exception);
}
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -20,6 +20,7 @@ import java.util.ServiceLoader;
import java.util.Collections;
import java.util.Collection;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.Set;
import java.util.Map;
@@ -46,13 +47,16 @@ import org.opengis.util.FactoryException
import org.opengis.util.InternationalString;
import org.apache.sis.internal.system.Loggers;
import org.apache.sis.internal.util.AbstractIterator;
-import org.apache.sis.internal.util.Citations;
import org.apache.sis.internal.util.DefinitionURI;
import org.apache.sis.internal.util.CollectionsExt;
-import org.apache.sis.internal.util.LazySet;
-import org.apache.sis.internal.util.LazySynchronizedIterator;
import org.apache.sis.internal.util.SetOfUnknownSize;
+import org.apache.sis.internal.metadata.NameMeaning;
+import org.apache.sis.internal.referencing.LazySet;
import org.apache.sis.internal.referencing.Resources;
+import org.apache.sis.internal.system.DefaultFactories;
+import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.CharSequences;
import org.apache.sis.util.ArgumentChecks;
@@ -75,7 +79,7 @@ import org.apache.sis.util.collection.Ba
* then the work is delegated to that factory. Otherwise a {@link NoSuchAuthorityFactoryException} is thrown.</p>
*
* <div class="section">URI syntax</div>
- * This factory can also parse URNs of the following forms:
+ * This factory can also parse URNs or URLs of the following forms:
*
* <ul>
* <li>{@code "urn:ogc:def:}<var>type</var>{@code :}<var>authority</var>{@code :}<var>version</var>{@code :}<var>code</var>{@code "}</li>
@@ -95,6 +99,30 @@ import org.apache.sis.util.collection.Ba
* instead of {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess#createObject(String)} because of the
* {@code "crs"} part in the URN. The more specific method gives better performances and avoid ambiguities.</div>
*
+ * This class accepts also combined URIs of the following forms
+ * (only two components shown, but arbitrary number of components is allowed):
+ *
+ * <ul>
+ * <li>{@code "urn:ogc:def:}<var>type</var>{@code ,}
+ * <var>type₁</var>{@code :}<var>authority₁</var>{@code :}<var>version₁</var>{@code :}<var>code₁</var>{@code ,}
+ * <var>type₂</var>{@code :}<var>authority₂</var>{@code :}<var>version₂</var>{@code :}<var>code₂</var>{@code "}</li>
+ * <li>{@code "http://www.opengis.net/def/crs-compound?}<br>
+ * {@code 1=http://www.opengis.net/def/crs/}<var>authority₁</var>{@code /}<var>version₁</var>{@code /}<var>code₁</var>{@code &}<br>
+ * {@code 2=http://www.opengis.net/def/crs/}<var>authority₂</var>{@code /}<var>version₂</var>{@code /}<var>code₂</var>{@code "}</li>
+ * </ul>
+ *
+ * Given such URIs, {@code MultiAuthoritiesFactory} invokes {@link #createObject(String)} for each component
+ * and combines the result as described by the {@link CRS#compound(CoordinateReferenceSystem...)} method.
+ * URNs (but not URLs) can also combine a
+ * {@linkplain org.apache.sis.referencing.datum.DefaultGeodeticDatum geodetic datum} with an
+ * {@linkplain org.apache.sis.referencing.cs.DefaultEllipsoidalCS ellipsoidal coordinate system} for creating a new
+ * {@linkplain org.apache.sis.referencing.crs.DefaultGeographicCRS geographic CRS}, or a base geographic CRS with a
+ * {@linkplain org.apache.sis.referencing.operation.DefaultConversion conversion} and a
+ * {@linkplain org.apache.sis.referencing.cs.DefaultCartesianCS Cartesian coordinate system} for creating a new
+ * {@linkplain org.apache.sis.referencing.crs.DefaultProjectedCRS projected coordinate reference system}, or
+ * {@linkplain org.apache.sis.referencing.operation.AbstractCoordinateOperation coordinate operations}
+ * for creating a concatenated operation.
+ *
* <div class="section">Multiple versions for the same authority</div>
* {@code MultiAuthoritiesFactory} accepts an arbitrary amount of factories for the same authority, provided that
* those factories have different version numbers. If a {@code createFoo(String)} method is invoked with a URN
@@ -118,7 +146,7 @@ import org.apache.sis.util.collection.Ba
* do not need to be thread-safe. See constructor Javadoc for more information.
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 0.7
+ * @version 0.8
*
* @see org.apache.sis.referencing.CRS#getAuthorityFactory(String)
*
@@ -728,21 +756,47 @@ public class MultiAuthoritiesFactory ext
* @return the object from one of the authority factory specified at construction time.
* @throws FactoryException if an error occurred while creating the object.
*/
- final <T> T create(AuthorityFactoryProxy<? extends T> proxy, String code) throws FactoryException {
+ private <T> T create(AuthorityFactoryProxy<? extends T> proxy, String code) throws FactoryException {
ArgumentChecks.ensureNonNull("code", code);
final String authority, version;
final String[] parameters;
final DefinitionURI uri = DefinitionURI.parse(code);
if (uri != null) {
+ Class<? extends T> type = proxy.type;
+ proxy = proxy.specialize(uri.type);
+ /*
+ * If the URN or URL contains combined references for compound coordinate reference systems,
+ * create the components. First we verify that all component references have been parsed
+ * before to start creating any object.
+ */
+ if (uri.code == null) {
+ final DefinitionURI[] components = uri.components;
+ if (components != null) {
+ for (int i=0; i < components.length; i++) {
+ if (components[i] == null) {
+ throw new NoSuchAuthorityCodeException(Resources.format(
+ Resources.Keys.CanNotParseCombinedReference_2, i+1, uri.isHTTP ? 1 : 0),
+ uri.authority, null, uri.toString());
+ }
+ }
+ if (proxy != null) type = proxy.type; // Use the more specific type declared in the URN.
+ return combine(type, components, uri.isHTTP);
+ }
+ }
+ /*
+ * At this point we determined that the URN or URL references a single instance (not combined references).
+ * Example: "urn:ogc:def:crs:EPSG:9.1:4326". Verifies that the object type is recognized and that a code
+ * is present. The remainder steps are the same as if the user gave a simple code (e.g. "EPSG:4326").
+ */
if (uri.authority == null) {
- throw new NoSuchAuthorityCodeException(Resources.format(Resources.Keys.MissingAuthority_1, code), null, uri.code, code);
+ // We want this check before the 'code' value is modified below.
+ throw new NoSuchAuthorityCodeException(
+ Resources.format(Resources.Keys.MissingAuthority_1, code), null, uri.code, code);
}
- final Class<? extends T> type = proxy.type;
authority = uri.authority;
version = uri.version;
code = uri.code;
parameters = uri.parameters;
- proxy = proxy.specialize(uri.type);
if (code == null || proxy == null) {
final String s = uri.toString();
final String message;
@@ -771,7 +825,7 @@ public class MultiAuthoritiesFactory ext
* Separate the version from the rest of the code. The version is optional. The code may have no room
* for version (e.g. "EPSG:4326"), or specify an empty version (e.g. "EPSG::4326"). If the version is
* equals to an empty string or to the "0" string, it will be considered as no version. Usage of 0 as
- * a pseudo-version is a practice commonly found in other softwares.
+ * a pseudo-version is a practice commonly found in other software products.
*/
int afterVersion = code.indexOf(DefaultNameSpace.DEFAULT_SEPARATOR, ++afterAuthority);
start = CharSequences.skipLeadingWhitespaces(code, afterAuthority, afterVersion);
@@ -1487,6 +1541,195 @@ public class MultiAuthoritiesFactory ext
}
/**
+ * Invoked when a {@code createFoo(…)} method is given a combined URI.
+ * A combined URI is a URN or URL referencing other components. For example if the given URI
+ * is {@code "urn:ogc:def:crs, crs:EPSG::27700, crs:EPSG::5701"}, then the components are:
+ * <ol>
+ * <li>{@code "urn:ogc:def:crs:EPSG:9.1:27700"}</li>
+ * <li>{@code "urn:ogc:def:crs:EPSG:9.1:5701"}</li>
+ * </ol>
+ *
+ * We do not require the components to be instance of CRS, since the "Definition identifier URNs in
+ * OGC namespace" best practice paper allows other kinds of combination (e.g. of coordinate operations).
+ *
+ * @param <T> compile-time value of {@code type} argument.
+ * @param type type of object to create.
+ * @param references parsed URI of the components.
+ * @param isHTTP whether the user URI is an URL (i.e. {@code "http://something"}) instead than a URN.
+ * @return the combined object.
+ * @throws FactoryException if an error occurred while creating the combined object.
+ */
+ private <T> T combine(final Class<T> type, final DefinitionURI[] references, final boolean isHTTP) throws FactoryException {
+ /*
+ * Identify the type requested by the user and create all components with the assumption that they will
+ * be of that type. This is the most common case. If during iteration we find an object of another kind,
+ * then the array type will be downgraded to IdentifiedObject[]. The 'componentType' variable will keep
+ * its non-null value only if the array stay of the expected sub-type.
+ */
+ final byte requestedType;
+ IdentifiedObject[] components;
+ Class<? extends IdentifiedObject> componentType;
+ if (CoordinateReferenceSystem.class.isAssignableFrom(type)) {
+ requestedType = AuthorityFactoryIdentifier.CRS;
+ componentType = CoordinateReferenceSystem.class;
+ components = new CoordinateReferenceSystem[references.length]; // Intentional covariance.
+ } else if (CoordinateOperation.class.isAssignableFrom(type)) {
+ requestedType = AuthorityFactoryIdentifier.OPERATION;
+ componentType = CoordinateOperation.class;
+ components = new CoordinateOperation[references.length]; // Intentional covariance.
+ } else {
+ throw new FactoryException(Resources.format(Resources.Keys.CanNotCombineUriAsType_1, type));
+ }
+ final String expected = NameMeaning.toObjectType(componentType); // Note: "compound-crs" ⟶ "crs".
+ for (int i=0; i<references.length; i++) {
+ final DefinitionURI ref = references[i];
+ final IdentifiedObject component = createObject(ref.toString());
+ if (componentType != null && (!componentType.isInstance(component) || !expected.equalsIgnoreCase(ref.type))) {
+ componentType = null;
+ components = Arrays.copyOf(components, components.length, IdentifiedObject[].class);
+ }
+ components[i] = component;
+ }
+ /*
+ * At this point we have successfully created all components. The way to interpret those components
+ * depends mostly on the type of object requested by the user. For a given requested type, different
+ * rules apply depending on the type of components. Those rules are described in OGC 07-092r1 (2007):
+ * "Definition identifier URNs in OGC namespace".
+ */
+ IdentifiedObject combined = null;
+ switch (requestedType) {
+ case AuthorityFactoryIdentifier.OPERATION: {
+ if (componentType != null) {
+ /*
+ * URN combined references for concatenated operations. We build an operation name from
+ * the operation identifiers (rather than CRS identifiers) because this is what the user
+ * gave to us, and because source/target CRS are not guaranteed to be defined. We do not
+ * yet support swapping roles of source and target CRS if an implied-reverse coordinate
+ * operation is included.
+ */
+ final CoordinateOperation[] ops = (CoordinateOperation[]) components;
+ String name = IdentifiedObjects.getIdentifierOrName(ops[0]) + " ⟶ "
+ + IdentifiedObjects.getIdentifierOrName(ops[ops.length - 1]);
+ combined = DefaultFactories.forBuildin(CoordinateOperationFactory.class)
+ .createConcatenatedOperation(Collections.singletonMap(CoordinateOperation.NAME_KEY, name), ops);
+ }
+ break;
+ }
+ case AuthorityFactoryIdentifier.CRS: {
+ if (componentType != null) {
+ /*
+ * URN combined references for compound coordinate reference systems.
+ * The URNs of the individual well-known CRSs are listed in the same order in which the
+ * individual coordinate tuples are combined to form the CompoundCRS coordinate tuple.
+ */
+ combined = CRS.compound((CoordinateReferenceSystem[]) components);
+ } else if (!isHTTP) {
+ final CoordinateSystem cs = remove(references, components, CoordinateSystem.class);
+ if (cs != null) {
+ final Datum datum = remove(references, components, Datum.class);
+ if (datum != null) {
+ /*
+ * URN combined references for datum and coordinate system. In this case, the URN shall
+ * concatenate the URNs of one well-known datum and one well-known coordinate system.
+ */
+ if (ArraysExt.allEquals(references, null)) {
+ combined = combine((GeodeticDatum) datum, cs);
+ }
+ } else {
+ /*
+ * URN combined references for projected or derived CRSs. In this case, the URN shall
+ * concatenate the URNs of the one well-known CRS, one well-known Conversion, and one
+ * well-known CartesianCS. Similar action can be taken for derived CRS.
+ */
+ CoordinateReferenceSystem baseCRS = remove(references, components, CoordinateReferenceSystem.class);
+ CoordinateOperation op = remove(references, components, CoordinateOperation.class);
+ if (ArraysExt.allEquals(references, null) && op instanceof Conversion) {
+ combined = combine(baseCRS, (Conversion) op, cs);
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ /*
+ * At this point the combined object has been created if we know how to create it.
+ * Maybe the result matches the definition of an existing object in the database,
+ * in which case we will use the existing definition for better metadata.
+ */
+ if (combined == null) {
+ throw new FactoryException(Resources.format(Resources.Keys.UnexpectedComponentInURI));
+ }
+ final IdentifiedObject existing = newIdentifiedObjectFinder().findSingleton(combined);
+ return type.cast(existing != null ? existing : combined);
+ }
+
+ /**
+ * If the given {@code type} is found in the given {@code references}, sets that reference element to {@code null}
+ * and returns the corresponding {@code components} element. Otherwise returns {@code null}. This is equivalent to
+ * {@link Map#remove(Object, Object)} where {@code references} are the keys and {@code components} are the values.
+ * We do not bother building that map because the arrays are very short (2 or 3 elements).
+ */
+ private static <T> T remove(final DefinitionURI[] references, final IdentifiedObject[] components, final Class<T> type) {
+ final String expected = NameMeaning.toObjectType(type);
+ for (int i=0; i<references.length; i++) {
+ final DefinitionURI ref = references[i];
+ if (ref != null && expected.equalsIgnoreCase(ref.type)) {
+ references[i] = null;
+ return type.cast(components[i]);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Invoked when a {@code createFoo(…)} method is given a combined URI containing a datum and a coordinate system.
+ * If the given information are not sufficient or not applicable, then this method returns {@code null}.
+ *
+ * @param datum the datum, or {@code null} if missing.
+ * @param cs the coordinate system (never null).
+ * @return the combined CRS, or {@code null} if the given information are not sufficient.
+ * @throws FactoryException if an error occurred while creating the combined CRS.
+ */
+ private static GeodeticCRS combine(final GeodeticDatum datum, final CoordinateSystem cs) throws FactoryException {
+ final Map<String,?> properties = IdentifiedObjects.getProperties(datum, Datum.IDENTIFIERS_KEY);
+ final CRSFactory factory = DefaultFactories.forBuildin(CRSFactory.class);
+ if (datum instanceof GeodeticDatum) {
+ if (cs instanceof EllipsoidalCS) {
+ return factory.createGeographicCRS(properties, datum, (EllipsoidalCS) cs);
+ } else if (cs instanceof SphericalCS) {
+ return factory.createGeocentricCRS(properties, datum, (SphericalCS) cs);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Invoked when a {@code createFoo(…)} method is given a combined URI containing a conversion and a coordinate
+ * system. If the given information are not sufficient or not applicable, then this method returns {@code null}.
+ *
+ * @param baseCRS the CRS on which the derived CRS will be based on, or {@code null} if missing.
+ * @param fromBase the conversion from {@code baseCRS} to the CRS to be created by this method.
+ * @param cs the coordinate system (never null).
+ * @return the combined CRS, or {@code null} if the given information are not sufficient.
+ * @throws FactoryException if an error occurred while creating the combined CRS.
+ */
+ private static GeneralDerivedCRS combine(final CoordinateReferenceSystem baseCRS, final Conversion fromBase,
+ final CoordinateSystem cs) throws FactoryException
+ {
+ if (baseCRS != null && fromBase.getSourceCRS() == null && fromBase.getTargetCRS() == null) {
+ final Map<String,?> properties = IdentifiedObjects.getProperties(fromBase, Datum.IDENTIFIERS_KEY);
+ final CRSFactory factory = DefaultFactories.forBuildin(CRSFactory.class);
+ if (baseCRS instanceof GeographicCRS && cs instanceof CartesianCS) {
+ return factory.createProjectedCRS(properties, (GeographicCRS) baseCRS, fromBase, (CartesianCS) cs);
+ } else {
+ return factory.createDerivedCRS(properties, baseCRS, fromBase, cs);
+ }
+ }
+ return null;
+ }
+
+ /**
* Creates a finder which can be used for looking up unidentified objects.
* The default implementation delegates the lookups to the underlying factories.
*
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -151,8 +151,8 @@ import org.apache.sis.internal.util.Stan
*
* <div class="section">SQL dialects</div>
* Because the primary distribution format for the EPSG dataset is MS-Access, this class uses SQL statements formatted
- * for the MS-Access dialect. For usage with other database softwares like PostgreSQL or Derby, a {@link SQLTranslator}
- * instance is provided to the constructor.
+ * for the MS-Access dialect. For usage with other database software products like PostgreSQL or Derby,
+ * a {@link SQLTranslator} instance is provided to the constructor.
*
* @author Yann Cézard (IRD)
* @author Martin Desruisseaux (IRD, Geomatys)
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGFactory.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGFactory.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGFactory.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGFactory.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -26,6 +26,7 @@ import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import javax.sql.DataSource;
import java.io.IOException;
+import java.io.FileNotFoundException;
import org.opengis.util.NameFactory;
import org.opengis.util.FactoryException;
import org.opengis.referencing.crs.CRSFactory;
@@ -366,7 +367,9 @@ public class EPSGFactory extends Concurr
* See <a href="https://issues.apache.org/jira/browse/LEGAL-183">LEGAL-183</a> for more information.</p>
*
* @param connection connection to the database where to create the EPSG schema.
- * @throws IOException if the SQL script can not be found or an I/O error occurred while reading them.
+ * @throws FileNotFoundException if a SQL script has not been found,
+ * typically because a required resource is not on the classpath.
+ * @throws IOException if an I/O error occurred while reading a SQL script.
* @throws SQLException if an error occurred while writing to the database.
*
* @see InstallationScriptProvider
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGInstaller.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGInstaller.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGInstaller.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -18,6 +18,7 @@ package org.apache.sis.referencing.facto
import java.util.Locale;
import java.io.IOException;
+import java.io.FileNotFoundException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
@@ -31,7 +32,7 @@ import org.apache.sis.internal.metadata.
import org.apache.sis.internal.metadata.sql.SQLUtilities;
import org.apache.sis.internal.system.DefaultFactories;
import org.apache.sis.internal.util.StandardDateFormat;
-import org.apache.sis.internal.util.Fallback;
+import org.apache.sis.internal.referencing.Fallback;
import org.apache.sis.util.Exceptions;
import org.apache.sis.util.resources.Messages;
import org.apache.sis.util.logging.PerformanceLevel;
@@ -230,7 +231,9 @@ final class EPSGInstaller extends Script
* Processes to the creation of the EPSG database using the SQL scripts from the given provider.
*
* @param scriptProvider user-provided scripts, or {@code null} for automatic lookup.
- * @throws IOException if an error occurred while reading an input.
+ * @param locale the locale for information or warning messages, if any.
+ * @throws FileNotFoundException if a SQL script has not been found.
+ * @throws IOException if another error occurred while reading an input.
* @throws SQLException if an error occurred while executing a SQL statement.
*/
public void run(InstallationResources scriptProvider, final Locale locale) throws SQLException, IOException {
@@ -255,12 +258,14 @@ final class EPSGInstaller extends Script
/**
* Searches for a SQL script provider on the classpath before to fallback on the default provider.
+ *
+ * @param locale the locale for information or warning messages, if any.
*/
private static InstallationResources lookupProvider(final Locale locale) throws IOException {
InstallationResources fallback = null;
for (final InstallationResources provider : DefaultFactories.createServiceLoader(InstallationResources.class)) {
if (provider.getAuthorities().contains(EPSG)) {
- if (provider.getClass().isAnnotationPresent(Fallback.class)) {
+ if (!provider.getClass().isAnnotationPresent(Fallback.class)) {
return provider;
}
fallback = provider;
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/InstallationScriptProvider.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/InstallationScriptProvider.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/InstallationScriptProvider.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/InstallationScriptProvider.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -38,6 +38,7 @@ import org.apache.sis.util.logging.Loggi
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.setup.InstallationResources;
import org.apache.sis.internal.referencing.Resources;
+import org.apache.sis.internal.referencing.Fallback;
import org.apache.sis.internal.system.DataDirectory;
import org.apache.sis.internal.system.Loggers;
import org.apache.sis.internal.util.CollectionsExt;
@@ -205,6 +206,7 @@ public abstract class InstallationScript
* @return a reader for the content of SQL script to execute.
* @throws IllegalArgumentException if the given {@code authority} argument is not the expected value.
* @throws IndexOutOfBoundsException if the given {@code resource} argument is out of bounds.
+ * @throws FileNotFoundException if the SQL script of the given name has not been found.
* @throws IOException if an error occurred while creating the reader.
*/
@Override
@@ -294,6 +296,7 @@ public abstract class InstallationScript
* @since 0.7
* @module
*/
+ @Fallback
static final class Default extends InstallationScriptProvider {
/**
* The directory containing the scripts, or {@code null} if it does not exist.
@@ -308,6 +311,8 @@ public abstract class InstallationScript
/**
* Creates a default provider.
+ *
+ * @param locale the locale for warning messages, if any.
*/
Default(final Locale locale) throws IOException {
super(Constants.EPSG,
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/package-info.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/package-info.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/package-info.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/package-info.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -35,6 +35,8 @@
* a JDBC connection for the {@code "jdbc:derby:$SIS_DATA/Databases/SpatialMetadata"} URL.</li>
* <li>If the {@code "derby.system.home"} {@linkplain java.lang.System#getProperty(String) property} is defined,
* a JDBC connection for the {@code "jdbc:derby:SpatialMetadata"} URL.</li>
+ * <li>If the {@code org.apache.sis.non-free:sis-embedded-data} module is present on the classpath,
+ * a read-only connection to the database in the JAR file.</li>
* </ol>
*
* In choice 1, the JDBC driver must be provided by the application container (e.g. Apache Tomcat).
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -17,9 +17,12 @@
package org.apache.sis.referencing.operation;
import java.util.Map;
+import java.util.Set;
import java.util.Objects;
import java.util.Collection;
import java.util.Collections;
+import java.io.IOException;
+import java.io.ObjectInputStream;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlSeeAlso;
@@ -55,6 +58,7 @@ import org.apache.sis.referencing.Abstra
import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.referencing.operation.transform.PassThroughTransform;
import org.apache.sis.internal.referencing.PositionalAccuracyConstant;
+import org.apache.sis.internal.referencing.CoordinateOperations;
import org.apache.sis.internal.referencing.ReferencingUtilities;
import org.apache.sis.internal.referencing.Resources;
import org.apache.sis.internal.referencing.WKTUtilities;
@@ -96,7 +100,7 @@ import static org.apache.sis.util.Utilit
* synchronization.
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 0.7
+ * @version 0.8
* @since 0.6
* @module
*/
@@ -207,6 +211,16 @@ public class AbstractCoordinateOperation
MathTransform transform;
/**
+ * Indices of target dimensions where "wrap around" may happen as a result of this coordinate operation.
+ * This is usually the longitude axis when the source CRS uses the [-180 … +180]° range and the target
+ * CRS uses the [0 … 360]° range, or the converse. If there is no change, then this is an empty set.
+ *
+ * @see #getWrapAroundChanges()
+ * @see #computeTransientFields()
+ */
+ private transient Set<Integer> wrapAroundChanges;
+
+ /**
* Creates a new coordinate operation initialized from the given properties.
* It is caller's responsibility to:
*
@@ -369,6 +383,30 @@ check: for (int isTarget=0; ; isTar
}
}
}
+ computeTransientFields();
+ }
+
+ /**
+ * Computes the {@link #wrapAroundChanges} field after we verified that the coordinate operation is valid.
+ */
+ final void computeTransientFields() {
+ if (sourceCRS != null && targetCRS != null) {
+ wrapAroundChanges = CoordinateOperations.wrapAroundChanges(sourceCRS, targetCRS.getCoordinateSystem());
+ } else {
+ wrapAroundChanges = Collections.emptySet();
+ }
+ }
+
+ /**
+ * Computes transient fields after deserialization.
+ *
+ * @param in the input stream from which to deserialize a coordinate operation.
+ * @throws IOException if an I/O error occurred while reading or if the stream contains invalid data.
+ * @throws ClassNotFoundException if the class serialized on the stream is not on the classpath.
+ */
+ private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ computeTransientFields();
}
/**
@@ -392,6 +430,11 @@ check: for (int isTarget=0; ; isTar
domainOfValidity = operation.getDomainOfValidity();
scope = operation.getScope();
transform = operation.getMathTransform();
+ if (operation instanceof AbstractCoordinateOperation) {
+ wrapAroundChanges = ((AbstractCoordinateOperation) operation).wrapAroundChanges;
+ } else {
+ computeTransientFields();
+ }
}
/**
@@ -727,6 +770,40 @@ check: for (int isTarget=0; ; isTar
}
/**
+ * Returns the indices of target dimensions where "wrap around" may happen as a result of this coordinate operation.
+ * If such change exists, then this is usually the longitude axis when the source CRS uses the [-180 … +180]° range
+ * and the target CRS uses the [0 … 360]° range, or the converse. If there is no change, then this is an empty set.
+ *
+ * <div class="note"><b>Inverse relationship:</b>
+ * sometime the target dimensions returned by this method can be mapped directly to wraparound axes in source CRS,
+ * but this is not always the case. For example consider the following operation chain:
+ *
+ * <center>source projected CRS ⟶ base CRS ⟶ target geographic CRS</center>
+ *
+ * In this example, a wraparound axis in the target CRS (the longitude) can be mapped to a wraparound axis in
+ * the {@linkplain org.apache.sis.referencing.crs.DefaultProjectedCRS#getBaseCRS() base CRS}. But there is no
+ * corresponding wraparound axis in the source CRS because the <em>easting</em> axis in projected CRS does not
+ * have a wraparound range meaning. We could argue that
+ * {@linkplain org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis#getDirection() axis directions} match,
+ * but such matching is not guaranteed to exist since {@code ProjectedCRS} is a special case of
+ * {@code GeneralDerivedCRS} and derived CRS can have rotations.</div>
+ *
+ * <p>The default implementation infers this set by inspecting the source and target coordinate system axes.
+ * It returns the indices of all target axes having {@link org.opengis.referencing.cs.RangeMeaning#WRAPAROUND}
+ * and for which the following condition holds: a colinear source axis exists with compatible unit of measurement,
+ * and the range (taking unit conversions in account) or range meaning of those source and target axes are not
+ * the same.</p>
+ *
+ * @return indices of target dimensions where "wrap around" may happen as a result of this coordinate operation.
+ *
+ * @since 0.8
+ */
+ @SuppressWarnings("ReturnOfCollectionOrArrayField")
+ public Set<Integer> getWrapAroundChanges() {
+ return wrapAroundChanges;
+ }
+
+ /**
* Compares this coordinate operation with the specified object for equality. If the {@code mode} argument
* is {@link ComparisonMode#STRICT} or {@link ComparisonMode#BY_CONTRACT BY_CONTRACT}, then all available
* properties are compared including the {@linkplain #getDomainOfValidity() domain of validity} and the
@@ -974,7 +1051,7 @@ check: for (int isTarget=0; ; isTar
private void setSource(final CoordinateReferenceSystem crs) {
if (sourceCRS == null) {
sourceCRS = crs;
- } else {
+ } else if (!sourceCRS.equals(crs)) { // Could be defined by ConcatenatedOperation.
MetadataUtilities.propertyAlreadySet(AbstractCoordinateOperation.class, "setSource", "sourceCRS");
}
}
@@ -993,7 +1070,7 @@ check: for (int isTarget=0; ; isTar
private void setTarget(final CoordinateReferenceSystem crs) {
if (targetCRS == null) {
targetCRS = crs;
- } else {
+ } else if (!targetCRS.equals(crs)) { // Could be defined by ConcatenatedOperation.
MetadataUtilities.propertyAlreadySet(AbstractCoordinateOperation.class, "setTarget", "targetCRS");
}
}
@@ -1057,4 +1134,11 @@ check: for (int isTarget=0; ; isTar
MetadataUtilities.propertyAlreadySet(AbstractCoordinateOperation.class, "setScope", "scope");
}
}
+
+ /**
+ * Invoked by JAXB after unmarshalling.
+ */
+ void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
+ computeTransientFields();
+ }
}
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java?rev=1813747&r1=1813746&r2=1813747&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/AbstractSingleOperation.java [UTF-8] Mon Oct 30 10:25:08 2017
@@ -70,7 +70,7 @@ import static org.apache.sis.util.Utilit
* {@link DefaultPassThroughOperation}.</p>
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 0.7
+ * @version 0.8
* @since 0.6
* @module
*/
@@ -173,10 +173,8 @@ class AbstractSingleOperation extends Ab
* In the particular case of a {@linkplain PassThroughTransform pass through transform} with more dimensions
* than what we would expect from the given method, the check will rather be performed against the
* {@linkplain PassThroughTransform#getSubTransform() sub transform}.
- *
- * <p>The intend is to allow creation of a three-dimensional {@code ProjectedCRS} with a two-dimensional
- * {@code OperationMethod}, where the third-dimension just pass through. This is not a recommended approach
- * and we do not document that as a supported feature, but we do not prevent it neither.</p>
+ * The intend is to allow creation of a three-dimensional {@code ProjectedCRS} with a two-dimensional
+ * {@code OperationMethod}, where the third-dimension just pass through.
*
* <p>This method tries to locates what seems to be the "core" of the given math transform. The definition
* of "core" is imprecise and may be adjusted in future SIS versions. The current algorithm is as below:</p>
@@ -513,7 +511,9 @@ class AbstractSingleOperation extends Ab
*
* @see <a href="http://issues.apache.org/jira/browse/SIS-291">SIS-291</a>
*/
- private void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
+ @Override
+ final void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
+ super.afterUnmarshal(unmarshaller, parent);
final CoordinateReferenceSystem sourceCRS = super.getSourceCRS();
final CoordinateReferenceSystem targetCRS = super.getTargetCRS();
if (transform == null && sourceCRS != null && targetCRS != null && parameters != null) try {