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 2022/09/07 13:43:34 UTC
[sis] branch geoapi-4.0 updated: Allow instantiation of temporal CRS by identifiers.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 9df7ce65de Allow instantiation of temporal CRS by identifiers.
9df7ce65de is described below
commit 9df7ce65de1454489f54eac5795e2b562c38fb10
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Wed Sep 7 15:42:51 2022 +0200
Allow instantiation of temporal CRS by identifiers.
https://issues.apache.org/jira/browse/SIS-558
---
.../main/java/org/apache/sis/referencing/CRS.java | 52 ++---
.../java/org/apache/sis/referencing/CommonCRS.java | 109 +++++++---
.../referencing/factory/CommonAuthorityCode.java | 159 +++++++++++++++
.../factory/CommonAuthorityFactory.java | 220 +++++++++++----------
.../factory/GeodeticAuthorityFactory.java | 9 +-
.../factory/MultiAuthoritiesFactory.java | 4 +-
.../sis/referencing/factory/package-info.java | 2 +-
.../java/org/apache/sis/referencing/CRSTest.java | 46 ++++-
.../org/apache/sis/referencing/CommonCRSTest.java | 37 +++-
.../factory/CommonAuthorityFactoryTest.java | 32 ++-
.../apache/sis/internal/util/DefinitionURI.java | 14 +-
.../java/org/apache/sis/util/resources/Errors.java | 5 +
.../apache/sis/util/resources/Errors.properties | 1 +
.../apache/sis/util/resources/Errors_fr.properties | 1 +
14 files changed, 518 insertions(+), 173 deletions(-)
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
index 265f31a557..6f11b645a3 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
@@ -158,35 +158,37 @@ public final class CRS extends Static {
* The set of available codes depends on the {@link CRSAuthorityFactory} instances available on the classpath.
* There is many thousands of <a href="https://sis.apache.org/tables/CoordinateReferenceSystems.html">CRS
* defined by EPSG authority or by other authorities</a>.
- * The following table lists a very small subset of codes which are guaranteed to be available
+ * The following table lists a small subset of codes which are guaranteed to be available
* on any installation of Apache SIS:
*
* <blockquote><table class="sis">
* <caption>Minimal set of supported authority codes</caption>
- * <tr><th>Code</th> <th>Enum</th> <th>CRS Type</th> <th>Description</th></tr>
- * <tr><td>CRS:27</td> <td>{@link CommonCRS#NAD27 NAD27}</td> <td>Geographic</td> <td>Like EPSG:4267 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
- * <tr><td>CRS:83</td> <td>{@link CommonCRS#NAD83 NAD83}</td> <td>Geographic</td> <td>Like EPSG:4269 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
- * <tr><td>CRS:84</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Geographic</td> <td>Like EPSG:4326 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
- * <tr><td>EPSG:4230</td> <td>{@link CommonCRS#ED50 ED50}</td> <td>Geographic</td> <td>European Datum 1950</td></tr>
- * <tr><td>EPSG:4258</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geographic</td> <td>European Terrestrial Reference Frame 1989</td></tr>
- * <tr><td>EPSG:4267</td> <td>{@link CommonCRS#NAD27 NAD27}</td> <td>Geographic</td> <td>North American Datum 1927</td></tr>
- * <tr><td>EPSG:4269</td> <td>{@link CommonCRS#NAD83 NAD83}</td> <td>Geographic</td> <td>North American Datum 1983</td></tr>
- * <tr><td>EPSG:4322</td> <td>{@link CommonCRS#WGS72 WGS72}</td> <td>Geographic</td> <td>World Geodetic System 1972</td></tr>
- * <tr><td>EPSG:4326</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Geographic</td> <td>World Geodetic System 1984</td></tr>
- * <tr><td>EPSG:4936</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geocentric</td> <td>European Terrestrial Reference Frame 1989</td></tr>
- * <tr><td>EPSG:4937</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geographic 3D</td> <td>European Terrestrial Reference Frame 1989</td></tr>
- * <tr><td>EPSG:4978</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Geocentric</td> <td>World Geodetic System 1984</td></tr>
- * <tr><td>EPSG:4979</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Geographic 3D</td> <td>World Geodetic System 1984</td></tr>
- * <tr><td>EPSG:4984</td> <td>{@link CommonCRS#WGS72 WGS72}</td> <td>Geocentric</td> <td>World Geodetic System 1972</td></tr>
- * <tr><td>EPSG:4985</td> <td>{@link CommonCRS#WGS72 WGS72}</td> <td>Geographic 3D</td> <td>World Geodetic System 1972</td></tr>
- * <tr><td>EPSG:5041</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Projected</td> <td>WGS 84 / UPS North (E,N)</td></tr>
- * <tr><td>EPSG:5042</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Projected</td> <td>WGS 84 / UPS South (E,N)</td></tr>
- * <tr><td>EPSG:322##</td><td>{@link CommonCRS#WGS72 WGS72}</td> <td>Projected</td> <td>WGS 72 / UTM zone ##N</td></tr>
- * <tr><td>EPSG:323##</td><td>{@link CommonCRS#WGS72 WGS72}</td> <td>Projected</td> <td>WGS 72 / UTM zone ##S</td></tr>
- * <tr><td>EPSG:326##</td><td>{@link CommonCRS#WGS84 WGS84}</td> <td>Projected</td> <td>WGS 84 / UTM zone ##N</td></tr>
- * <tr><td>EPSG:327##</td><td>{@link CommonCRS#WGS84 WGS84}</td> <td>Projected</td> <td>WGS 84 / UTM zone ##S</td></tr>
- * <tr><td>EPSG:5715</td> <td>{@link CommonCRS.Vertical#DEPTH DEPTH}</td> <td>Vertical</td> <td>Mean Sea Level depth</td></tr>
- * <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>
+ * <tr><th>Code</th> <th>Enum</th> <th>CRS Type</th> <th>Description</th></tr>
+ * <tr><td>CRS:27</td> <td>{@link CommonCRS#NAD27 NAD27}</td> <td>Geographic</td> <td>Like EPSG:4267 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
+ * <tr><td>CRS:83</td> <td>{@link CommonCRS#NAD83 NAD83}</td> <td>Geographic</td> <td>Like EPSG:4269 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
+ * <tr><td>CRS:84</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Geographic</td> <td>Like EPSG:4326 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
+ * <tr><td>EPSG:4230</td> <td>{@link CommonCRS#ED50 ED50}</td> <td>Geographic</td> <td>European Datum 1950</td></tr>
+ * <tr><td>EPSG:4258</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geographic</td> <td>European Terrestrial Reference Frame 1989</td></tr>
+ * <tr><td>EPSG:4267</td> <td>{@link CommonCRS#NAD27 NAD27}</td> <td>Geographic</td> <td>North American Datum 1927</td></tr>
+ * <tr><td>EPSG:4269</td> <td>{@link CommonCRS#NAD83 NAD83}</td> <td>Geographic</td> <td>North American Datum 1983</td></tr>
+ * <tr><td>EPSG:4322</td> <td>{@link CommonCRS#WGS72 WGS72}</td> <td>Geographic</td> <td>World Geodetic System 1972</td></tr>
+ * <tr><td>EPSG:4326</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Geographic</td> <td>World Geodetic System 1984</td></tr>
+ * <tr><td>EPSG:4936</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geocentric</td> <td>European Terrestrial Reference Frame 1989</td></tr>
+ * <tr><td>EPSG:4937</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geographic 3D</td> <td>European Terrestrial Reference Frame 1989</td></tr>
+ * <tr><td>EPSG:4978</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Geocentric</td> <td>World Geodetic System 1984</td></tr>
+ * <tr><td>EPSG:4979</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Geographic 3D</td> <td>World Geodetic System 1984</td></tr>
+ * <tr><td>EPSG:4984</td> <td>{@link CommonCRS#WGS72 WGS72}</td> <td>Geocentric</td> <td>World Geodetic System 1972</td></tr>
+ * <tr><td>EPSG:4985</td> <td>{@link CommonCRS#WGS72 WGS72}</td> <td>Geographic 3D</td> <td>World Geodetic System 1972</td></tr>
+ * <tr><td>EPSG:5041</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Projected</td> <td>WGS 84 / UPS North (E,N)</td></tr>
+ * <tr><td>EPSG:5042</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Projected</td> <td>WGS 84 / UPS South (E,N)</td></tr>
+ * <tr><td>EPSG:322##</td> <td>{@link CommonCRS#WGS72 WGS72}</td> <td>Projected</td> <td>WGS 72 / UTM zone ##N</td></tr>
+ * <tr><td>EPSG:323##</td> <td>{@link CommonCRS#WGS72 WGS72}</td> <td>Projected</td> <td>WGS 72 / UTM zone ##S</td></tr>
+ * <tr><td>EPSG:326##</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Projected</td> <td>WGS 84 / UTM zone ##N</td></tr>
+ * <tr><td>EPSG:327##</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Projected</td> <td>WGS 84 / UTM zone ##S</td></tr>
+ * <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>
+ * <tr><td>EPSG:5715</td> <td>{@link CommonCRS.Vertical#DEPTH DEPTH}</td> <td>Vertical</td> <td>Mean Sea Level depth</td></tr>
+ * <tr><td>OGC:JulianDate</td><td>{@link CommonCRS.Temporal#JULIAN JULIAN}</td> <td>Temporal</td> <td>Julian date (days)</td></tr>
+ * <tr><td>OGC:UnixTime</td> <td>{@link CommonCRS.Temporal#UNIX UNIX}</td> <td>Unix</td> <td>Unix time (seconds)</td></tr>
* </table></blockquote>
*
* <h4>URI forms</h4>
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java
index f9ddfcb9e5..f71dccd8bd 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java
@@ -76,6 +76,7 @@ import org.apache.sis.internal.system.Modules;
import org.apache.sis.internal.system.Loggers;
import org.apache.sis.internal.util.Constants;
import org.apache.sis.internal.jdk9.JDK9;
+import org.apache.sis.util.OptionalCandidate;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.logging.Logging;
@@ -456,7 +457,7 @@ public enum CommonCRS {
* Invoked by when the cache needs to be cleared after a classpath change.
*/
@SuppressWarnings("NestedSynchronizedStatement") // Safe because cachedProjections never call any method of 'this'.
- synchronized void clear() {
+ final synchronized void clear() {
cached = null;
cachedGeo3D = null;
cachedNormalized = null;
@@ -1363,7 +1364,7 @@ public enum CommonCRS {
/**
* Invoked by when the cache needs to be cleared after a classpath change.
*/
- synchronized void clear() {
+ final synchronized void clear() {
cached = null;
}
@@ -1514,21 +1515,22 @@ public enum CommonCRS {
* TemporalCRS crs = CommonCRS.Temporal.JULIAN.crs();
* }
*
- * Below is an alphabetical list of object names available in this enumeration:
+ * Below is an alphabetical list of object names available in this enumeration.
+ * Note that the namespace of identifiers ("OGC" versus "SIS") may change in any future version.
*
* <blockquote><table class="sis">
* <caption>Temporal objects accessible by enumeration constants</caption>
- * <tr><th>Name or alias</th> <th>Object type</th> <th>Enumeration value</th></tr>
- * <tr><td>Dublin Julian</td> <td>CRS, Datum</td> <td>{@link #DUBLIN_JULIAN}</td></tr>
- * <tr><td>Java time</td> <td>CRS</td> <td>{@link #JAVA}</td></tr>
- * <tr><td>Julian</td> <td>CRS, Datum</td> <td>{@link #JULIAN}</td></tr>
- * <tr><td>Modified Julian</td> <td>CRS, Datum</td> <td>{@link #MODIFIED_JULIAN}</td></tr>
- * <tr><td>Truncated Julian</td> <td>CRS, Datum</td> <td>{@link #TRUNCATED_JULIAN}</td></tr>
- * <tr><td>Unix/POSIX time</td> <td>CRS, Datum</td> <td>{@link #UNIX}</td></tr>
+ * <tr><th>Name or alias</th> <th>Identifier</th> <th>Object type</th> <th>Enumeration value</th></tr>
+ * <tr><td>Dublin Julian</td> <td>{@code SIS:DublinJulian}</td> <td>CRS, Datum</td> <td>{@link #DUBLIN_JULIAN}</td></tr>
+ * <tr><td>Java time</td> <td>{@code SIS:JavaTime}</td> <td>CRS</td> <td>{@link #JAVA}</td></tr>
+ * <tr><td>Julian</td> <td>{@code OGC:JulianDate}</td> <td>CRS, Datum</td> <td>{@link #JULIAN}</td></tr>
+ * <tr><td>Modified Julian</td> <td>{@code SIS:ModifiedJulianDate}</td> <td>CRS, Datum</td> <td>{@link #MODIFIED_JULIAN}</td></tr>
+ * <tr><td>Truncated Julian</td> <td>{@code OGC:TruncatedJulianDate}</td> <td>CRS, Datum</td> <td>{@link #TRUNCATED_JULIAN}</td></tr>
+ * <tr><td>Unix/POSIX time</td> <td>{@code OGC:UnixTime}</td> <td>CRS, Datum</td> <td>{@link #UNIX}</td></tr>
* </table></blockquote>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.3
*
* @see Engineering#TIME
*
@@ -1549,14 +1551,16 @@ public enum CommonCRS {
* the {@link java.text.SimpleDateFormat} class is closer to the common practice (but not ISO 8601
* compliant).</p>
*/
- JULIAN(Vocabulary.Keys.Julian, -2440588L * MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY/2),
+ JULIAN(Vocabulary.Keys.Julian, -2440588L * MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY/2,
+ "JulianDate", true),
/**
* Time measured as days since November 17, 1858 at 00:00 UTC.
* A <cite>Modified Julian day</cite> (MJD) is defined relative to
* <cite>Julian day</cite> (JD) as {@code MJD = JD − 2400000.5}.
*/
- MODIFIED_JULIAN(Vocabulary.Keys.ModifiedJulian, -40587L * MILLISECONDS_PER_DAY),
+ MODIFIED_JULIAN(Vocabulary.Keys.ModifiedJulian, -40587L * MILLISECONDS_PER_DAY,
+ "ModifiedJulianDate", false),
/**
* Time measured as days since May 24, 1968 at 00:00 UTC.
@@ -1564,24 +1568,26 @@ public enum CommonCRS {
* A <cite>Truncated Julian day</cite> (TJD) is defined relative to
* <cite>Julian day</cite> (JD) as {@code TJD = JD − 2440000.5}.
*/
- TRUNCATED_JULIAN(Vocabulary.Keys.TruncatedJulian, -587L * MILLISECONDS_PER_DAY),
+ TRUNCATED_JULIAN(Vocabulary.Keys.TruncatedJulian, -587L * MILLISECONDS_PER_DAY,
+ "TruncatedJulianDate", true),
/**
* Time measured as days since December 31, 1899 at 12:00 UTC.
* A <cite>Dublin Julian day</cite> (DJD) is defined relative to
* <cite>Julian day</cite> (JD) as {@code DJD = JD − 2415020}.
*/
- DUBLIN_JULIAN(Vocabulary.Keys.DublinJulian, -25568L * MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY/2),
+ DUBLIN_JULIAN(Vocabulary.Keys.DublinJulian, -25568L * MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY/2,
+ "DublinJulian", false),
/**
* Time measured as seconds since January 1st, 1970 at 00:00 UTC.
*/
- UNIX(Vocabulary.Keys.Time_1, 0),
+ UNIX(Vocabulary.Keys.Time_1, 0, "UnixTime", true),
/**
* Time measured as milliseconds since January 1st, 1970 at 00:00 UTC.
*/
- JAVA(Vocabulary.Keys.Time_1, 0);
+ JAVA(Vocabulary.Keys.Time_1, 0, "JavaTime", false);
/**
* The resource keys for the name as one of the {@code Vocabulary.Keys} constants.
@@ -1593,6 +1599,18 @@ public enum CommonCRS {
*/
private final long epoch;
+ /**
+ * Identifier in OGC or SIS namespace.
+ *
+ * @see org.apache.sis.referencing.factory.CommonAuthorityFactory#TEMPORAL_NAMES
+ */
+ private final String identifier;
+
+ /**
+ * Whether the identifier is in OGC namespace.
+ */
+ private final boolean isOGC;
+
/**
* The cached object. This is initially {@code null}, then set to various kinds of objects depending
* on which method has been invoked. The kind of object stored in this field may change during the
@@ -1603,9 +1621,11 @@ public enum CommonCRS {
/**
* Creates a new enumeration value of the given name with time counted since the given epoch.
*/
- private Temporal(final short name, final long epoch) {
- this.key = name;
- this.epoch = epoch;
+ private Temporal(final short name, final long epoch, final String identifier, final boolean isOGC) {
+ this.key = name;
+ this.epoch = epoch;
+ this.identifier = identifier;
+ this.isOGC = isOGC;
}
/**
@@ -1625,10 +1645,39 @@ public enum CommonCRS {
/**
* Invoked by when the cache needs to be cleared after a classpath change.
*/
- synchronized void clear() {
+ final synchronized void clear() {
cached = null;
}
+ /**
+ * Returns the enumeration value for the given identifier (without namespace).
+ * Identifiers in OGC namespace are {@code "JulianDate"}, {@code "TruncatedJulianDate"} and {@code "UnixTime"}.
+ * Identifiers in SIS namespace are {@code "ModifiedJulianDate"}, {@code "DublinJulian"} and {@code "JavaTime"}.
+ * Note that the content of OGC and SIS namespaces may change in any future version.
+ *
+ * @param identifier case-insensitive identifier of the desired temporal CRS, without namespace.
+ * @param onlyOGC whether to return the CRS only if its identifier is in OGC namespace.
+ * @return the enumeration value for the given identifier.
+ * @throws IllegalArgumentException if the given identifier is not recognized.
+ *
+ * @see <a href="http://www.opengis.net/def/crs/OGC/0">OGC Definitions Server</a>
+ *
+ * @since 1.3
+ */
+ public static Temporal forIdentifier(final String identifier, final boolean onlyOGC) {
+ ArgumentChecks.ensureNonEmpty("identifier", identifier);
+ for (final Temporal candidate : values()) {
+ if (candidate.identifier.equalsIgnoreCase(identifier)) {
+ if (onlyOGC & !candidate.isOGC) {
+ throw new IllegalArgumentException(Errors.format(
+ Errors.Keys.IdentifierNotInNamespace_2, Constants.OGC, candidate.identifier));
+ }
+ return candidate;
+ }
+ }
+ throw new IllegalArgumentException(Errors.format(Errors.Keys.UnknownEnumValue_2, Temporal.class, identifier));
+ }
+
/**
* Returns the enumeration value for the given epoch, or {@code null} if none.
* If the epoch is January 1st, 1970, then this method returns {@link #UNIX}.
@@ -1638,6 +1687,7 @@ public enum CommonCRS {
*
* @since 1.0
*/
+ @OptionalCandidate
public static Temporal forEpoch(final Instant epoch) {
if (epoch != null) {
final long e = epoch.toEpochMilli();
@@ -1677,12 +1727,15 @@ public enum CommonCRS {
object = crs(cached);
if (object == null) {
final TemporalDatum datum = datum();
- final Map<String,?> properties;
+ final Map<String,?> source;
if (this == JAVA) {
- properties = properties(Vocabulary.formatInternational(key, "Java"));
+ source = properties(Vocabulary.formatInternational(key, "Java"));
} else {
- properties = IdentifiedObjects.getProperties(datum, exclude());
+ source = IdentifiedObjects.getProperties(datum, exclude());
}
+ final Map<String,Object> properties = new HashMap<>(source);
+ properties.put(TemporalCRS.IDENTIFIERS_KEY,
+ new NamedIdentifier(isOGC ? Citations.OGC : Citations.SIS, identifier));
object = new DefaultTemporalCRS(properties, datum, cs());
cached = object;
}
@@ -1958,14 +2011,14 @@ public enum CommonCRS {
* @param key a constant from {@link org.apache.sis.util.resources.Vocabulary.Keys}.
* @return the properties to give to the object constructor.
*/
- static Map<String,?> properties(final short key) {
+ private static Map<String,?> properties(final short key) {
return properties(Vocabulary.formatInternational(key));
}
/**
* Puts the given name in a map of properties to be given to object constructors.
*/
- static Map<String,?> properties(final InternationalString name) {
+ private static Map<String,?> properties(final InternationalString name) {
return singletonMap(NAME_KEY, new NamedIdentifier(null, name));
}
@@ -1989,7 +2042,7 @@ public enum CommonCRS {
* Returns the EPSG factory to use for creating CRS, or {@code null} if none.
* If this method returns {@code null}, then the caller will silently fallback on hard-coded values.
*/
- static GeodeticAuthorityFactory factory() {
+ private static GeodeticAuthorityFactory factory() {
if (!EPSGFactoryFallback.FORCE_HARDCODED) {
final GeodeticAuthorityFactory factory = AuthorityFactories.EPSG();
if (!(factory instanceof EPSGFactoryFallback)) {
@@ -2003,7 +2056,7 @@ public enum CommonCRS {
* Invoked when a factory failed to create an object.
* After invoking this method, the caller will fallback on hard-coded values.
*/
- static void failure(final Object caller, final String method, final FactoryException e, final int code) {
+ private static void failure(final Object caller, final String method, final FactoryException e, final int code) {
String message = Resources.format(Resources.Keys.CanNotInstantiateGeodeticObject_1, (Constants.EPSG + ':') + code);
message = Exceptions.formatChainedMessages(null, message, e);
final LogRecord record = new LogRecord(Level.WARNING, message);
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityCode.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityCode.java
new file mode 100644
index 0000000000..8aa66bd213
--- /dev/null
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityCode.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.referencing.factory;
+
+import org.opengis.referencing.NoSuchAuthorityCodeException;
+import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.internal.util.Constants;
+import org.apache.sis.internal.referencing.Resources;
+
+
+/**
+ * Result of parsing a code in "OGC", "CRS", "AUTO" or "AUTO2" namespace.
+ *
+ * @author Martin Desruisseaux (IRD, Geomatys)
+ * @version 1.3
+ * @since 0.7
+ * @module
+ */
+final class CommonAuthorityCode {
+ /**
+ * The parameter separator for codes in the {@code "AUTO(2)"} namespace.
+ */
+ static final char SEPARATOR = ',';
+
+ /**
+ * Local part of the code, without the authority part and without the parameters.
+ */
+ final String localCode;
+
+ /**
+ * The remaining part of the code after {@link #localCode}, or {@code null} if none.
+ * This part may exist with codes in the {@code AUTO} or {@code AUTO2} namespace.
+ */
+ private String complement;
+
+ /**
+ * The result of parsing {@link #complement} as numerical parameters.
+ * Computed by {@link #parameters()} when first requested.
+ */
+ private double[] parameters;
+
+ /**
+ * If the authority is {@code "AUTO"}, version of that authority (1 or 2). Otherwise 0.
+ */
+ private int versionOfAuto;
+
+ /**
+ * Whether the first character of {@link #localCode} is a decimal digit, the minus or the plus character.
+ */
+ final boolean isNumeric;
+
+ /**
+ * {@code true} if the "OGC" namespace was explicitly specified.
+ */
+ boolean isOGC;
+
+ /**
+ * Finds the index where the code begins, ignoring spaces and the {@code "OGC"}, {@code "CRS"}, {@code "AUTO"},
+ * {@code "AUTO1"} or {@code "AUTO2"} namespaces if present. If a namespace is found and is a legacy one, then
+ * the {@link #isLegacy} flag will be set.
+ *
+ * @param code authority, code and parameters to parse.
+ * @throws NoSuchAuthorityCodeException if an authority is present but is not one of the recognized authorities.
+ */
+ CommonAuthorityCode(final String code) throws NoSuchAuthorityCodeException {
+ int s = code.indexOf(Constants.DEFAULT_SEPARATOR);
+ if (s >= 0) {
+ final int end = CharSequences.skipTrailingWhitespaces(code, 0, s);
+ final int start = CharSequences.skipLeadingWhitespaces (code, 0, end);
+ isOGC = GeodeticAuthorityFactory.regionMatches(Constants.OGC, code, start, end);
+ if (!isOGC && !GeodeticAuthorityFactory.regionMatches(Constants.CRS, code, start, end)) {
+ if (code.regionMatches(true, start, "AUTO", 0, 4)) { // 4 is the length of "AUTO".
+ switch (end - start) {
+ case 4: versionOfAuto = 1; break; // "AUTO".
+ case 5: versionOfAuto = (code.charAt(end-1) - '0'); break; // "AUTO1" or "AUTO2".
+ }
+ }
+ if (!isAuto(false)) {
+ throw new NoSuchAuthorityCodeException(Resources.format(Resources.Keys.UnknownAuthority_1,
+ CharSequences.trimWhitespaces(code, 0, s)), Constants.OGC, code);
+ }
+ }
+ }
+ final int length = code.length();
+ s = CharSequences.skipLeadingWhitespaces(code, s+1, length);
+ /*
+ * Above code removed the "CRS" part when it is used as a namespace, as in "CRS:84".
+ * The code below removes the "CRS" prefix when it is concatenated within the code,
+ * as in "CRS84". Together, those two checks handle redundant codes like "CRS:CRS84"
+ * (malformed code, but seen in practice).
+ */
+ if (code.regionMatches(true, s, Constants.CRS, 0, Constants.CRS.length())) {
+ s = CharSequences.skipLeadingWhitespaces(code, s + Constants.CRS.length(), length);
+ }
+ if (s >= length) {
+ throw new NoSuchAuthorityCodeException(Errors.format(Errors.Keys.EmptyArgument_1, "code"), Constants.OGC, code);
+ }
+ /*
+ * Check whether the code has parameters. It should happen only in code in "AUTO" or "AUTO2" namespace,
+ * but we nevertheless check for all authorities.
+ */
+ int end = CharSequences.skipTrailingWhitespaces(code, s, length);
+ final int startOfParameters = code.indexOf(SEPARATOR, s);
+ if (startOfParameters >= 0) {
+ complement = code.substring(startOfParameters + 1, end);
+ end = CharSequences.skipTrailingWhitespaces(code, s, startOfParameters);
+ }
+ localCode = code.substring(s, end);
+ final char c = localCode.charAt(0);
+ isNumeric = (c >= '0' && c <= '9') || (c == '-' || c == '+');
+ }
+
+ /**
+ * Returns whether the authority is "AUTO", "AUTO1" or "AUTO2".
+ *
+ * @param legacy whether to returns {@code true} only if the authority is "AUTO" or "AUTO1".
+ * @return whether the authority is some "AUTO" namespace.
+ */
+ final boolean isAuto(final boolean legacy) {
+ return legacy ? (versionOfAuto == 1) : (versionOfAuto >= 1 && versionOfAuto <= 2);
+ }
+
+ /**
+ * Returns the result of parsing the comma-separated list of optional parameters after the code.
+ * If there is no parameter, then this method returns an empty array.
+ * Caller should not modify the returned array.
+ *
+ * @return the parameters after the code, or an empty array if none.
+ */
+ @SuppressWarnings("ReturnOfCollectionOrArrayField")
+ final double[] parameters() {
+ if (parameters == null) {
+ parameters = CharSequences.parseDoubles(complement, SEPARATOR); // `parseDoubles(…)` is null-safe.
+ }
+ return parameters;
+ }
+
+ /**
+ * Returns the error message for unexpected parameters after the code.
+ */
+ final String unexpectedParameters() {
+ return Errors.format(Errors.Keys.UnexpectedCharactersAfter_2, localCode, complement);
+ }
+}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityFactory.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityFactory.java
index fe06784f67..a80f26a879 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityFactory.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/CommonAuthorityFactory.java
@@ -26,6 +26,8 @@ import javax.measure.Unit;
import javax.measure.quantity.Length;
import org.opengis.util.FactoryException;
import org.opengis.util.InternationalString;
+import org.opengis.util.GenericName;
+import org.opengis.metadata.Identifier;
import org.opengis.metadata.citation.Citation;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.NoSuchAuthorityCodeException;
@@ -35,6 +37,7 @@ import org.opengis.referencing.crs.EngineeringCRS;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.crs.ProjectedCRS;
import org.opengis.referencing.crs.VerticalCRS;
+import org.opengis.referencing.crs.TemporalCRS;
import org.opengis.referencing.crs.SingleCRS;
import org.opengis.referencing.cs.CartesianCS;
import org.apache.sis.internal.referencing.provider.TransverseMercator.Zoner;
@@ -47,8 +50,6 @@ import org.apache.sis.measure.Units;
import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.referencing.cs.CoordinateSystems;
import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.util.ArraysExt;
-import org.apache.sis.util.CharSequences;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.SimpleInternationalString;
@@ -151,6 +152,30 @@ import static java.util.logging.Logger.getLogger;
* <td>{@linkplain org.apache.sis.referencing.cs.DefaultCartesianCS Cartesian}</td>
* <td>(east, north)</td>
* <td>user-specified</td>
+ * </tr><tr>
+ * <td>OGC</td>
+ * <td>JulianDate</td>
+ * <td>Julian</td>
+ * <td>{@linkplain org.apache.sis.referencing.crs.DefaultTemporalCRS Temporal}</td>
+ * <td>{@linkplain org.apache.sis.referencing.cs.DefaultTimeCS Time}</td>
+ * <td>(future)</td>
+ * <td>days</td>
+ * </tr><tr>
+ * <td>OGC</td>
+ * <td>TruncatedJulianDate</td>
+ * <td>Truncated Julian</td>
+ * <td>{@linkplain org.apache.sis.referencing.crs.DefaultTemporalCRS Temporal}</td>
+ * <td>{@linkplain org.apache.sis.referencing.cs.DefaultTimeCS Time}</td>
+ * <td>(future)</td>
+ * <td>days</td>
+ * </tr><tr>
+ * <td>OGC</td>
+ * <td>UnixTime</td>
+ * <td>Unix Time</td>
+ * <td>{@linkplain org.apache.sis.referencing.crs.DefaultTemporalCRS Temporal}</td>
+ * <td>{@linkplain org.apache.sis.referencing.cs.DefaultTimeCS Time}</td>
+ * <td>(future)</td>
+ * <td>seconds</td>
* </tr>
* </table>
*
@@ -184,7 +209,7 @@ import static java.util.logging.Logger.getLogger;
* switching to polar stereographic projections for high latitudes.</p>
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 1.1
+ * @version 1.3
*
* @see CommonCRS
*
@@ -195,7 +220,7 @@ public class CommonAuthorityFactory extends GeodeticAuthorityFactory implements
/**
* The {@value} prefix for a code identified by parameters.
* This is defined in annexes B.7 to B.11 of WMS 1.3 specification.
- * The {@code "AUTO(2)"} namespaces are not considered by SIS as real authorities.
+ * The {@code "AUTO(2)"} namespaces are not considered by Apache SIS as real authorities.
*/
private static final String AUTO2 = "AUTO2";
@@ -207,11 +232,6 @@ public class CommonAuthorityFactory extends GeodeticAuthorityFactory implements
private static final Set<String> CODESPACES = Collections.unmodifiableSet(
new LinkedHashSet<>(Arrays.asList(Constants.OGC, Constants.CRS, "AUTO", AUTO2)));
- /**
- * The bit for saying that a namespace is the legacy {@code "AUTO"} namespace.
- */
- private static final int LEGACY_MASK = 0x80000000;
-
/**
* First code in the AUTO(2) namespace.
*/
@@ -232,13 +252,22 @@ public class CommonAuthorityFactory extends GeodeticAuthorityFactory implements
};
/**
- * The parameter separator for codes in the {@code "AUTO(2)"} namespace.
+ * Names of temporal CRS in OGC namespace.
+ * Those CRS are defined by {@link CommonCRS.Temporal}.
+ * Codes in Apache SIS namespace are excluded from this list.
+ *
+ * @see CommonCRS.Temporal#identifier
*/
- static final char SEPARATOR = ',';
+ private static final String[] TEMPORAL_NAMES = {
+ "JulianDate",
+ "TruncatedJulianDate",
+ "UnixTime"
+ };
/**
* The codes known to this factory, associated with their CRS type. This is set to an empty map
* at {@code CommonAuthorityFactory} construction time and filled only when first needed.
+ * Keys are of the form "AUTHORITY:IDENTIFIER".
*/
private final Map<String,Class<?>> codes;
@@ -274,79 +303,41 @@ public class CommonAuthorityFactory extends GeodeticAuthorityFactory implements
}
/**
- * Rewrites the given code in a canonical format.
- * If the code can not be reformatted, then this method returns {@code null}.
+ * Rewrites the given code in a canonical format and without parameters.
+ * If the code is not in a known namespace, then this method returns {@code null}.
*/
- static String reformat(final String code) {
+ static String reformat(String code) {
try {
- return format(Integer.parseInt(code.substring(skipNamespace(code) & ~LEGACY_MASK)));
+ final CommonAuthorityCode parsed = new CommonAuthorityCode(code);
+ code = parsed.localCode;
+ code = parsed.isNumeric ? format(Integer.parseInt(code)) : format(code);
} catch (NoSuchAuthorityCodeException | NumberFormatException e) {
- Logging.recoverableException(getLogger(Loggers.CRS_FACTORY), CommonAuthorityFactory.class, "reformat", e);
+ Logging.ignorableException(getLogger(Loggers.CRS_FACTORY), CommonAuthorityFactory.class, "reformat", e);
return null;
}
+ return code;
}
/**
- * Returns the index where the code begins, ignoring spaces and the {@code "OGC"}, {@code "CRS"}, {@code "AUTO"},
- * {@code "AUTO1"} or {@code "AUTO2"} namespaces if present. If a namespace is found and is a legacy one, then
- * this {@link #LEGACY_MASK} bit will be set.
- *
- * @return index where the code begin, possibly with the {@link #LEGACY_MASK} bit set.
- * @throws NoSuchAuthorityCodeException if an authority is present but is not one of the recognized authorities.
+ * Formats the given code with a {@code "CRS:"} or {@code "AUTO2:"} prefix.
+ * This is used for numerical codes such as "CRS:84".
*/
- private static int skipNamespace(final String code) throws NoSuchAuthorityCodeException {
- int isLegacy = 0;
- int s = code.indexOf(Constants.DEFAULT_SEPARATOR);
- if (s >= 0) {
- final int end = CharSequences.skipTrailingWhitespaces(code, 0, s);
- final int start = CharSequences.skipLeadingWhitespaces (code, 0, end);
- if (!regionMatches(Constants.CRS, code, start, end) &&
- !regionMatches(Constants.OGC, code, start, end))
- {
- boolean isRecognized = false;
- final int length = AUTO2.length() - 1;
- if (code.regionMatches(true, start, AUTO2, 0, length)) {
- switch (end - start - length) { // Number of extra characters after "AUTO".
- case 0: { // Namespace is exactly "AUTO" (ignoring case).
- isRecognized = true;
- isLegacy = LEGACY_MASK;
- break;
- }
- case 1: { // Namespace has one more character than "AUTO".
- final char c = code.charAt(end - 1);
- isRecognized = (c >= '1' && c <= '2');
- if (c == '1') {
- isLegacy = LEGACY_MASK;
- }
- }
- }
- }
- if (!isRecognized) {
- throw new NoSuchAuthorityCodeException(Resources.format(Resources.Keys.UnknownAuthority_1,
- CharSequences.trimWhitespaces(code, 0, s)), Constants.OGC, code);
- }
- }
- }
- s = CharSequences.skipLeadingWhitespaces(code, s+1, code.length());
- /*
- * Above code removed the "CRS" part when it is used as a namespace, as in "CRS:84".
- * The code below removes the "CRS" prefix when it is concatenated within the code,
- * as in "CRS84". Together, those two checks handle redundant codes like "CRS:CRS84"
- * (malformed code, but seen in practice).
- */
- if (code.regionMatches(true, s, Constants.CRS, 0, Constants.CRS.length())) {
- s = CharSequences.skipLeadingWhitespaces(code, s + Constants.CRS.length(), code.length());
- }
- if (s >= code.length()) {
- throw new NoSuchAuthorityCodeException(Errors.format(Errors.Keys.EmptyArgument_1, "code"), Constants.OGC, code);
- }
- return s | isLegacy;
+ private static String format(final int code) {
+ return ((code >= FIRST_PROJECTION_CODE) ? AUTO2 : Constants.CRS) + Constants.DEFAULT_SEPARATOR + code;
+ }
+
+ /**
+ * Formats the given code with an {@code "OGC:"} prefix.
+ * This is used for non-numerical codes such as "OGC:JulianDate".
+ */
+ private static String format(final String code) {
+ return Constants.OGC + Constants.DEFAULT_SEPARATOR + code;
}
/**
* Provides a complete set of the known codes provided by this factory.
- * The returned set contains a namespace followed by numeric identifiers
- * like {@code "CRS:84"}, {@code "CRS:27"}, {@code "AUTO2:42001"}, <i>etc</i>.
+ * The returned set contains a namespace followed by identifiers like
+ * {@code "CRS:84"}, {@code "CRS:27"}, {@code "AUTO2:42001"}, <i>etc</i>.
*
* @param type the spatial reference objects type.
* @return the set of authority codes for spatial reference objects of the given type.
@@ -368,18 +359,14 @@ public class CommonAuthorityFactory extends GeodeticAuthorityFactory implements
for (int code = FIRST_PROJECTION_CODE; code < FIRST_PROJECTION_CODE + PROJECTION_NAMES.length; code++) {
add(code, ProjectedCRS.class);
}
+ for (final String name : TEMPORAL_NAMES) {
+ codes.put(format(name), TemporalCRS.class);
+ }
}
}
return new FilteredCodes(codes, type).keySet();
}
- /**
- * Formats the given code with a {@code "CRS:"} or {@code "AUTO2:"} prefix.
- */
- private static String format(final int code) {
- return ((code >= FIRST_PROJECTION_CODE) ? AUTO2 : Constants.CRS) + Constants.DEFAULT_SEPARATOR + code;
- }
-
/**
* Adds an element in the {@link #codes} map, witch check against duplicated values.
*/
@@ -434,27 +421,37 @@ public class CommonAuthorityFactory extends GeodeticAuthorityFactory implements
*/
@Override
public InternationalString getDescriptionText(final String code) throws FactoryException {
- final int s = skipNamespace(code) & ~LEGACY_MASK;
- final String localCode = code.substring(s, CharSequences.skipTrailingWhitespaces(code, s, code.length()));
- if (localCode.indexOf(SEPARATOR) < 0) {
+ final CommonAuthorityCode parsed = new CommonAuthorityCode(code);
+ if (parsed.isNumeric && parsed.parameters().length == 0) {
/*
* For codes in the "AUTO(2)" namespace without parameters, we can not rely on the default implementation
- * since it would fail to create the ProjectedCRS instance. Instead we return a generic description.
+ * because it would fail to create the ProjectedCRS instance. Instead we return a generic description.
* Note that we do not execute this block if parametes were specified. If there is parameters,
* then we instead rely on the default implementation for a more accurate description text.
+ * Note also that we do not restrict to "AUTOx" namespaces because erroneous namespaces exist
+ * in practice and the numerical codes are non-ambiguous (at least in current version).
*/
final int codeValue;
try {
- codeValue = Integer.parseInt(localCode);
+ codeValue = Integer.parseInt(parsed.localCode);
} catch (NumberFormatException exception) {
- throw noSuchAuthorityCode(localCode, code, exception);
+ throw noSuchAuthorityCode(parsed.localCode, code, exception);
}
final int i = codeValue - FIRST_PROJECTION_CODE;
if (i >= 0 && i < PROJECTION_NAMES.length) {
return new SimpleInternationalString(PROJECTION_NAMES[i]);
}
}
- return new SimpleInternationalString(createCoordinateReferenceSystem(localCode).getName().getCode());
+ /*
+ * Fallback on fetching the full CRS, then request its name.
+ * It will include the parsing of parameters if any.
+ */
+ final Identifier name = createCoordinateReferenceSystem(code, parsed).getName();
+ if (name instanceof GenericName) {
+ return ((GenericName) name).tip().toInternationalString();
+ } else {
+ return new SimpleInternationalString(name.getCode());
+ }
}
/**
@@ -488,28 +485,38 @@ public class CommonAuthorityFactory extends GeodeticAuthorityFactory implements
@Override
public CoordinateReferenceSystem createCoordinateReferenceSystem(final String code) throws FactoryException {
ArgumentChecks.ensureNonNull("code", code);
- final String localCode;
- final boolean isLegacy;
- String complement = null;
- { // Block for keeping 'start' and 'end' variables locale.
- int start = skipNamespace(code);
- isLegacy = (start & LEGACY_MASK) != 0;
- start &= ~LEGACY_MASK;
- final int startOfParameters = code.indexOf(SEPARATOR, start);
- int end = CharSequences.skipTrailingWhitespaces(code, start, code.length());
- if (startOfParameters >= 0) {
- complement = code.substring(startOfParameters + 1);
- end = CharSequences.skipTrailingWhitespaces(code, start, startOfParameters);
- }
- localCode = code.substring(start, end);
+ return createCoordinateReferenceSystem(code, new CommonAuthorityCode(code));
+ }
+
+ /**
+ * Implementation of {@link #createCoordinateReferenceSystem(String)} after the user-supplied code has been parsed.
+ *
+ * @param code the user-supplied code of desired CRS.
+ * @param parsed result of parsing the supplied {@code code}.
+ * @throws FactoryException if the object creation failed.
+ */
+ private CoordinateReferenceSystem createCoordinateReferenceSystem(final String code, final CommonAuthorityCode parsed)
+ throws FactoryException
+ {
+ /*
+ * First, handled the case of non-numerical parameterless codes ("OGC:JulianDate", etc.)
+ * We accept also SIS-specific codes (e.g. "OGC:ModifiedJulianDate", "OGC:JavaTime") if
+ * the namespace is the more neutral "CRS" instead of "OGC". The SIS-specific codes are
+ * never listed in `getAuthorityCodes(…)`.
+ */
+ final String localCode = parsed.localCode;
+ final double[] parameters = parsed.parameters();
+ if (!parsed.isNumeric && !parsed.isAuto(false) && parameters.length == 0) try {
+ return CommonCRS.Temporal.forIdentifier(localCode, parsed.isOGC).crs();
+ } catch (IllegalArgumentException e) {
+ throw noSuchAuthorityCode(localCode, code, e);
}
- int codeValue = 0;
- double[] parameters = ArraysExt.EMPTY_DOUBLE;
+ /*
+ * In current version, all non-temporal CRS have a numerical code.
+ */
+ final int codeValue;
try {
codeValue = Integer.parseInt(localCode);
- if (complement != null) {
- parameters = CharSequences.parseDoubles(complement, SEPARATOR);
- }
} catch (NumberFormatException exception) {
throw noSuchAuthorityCode(localCode, code, exception);
}
@@ -528,6 +535,7 @@ public class CommonAuthorityFactory extends GeodeticAuthorityFactory implements
errorKey = Errors.Keys.TooManyArguments_2;
}
if (errorKey == 0) {
+ final boolean isLegacy = parsed.isAuto(true);
return createAuto(code, codeValue, isLegacy,
(count > 2) ? parameters[0] : isLegacy ? Constants.EPSG_METRE : 1,
parameters[count - 2],
@@ -536,8 +544,7 @@ public class CommonAuthorityFactory extends GeodeticAuthorityFactory implements
throw new NoSuchAuthorityCodeException(Errors.format(errorKey, expected, count), AUTO2, localCode, code);
}
if (count != 0) {
- throw new NoSuchAuthorityCodeException(Errors.format(Errors.Keys.UnexpectedCharactersAfter_2,
- localCode, complement), Constants.CRS, localCode, code);
+ throw new NoSuchAuthorityCodeException(parsed.unexpectedParameters(), Constants.CRS, localCode, code);
}
final CommonCRS crs;
switch (codeValue) {
@@ -562,7 +569,6 @@ public class CommonAuthorityFactory extends GeodeticAuthorityFactory implements
* @param latitude a latitude in the desired projection zone.
* @return the projected CRS for the given projection and parameters.
*/
- @SuppressWarnings("null")
private ProjectedCRS createAuto(final String code, final int projection, final boolean isLegacy,
final double factor, final double longitude, final double latitude) throws FactoryException
{
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
index fd11ee22c4..417804cb6d 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
@@ -1246,9 +1246,16 @@ public abstract class GeodeticAuthorityFactory extends AbstractFactory implement
/**
* Returns {@code true} if the given portion of the code is equal, ignoring case, to the given namespace.
+ *
+ * @param namespace expected namespace (e.g. "OGC" or "EPSG").
+ * @param code the code where to check namespace.
+ * @param start index of first character of the namespace in the code.
+ * @param end index after last character of the namespace in the code.
+ * @return whether the specified region of the code is equal to the namespace.
*/
static boolean regionMatches(final String namespace, final String code, final int start, final int end) {
- return (namespace.length() == end - start) && code.regionMatches(true, start, namespace, 0, namespace.length());
+ final int length = namespace.length();
+ return (length == end - start) && code.regionMatches(true, start, namespace, 0, length);
}
/**
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
index 77f579a4d2..a7376779f1 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/MultiAuthoritiesFactory.java
@@ -847,12 +847,12 @@ public class MultiAuthoritiesFactory extends GeodeticAuthorityFactory implements
* depends on whether the authority is "AUTO" or "AUTO2". This works for now, but we may need a more
* rigorous approach in a future SIS version.
*/
- if (parameters != null || code.indexOf(CommonAuthorityFactory.SEPARATOR) >= 0) {
+ if (parameters != null || code.indexOf(CommonAuthorityCode.SEPARATOR) >= 0) {
final StringBuilder buffer = new StringBuilder(authority.length() + code.length() + 1)
.append(authority).append(Constants.DEFAULT_SEPARATOR).append(code);
if (parameters != null) {
for (final String p : parameters) {
- buffer.append(CommonAuthorityFactory.SEPARATOR).append(p);
+ buffer.append(CommonAuthorityCode.SEPARATOR).append(p);
}
}
code = buffer.toString();
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/package-info.java b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/package-info.java
index 0c2c551c70..618fb78fb1 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/package-info.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/package-info.java
@@ -56,7 +56,7 @@
* </table>
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 0.6
* @module
*/
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java
index b48ee806e1..80421469b2 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/CRSTest.java
@@ -19,6 +19,7 @@ package org.apache.sis.referencing;
import java.util.Map;
import java.util.HashMap;
import java.util.Arrays;
+import java.util.List;
import org.apache.sis.internal.system.Loggers;
import org.opengis.util.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
@@ -55,7 +56,7 @@ import static org.apache.sis.test.Assert.*;
*
* @author Martin Desruisseaux (Geomatys)
* @author Alexis Manin (Geomatys)
- * @version 1.1
+ * @version 1.3
* @since 0.4
* @module
*/
@@ -142,6 +143,19 @@ public final strictfp class CRSTest extends TestCase {
verifyForCode(CommonCRS.NAD83.normalizedGeographic(), "http://www.opengis.net/gml/srs/crs.xml#83");
}
+ /**
+ * Tests {@link CRS#forCode(String)} with temporal CRS codes.
+ *
+ * @throws FactoryException if a CRS can not be constructed.
+ */
+ @Test
+ public void testForTemporalCode() throws FactoryException {
+ verifyForCode(CommonCRS.Temporal.JULIAN.crs(), "OGC:JulianDate");
+ verifyForCode(CommonCRS.Temporal.UNIX.crs(), "OGC:UnixTime");
+ verifyForCode(CommonCRS.Temporal.TRUNCATED_JULIAN.crs(),
+ "http://www.opengis.net/gml/srs/crs.xml#TruncatedJulianDate");
+ }
+
/**
* Test {@link CRS#forCode(String)} with values that should be invalid.
*
@@ -157,6 +171,36 @@ public final strictfp class CRSTest extends TestCase {
}
}
+ /**
+ * Asserts that the result of {@link CRS#forCode(String)} for a compound CRS are the given components.
+ */
+ private static void verifyForCompoundCode(final String code, final SingleCRS... expected) throws FactoryException {
+ final List<SingleCRS> components = CRS.getSingleComponents(CRS.forCode(code));
+ final int count = Math.min(components.size(), expected.length);
+ for (int i=0; i<count; i++) {
+ assertTrue(String.valueOf(i), Utilities.deepEquals(expected[i], components.get(i), ComparisonMode.DEBUG));
+ }
+ assertEquals(expected.length, components.size());
+ }
+
+ /**
+ * Tests {@link CRS#forCode(String)} with compound CRS codes.
+ *
+ * @throws FactoryException if a CRS can not be constructed.
+ */
+ @Test
+ public void testForCompoundCode() throws FactoryException {
+ verifyForCompoundCode("urn:ogc:def:crs,crs:EPSG::4326,crs:EPSG::5714",
+ CommonCRS.WGS84.geographic(), CommonCRS.Vertical.MEAN_SEA_LEVEL.crs());
+ verifyForCompoundCode("urn:ogc:def:crs,crs:EPSG::4326,crs:EPSG::5714,crs:OGC::TruncatedJulianDate",
+ CommonCRS.WGS84.geographic(), CommonCRS.Vertical.MEAN_SEA_LEVEL.crs(), CommonCRS.Temporal.TRUNCATED_JULIAN.crs());
+
+ verifyForCompoundCode("http://www.opengis.net/def/crs-compound?" +
+ "1=http://www.opengis.net/def/crs/epsg/0/4326&" +
+ "2=http://www.opengis.net/def/crs/epsg/0/5715",
+ CommonCRS.WGS84.geographic(), CommonCRS.Vertical.DEPTH.crs());
+ }
+
/**
* Tests simple WKT parsing. It is not the purpose of this class to test extensively the WKT parser;
* those tests are rather done by {@link org.apache.sis.io.wkt.GeodeticObjectParserTest}.
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java
index 547d069c3f..c8d6c17695 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/CommonCRSTest.java
@@ -20,6 +20,7 @@ import java.util.Date;
import java.util.Map;
import java.util.HashMap;
import java.time.Instant;
+import org.opengis.util.FactoryException;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.crs.TemporalCRS;
@@ -33,6 +34,7 @@ import org.opengis.referencing.cs.EllipsoidalCS;
import org.opengis.referencing.datum.TemporalDatum;
import org.opengis.referencing.datum.VerticalDatum;
import org.opengis.referencing.datum.VerticalDatumType;
+import org.apache.sis.metadata.iso.citation.Citations;
import org.apache.sis.internal.metadata.AxisNames;
import org.apache.sis.internal.referencing.VerticalDatumTypes;
import org.apache.sis.internal.util.Constants;
@@ -52,7 +54,7 @@ import static org.apache.sis.test.TestUtilities.*;
* Tests the {@link CommonCRS} class.
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 1.1
+ * @version 1.3
* @since 0.4
* @module
*/
@@ -301,6 +303,39 @@ public final strictfp class CommonCRSTest extends TestCase {
assertTrue(name, name.contains(word));
}
+ /**
+ * Tests {@link CommonCRS.Temporal#forIdentifier(String, boolean)}.
+ */
+ @Test
+ public void testTemporalForIdentifier() {
+ assertSame(CommonCRS.Temporal.TRUNCATED_JULIAN, CommonCRS.Temporal.forIdentifier("TruncatedJulianDate", false));
+ assertSame(CommonCRS.Temporal.TRUNCATED_JULIAN, CommonCRS.Temporal.forIdentifier("TruncatedJulianDate", true));
+ assertSame(CommonCRS.Temporal.MODIFIED_JULIAN, CommonCRS.Temporal.forIdentifier("ModifiedJulianDate", false));
+ try {
+ CommonCRS.Temporal.forIdentifier("ModifiedJulianDate", true);
+ fail("Unexpected because not in OGC namespace.");
+ } catch (IllegalArgumentException e) {
+ final String message = e.getMessage();
+ assertTrue(message.contains("ModifiedJulianDate"));
+ assertTrue(message.contains("OGC"));
+ }
+ assertEquals("OGC:TruncatedJulianDate", getSingleton(CommonCRS.Temporal.TRUNCATED_JULIAN.crs().getIdentifiers()).toString());
+ assertEquals("SIS:ModifiedJulianDate", getSingleton(CommonCRS.Temporal. MODIFIED_JULIAN.crs().getIdentifiers()).toString());
+ }
+
+ /**
+ * Tests the URN lookup on temporal CRS.
+ *
+ * @throws FactoryException if a call to {@link IdentifiedObjects#lookupURN lookupURN(…)} failed.
+ */
+ @Test
+ public void testLookupURN() throws FactoryException {
+ final TemporalCRS crs = CommonCRS.Temporal.TRUNCATED_JULIAN.crs();
+ assertNull(IdentifiedObjects.lookupEPSG(crs)); // Not an EPSG code.
+ assertNull(IdentifiedObjects.lookupURN(crs, Citations.SIS)); // Not in SIS namespace.
+ assertEquals("urn:ogc:def:crs:OGC::TruncatedJulianDate", IdentifiedObjects.lookupURN(crs, Citations.OGC));
+ }
+
/**
* Tests {@link CommonCRS#universal(double, double)} with Universal Transverse Mercator (UTM) projections.
*
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java
index 719fa84b25..310db89267 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/factory/CommonAuthorityFactoryTest.java
@@ -18,6 +18,7 @@ package org.apache.sis.referencing.factory;
import java.util.Arrays;
import java.util.Set;
+import java.util.HashSet;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import org.opengis.util.FactoryException;
@@ -29,11 +30,13 @@ import org.opengis.referencing.crs.EngineeringCRS;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.crs.ProjectedCRS;
import org.opengis.referencing.crs.VerticalCRS;
+import org.opengis.referencing.crs.TemporalCRS;
import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.datum.Datum;
import org.apache.sis.internal.util.Constants;
import org.apache.sis.internal.referencing.provider.TransverseMercator;
import org.apache.sis.referencing.operation.transform.LinearTransform;
+import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.metadata.iso.citation.Citations;
import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.io.wkt.Convention;
@@ -47,13 +50,14 @@ import org.junit.Ignore;
import org.junit.Test;
import static org.apache.sis.test.ReferencingAssert.*;
+import static org.apache.sis.test.TestUtilities.getSingleton;
/**
* Tests {@link CommonAuthorityFactory}.
*
* @author Martin Desruisseaux (IRD, Geomatys)
- * @version 0.7
+ * @version 1.3
* @since 0.7
* @module
*/
@@ -81,7 +85,8 @@ public final strictfp class CommonAuthorityFactoryTest extends TestCase {
assertTrue("getAuthorityCodes(Datum.class)",
factory.getAuthorityCodes(Datum.class).isEmpty());
assertSetEquals(Arrays.asList("CRS:1", "CRS:27", "CRS:83", "CRS:84", "CRS:88",
- "AUTO2:42001", "AUTO2:42002", "AUTO2:42003", "AUTO2:42004", "AUTO2:42005"),
+ "AUTO2:42001", "AUTO2:42002", "AUTO2:42003", "AUTO2:42004", "AUTO2:42005",
+ "OGC:JulianDate", "OGC:TruncatedJulianDate", "OGC:UnixTime"),
factory.getAuthorityCodes(CoordinateReferenceSystem.class));
assertSetEquals(Arrays.asList("AUTO2:42001", "AUTO2:42002", "AUTO2:42003", "AUTO2:42004", "AUTO2:42005"),
factory.getAuthorityCodes(ProjectedCRS.class));
@@ -89,6 +94,8 @@ public final strictfp class CommonAuthorityFactoryTest extends TestCase {
factory.getAuthorityCodes(GeographicCRS.class));
assertSetEquals(Arrays.asList("CRS:88"),
factory.getAuthorityCodes(VerticalCRS.class));
+ assertSetEquals(Arrays.asList("OGC:JulianDate", "OGC:TruncatedJulianDate", "OGC:UnixTime"),
+ factory.getAuthorityCodes(TemporalCRS.class));
assertSetEquals(Arrays.asList("CRS:1"),
factory.getAuthorityCodes(EngineeringCRS.class));
@@ -103,6 +110,27 @@ public final strictfp class CommonAuthorityFactoryTest extends TestCase {
assertTrue ("OGC:CRS084", codes.contains("OGC:CRS084"));
}
+ /**
+ * Verifies that the names of temporal CRS (including namespace)
+ * are identical to the one defined by {@link CommonCRS.Temporal}.
+ *
+ * @throws FactoryException if an error occurred while fetching the set of codes.
+ */
+ @Test
+ public void verifyTemporalCodes() throws FactoryException {
+ final Set<String> expected = new HashSet<>();
+ for (final CommonCRS.Temporal e : CommonCRS.Temporal.values()) {
+ assertTrue(expected.add(IdentifiedObjects.toString(getSingleton(e.crs().getIdentifiers()))));
+ }
+ assertFalse(expected.isEmpty());
+ for (final String code : factory.getAuthorityCodes(TemporalCRS.class)) {
+ assertTrue(code, expected.remove(code));
+ }
+ for (final String remaining : expected) {
+ assertFalse(remaining, remaining.startsWith(Constants.OGC));
+ }
+ }
+
/**
* Tests {@link CommonAuthorityFactory#getDescriptionText(String)}.
*
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/DefinitionURI.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/DefinitionURI.java
index 615fdf8572..86d679a1c0 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/DefinitionURI.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/DefinitionURI.java
@@ -716,14 +716,18 @@ public final class DefinitionURI {
* and "http://www.opengis.net/def/crs-compound?1=(…)/crs/EPSG/9.1/27700&2=(…)/crs/EPSG/9.1/5701".
*/
if (components != null) {
+ boolean first = true;
for (int i=0; i<components.length;) {
final DefinitionURI c = components[i++];
- if (isHTTP) {
- buffer.append(i == 1 ? COMPONENT_SEPARATOR_1
- : COMPONENT_SEPARATOR_2)
- .append(i).append(KEY_VALUE_SEPARATOR);
+ if (c != null) {
+ if (isHTTP) {
+ buffer.append(first ? COMPONENT_SEPARATOR_1
+ : COMPONENT_SEPARATOR_2)
+ .append(i).append(KEY_VALUE_SEPARATOR);
+ }
+ c.appendStringTo(buffer, COMPONENT_SEPARATOR);
+ first = false;
}
- c.appendStringTo(buffer, COMPONENT_SEPARATOR);
}
}
}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
index de7a0030d2..195d419288 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
@@ -328,6 +328,11 @@ public final class Errors extends IndexedResourceBundle {
*/
public static final short ForbiddenProperty_1 = 41;
+ /**
+ * Identifier “{1}” is not in “{0}” namespace.
+ */
+ public static final short IdentifierNotInNamespace_2 = 199;
+
/**
* Argument ‘{0}’ can not be an instance of ‘{1}’.
*/
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
index ec0853a874..9919d73574 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
@@ -77,6 +77,7 @@ FactoryNotFound_1 = No factory of kind \u2018{0}\u2019 found.
FileNotFound_1 = File \u201c{0}\u201d has not been found.
ForbiddenAttribute_2 = Attribute \u201c{0}\u201d is not allowed for an object of type \u2018{1}\u2019.
ForbiddenProperty_1 = Property \u201c{0}\u201d is not allowed.
+IdentifierNotInNamespace_2 = Identifier \u201c{1}\u201d is not in \u201c{0}\u201d namespace.
IllegalArgumentClass_2 = Argument \u2018{0}\u2019 can not be an instance of \u2018{1}\u2019.
IllegalArgumentClass_3 = Argument \u2018{0}\u2019 can not be an instance of \u2018{2}\u2019. Expected an instance of \u2018{1}\u2019 or derived type.
IllegalArgumentValue_2 = Argument \u2018{0}\u2019 can not take the \u201c{1}\u201d value.
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
index 305e55dfd6..2f74f10f84 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
@@ -74,6 +74,7 @@ FactoryNotFound_1 = Aucune fabrique de type \u2018{0}\u2019 n\u2
FileNotFound_1 = Le fichier \u00ab\u202f{0}\u202f\u00bb n\u2019a pas \u00e9t\u00e9 trouv\u00e9.
ForbiddenAttribute_2 = L\u2019attribut \u00ab\u202f{0}\u202f\u00bb n\u2019est pas autoris\u00e9 pour un objet de type \u2018{1}\u2019.
ForbiddenProperty_1 = La propri\u00e9t\u00e9 \u00ab\u202f{0}\u202f\u00bb n\u2019est pas autoris\u00e9e.
+IdentifierNotInNamespace_2 = L\u2019identifiant \u201c{1}\u201d n\u2019est pas dans l\u2019espace de noms \u201c{0}\u201d.
IllegalArgumentClass_2 = L\u2019argument \u2018{0}\u2019 ne peut pas \u00eatre de type \u2018{1}\u2019.
IllegalArgumentClass_3 = L\u2019argument \u2018{0}\u2019 ne peut pas \u00eatre de type \u2018{2}\u2019. Une instance de \u2018{1}\u2019 ou d\u2019un type d\u00e9riv\u00e9 \u00e9tait attendue.
IllegalArgumentValue_2 = L\u2019argument \u2018{0}\u2019 n\u2019accepte pas la valeur \u00ab\u202f{1}\u202f\u00bb.