From 3d6e975d21b337b047ad456f032c55626ceaf0f7 Mon Sep 17 00:00:00 2001 From: Clemens Portele Date: Fri, 3 Jul 2026 13:15:10 +0200 Subject: [PATCH] fix the reported CRS identifier when the CRS84 alias URI is requested When a request references CRS84 by its registered alias URI http://www.opengis.net/def/crs/OGC/0/CRS84, the CRS of the response (e.g., in the Content-Crs header) was reported with the primary URI http://www.opengis.net/def/crs/OGC/1.3/CRS84. OGC API Features Part 2 requires that the CRS identifier used in the request is echoed. - EpsgCrs: new auxiliary attribute uriOverride, excluded from equals()/hashCode(); toUriString() returns it when present. Instances that differ only in the URI used to reference the CRS remain equal everywhere (supported-CRS checks, transformer caches, CRS84 special cases). - OgcCrs: new constant CRS84_OGC0, the same CRS as CRS84, but with the alias URI as uriOverride; fromString() returns it for the alias spelling. - EpsgCrs: toAlternativeUriString() replaced by allUris(), which lists all registered URIs of a CRS (the canonical URI plus, for CRS84, the alias URI). --- .../ii/xtraplatform/crs/domain/EpsgCrs.java | 28 ++++++++++++--- .../de/ii/xtraplatform/crs/domain/OgcCrs.java | 9 ++++- .../xtraplatform/crs/infra/EpsgCrsSpec.groovy | 36 ++++++++++++++++--- 3 files changed, 64 insertions(+), 9 deletions(-) diff --git a/xtraplatform-crs/src/main/java/de/ii/xtraplatform/crs/domain/EpsgCrs.java b/xtraplatform-crs/src/main/java/de/ii/xtraplatform/crs/domain/EpsgCrs.java index ae74caea2..f38063750 100644 --- a/xtraplatform-crs/src/main/java/de/ii/xtraplatform/crs/domain/EpsgCrs.java +++ b/xtraplatform-crs/src/main/java/de/ii/xtraplatform/crs/domain/EpsgCrs.java @@ -89,6 +89,17 @@ default Force getForceAxisOrder() { return Force.NONE; } + /** + * Optional CRS identifier (URI) used to reference this CRS, e.g., the alias + * "http://www.opengis.net/def/crs/OGC/0/CRS84" for CRS84. If present, {@code toUriString()} + * returns this value so that responses echo the identifier used in the request. Auxiliary, i.e., + * excluded from {@code equals()}/{@code hashCode()}: instances differing only in this attribute + * represent the same CRS. + */ + @JsonIgnore + @Value.Auxiliary + Optional getUriOverride(); + @JsonIgnore @Value.Lazy default boolean isCompoundCrs() { @@ -130,6 +141,9 @@ default List toUrnStrings() { @JsonIgnore @Value.Lazy default String toUriString() { + if (getUriOverride().isPresent()) { + return getUriOverride().get(); + } if (Objects.equals(this, OgcCrs.CRS84)) { return OgcCrs.CRS84_URI; } @@ -139,12 +153,18 @@ default String toUriString() { return String.format("http://www.opengis.net/def/crs/EPSG/0/%d", getCode()); } + /** + * Returns all registered URIs of this CRS: the canonical URI plus, in the case of CRS84, the + * "OGC/0/CRS84" alias URI. Unlike {@code toUriStrings()}, the list does not decompose a compound + * CRS into its components, it lists the identifiers of this CRS. + */ @JsonIgnore @Value.Lazy - default Optional toAlternativeUriString() { - return Objects.equals(this, OgcCrs.CRS84) - ? Optional.of(OgcCrs.CRS84_URI_NEW) - : Optional.empty(); + default List allUris() { + if (Objects.equals(this, OgcCrs.CRS84)) { + return List.of(OgcCrs.CRS84_URI, OgcCrs.CRS84_URI_NEW); + } + return List.of(toUriString()); } @JsonIgnore diff --git a/xtraplatform-crs/src/main/java/de/ii/xtraplatform/crs/domain/OgcCrs.java b/xtraplatform-crs/src/main/java/de/ii/xtraplatform/crs/domain/OgcCrs.java index a7c3ba117..0e97340fc 100644 --- a/xtraplatform-crs/src/main/java/de/ii/xtraplatform/crs/domain/OgcCrs.java +++ b/xtraplatform-crs/src/main/java/de/ii/xtraplatform/crs/domain/OgcCrs.java @@ -20,6 +20,13 @@ public interface OgcCrs { String CRS84_URI_NEW = "http://www.opengis.net/def/crs/OGC/0/CRS84"; String CRS84_CURIE = "OGC:CRS84"; + /** + * The same CRS as {@link #CRS84} ({@code equals()} is {@code true}), but {@code toUriString()} + * returns the "OGC/0/CRS84" alias URI instead of the "OGC/1.3/CRS84" URI. + */ + EpsgCrs CRS84_OGC0 = + new ImmutableEpsgCrs.Builder().from(CRS84).uriOverride(CRS84_URI_NEW).build(); + EpsgCrs CRS84h = EpsgCrs.of(4979, Force.LON_LAT); String CRS84h_URI = "http://www.opengis.net/def/crs/OGC/0/CRS84h"; @@ -27,7 +34,7 @@ public interface OgcCrs { static Optional fromString(String prefixedCode) { if (Objects.equals(prefixedCode, CRS84_URI_NEW)) { - return Optional.of(CRS84); + return Optional.of(CRS84_OGC0); } if (Objects.equals(prefixedCode, CRS84_URI)) { return Optional.of(CRS84); diff --git a/xtraplatform-crs/src/test/groovy/de/ii/xtraplatform/crs/infra/EpsgCrsSpec.groovy b/xtraplatform-crs/src/test/groovy/de/ii/xtraplatform/crs/infra/EpsgCrsSpec.groovy index 27b7b5f77..fef7ccdb1 100644 --- a/xtraplatform-crs/src/test/groovy/de/ii/xtraplatform/crs/infra/EpsgCrsSpec.groovy +++ b/xtraplatform-crs/src/test/groovy/de/ii/xtraplatform/crs/infra/EpsgCrsSpec.groovy @@ -43,6 +43,21 @@ class EpsgCrsSpec extends Specification { } + def 'fromString - crs84 alias'() { + when: + def crs = EpsgCrs.fromString(OgcCrs.CRS84_URI_NEW) + + then: + crs.code == 4326 + crs.forceAxisOrder == EpsgCrs.Force.LON_LAT + crs == OgcCrs.CRS84 + crs.hashCode() == OgcCrs.CRS84.hashCode() + crs.toUriString() == OgcCrs.CRS84_URI_NEW + crs.toUrnString() == "urn:ogc:def:crs:OGC::4326" + crs.toSafeCurie() == OgcCrs.CRS84_CURIE + + } + def 'fromString - crs84h'() { when: def crs = EpsgCrs.fromString(OgcCrs.CRS84h_URI) @@ -93,10 +108,23 @@ class EpsgCrsSpec extends Specification { crs.toUriString() == uri where: - crs | uri - EpsgCrs.of(25832) | "http://www.opengis.net/def/crs/EPSG/0/25832" - OgcCrs.CRS84 | OgcCrs.CRS84_URI - OgcCrs.CRS84h | OgcCrs.CRS84h_URI + crs | uri + EpsgCrs.of(25832) | "http://www.opengis.net/def/crs/EPSG/0/25832" + OgcCrs.CRS84 | OgcCrs.CRS84_URI + OgcCrs.CRS84_OGC0 | OgcCrs.CRS84_URI_NEW + OgcCrs.CRS84h | OgcCrs.CRS84h_URI + } + + def 'allUris'() { + expect: + crs.allUris() == uris + + where: + crs | uris + EpsgCrs.of(25832) | ["http://www.opengis.net/def/crs/EPSG/0/25832"] + OgcCrs.CRS84 | [OgcCrs.CRS84_URI, OgcCrs.CRS84_URI_NEW] + OgcCrs.CRS84_OGC0 | [OgcCrs.CRS84_URI, OgcCrs.CRS84_URI_NEW] + OgcCrs.CRS84h | [OgcCrs.CRS84h_URI] } def 'CoordinateTupleWithPrecision - uri'() {