Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,19 @@ default Force getForceAxisOrder() {
@Value.Auxiliary
Optional<String> getUriOverride();

/**
* An alternative identifier under which this CRS is known in a community (e.g. the AdV identifier
* {@code urn:adv:crs:ETRS89_UTM32} for EPSG:25832), declared on the entries of the {@code
* additionalCrs} option of the CRS building block. Unlike {@link #getUriOverride()} — which
* echoes the identifier a request used — the alternative URI is only used when a feature encoding
* renders CRS identifiers on the wire (e.g. the GML {@code srsName} with {@code srsNameStyle:
* TEMPLATE}) and when decoding such identifiers on input. Auxiliary, i.e., excluded from {@code
* equals()}/{@code hashCode()}: instances differing only in this attribute represent the same
* CRS.
*/
@Value.Auxiliary
Optional<String> getAlternativeUri();

@JsonIgnore
@Value.Lazy
default boolean isCompoundCrs() {
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@
public interface FeatureTokenDecoderGmlInputProfile {

/**
* Reverse-mapping from the {@code srsName} value seen on a geometry to the resolved {@link
* EpsgCrs}. Required for input shaped after ALKIS NAS, which uses ADV URN forms such as {@code
* urn:adv:crs:DE_DHDN_3GK2_NW101} that the built-in EPSG / OGC URN parser cannot resolve.
* Reverse-mapping from the {@code srsName} value seen on an ordinary geometry to the resolved
* {@link EpsgCrs} — the {@code alternativeUri} declarations of the requestable CRSs (CRS building
* block). Required for input shaped after ALKIS NAS, which uses AdV URN forms such as {@code
* urn:adv:crs:ETRS89_UTM32} that the built-in EPSG / OGC URN parser cannot resolve. The
* identifiers of position variants are not part of this map; they are declared in the {@code
* FeatureSchema} ({@code originalCrsIdentifiers}).
*/
Map<String, EpsgCrs> getSrsNameMappings();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,29 @@ static class Frame {

private final Deque<Frame> stack = new ArrayDeque<>();
private final Map<String, EpsgCrs> srsNameMappings;
private final Set<String> verticalSrsNames;
private boolean waitingForInput = false;
private Geometry<?> result;

/**
* The verbatim {@code srsName} attribute of the outermost geometry element of the current decode,
* or {@code null} when none was present. Unlike the resolved {@link EpsgCrs} this distinguishes
* application-profile forms that map to the same EPSG code (e.g. the AdV realizations {@code
* urn:adv:crs:DE_DHDN_3GK3_HE100} and {@code …HE120}).
*/
private String rawSrsName;

/**
* Set when {@link #rawSrsName} is one of {@link #verticalSrsNames}: the "geometry" is a 1D
* position (a single number in {@code gml:pos}, e.g. a height in a German height reference
* system), which has no representation in the geometry model. The coordinate text is captured
* verbatim in {@link #verticalValue} and no {@link Geometry} is built; {@code decode} returns
* empty with {@code waitingForInput == false} once the subtree is consumed.
*/
private boolean verticalMode;

private String verticalValue;

public GeometryDecoderGml() {
this(Map.of());
}
Expand All @@ -144,14 +164,43 @@ public GeometryDecoderGml() {
* the built-in parsers cannot handle.
*/
public GeometryDecoderGml(Map<String, EpsgCrs> srsNameMappings) {
this(srsNameMappings, Set.of());
}

/**
* @param verticalSrsNames {@code srsName} URI/URN forms of 1D (vertical) reference systems; a
* geometry whose outermost element carries one of these is captured as a scalar value (see
* {@link #getVerticalValue()}) instead of being decoded into a {@link Geometry}.
*/
public GeometryDecoderGml(Map<String, EpsgCrs> srsNameMappings, Set<String> verticalSrsNames) {
this.srsNameMappings = srsNameMappings == null ? Map.of() : srsNameMappings;
this.verticalSrsNames = verticalSrsNames == null ? Set.of() : verticalSrsNames;
}

/** The verbatim srsName of the outermost geometry element of the last completed decode. */
public Optional<String> getRawSrsName() {
return Optional.ofNullable(rawSrsName);
}

/**
* The verbatim coordinate text of a 1D position, present when the last decode consumed a geometry
* in a vertical reference system (see {@link #GeometryDecoderGml(Map, Set)}); in that case {@code
* decode} returned empty although no further input is needed.
*/
public Optional<String> getVerticalValue() {
return Optional.ofNullable(verticalValue);
}

public Optional<Geometry<?>> decode(
AsyncXMLStreamReader<AsyncByteArrayFeeder> parser,
Optional<EpsgCrs> crs,
OptionalInt srsDimension)
throws XMLStreamException, IOException {
// fresh decode of a new geometry property — the 4-arg overload is also used to continue a
// paused decode and must not clear the per-geometry state
this.rawSrsName = null;
this.verticalMode = false;
this.verticalValue = null;
return decode(parser, crs, srsDimension, false);
}

Expand Down Expand Up @@ -192,6 +241,12 @@ public Optional<Geometry<?>> decode(
waitingForInput = false;
return Optional.of(r);
}
if (verticalMode && stack.isEmpty()) {
// 1D position fully consumed — no Geometry to return; the caller reads the captured
// scalar via getVerticalValue()
waitingForInput = false;
return Optional.empty();
}
break;

default:
Expand All @@ -214,6 +269,11 @@ public Optional<Geometry<?>> continueDecoding(
if (bufferedText != null) {
top.textBuffer.append(bufferedText);
}
if (verticalMode) {
this.verticalValue = top.textBuffer.toString().trim();
stack.pop();
return decode(parser, defaultCrs, srsDimension, false);
}
finalizeCoordinates(top);
stack.pop();
applyCoordinates(top);
Expand Down Expand Up @@ -287,6 +347,12 @@ private boolean handleStart(
f.elementName = localName;

if (isObject(kind)) {
if (stack.isEmpty()) {
// outermost geometry element of this decode — keep the verbatim srsName for callers that
// need the original form, and detect 1D positions in vertical reference systems
this.rawSrsName = parser.getAttributeValue(null, "srsName");
this.verticalMode = rawSrsName != null && verticalSrsNames.contains(rawSrsName);
}
Optional<EpsgCrs> explicitCrs = parseSrsName(parser, srsNameMappings);
f.crs = explicitCrs.or(() -> defaultCrs).or(this::inheritedCrs);
OptionalInt dim = parseSrsDimension(parser);
Expand Down Expand Up @@ -317,6 +383,11 @@ private void handleEnd(String localName) throws IOException {
return;
}

if (verticalMode) {
// a 1D position builds no Geometry; the scalar was captured in verticalValue
return;
}

Geometry<?> built = buildGeometry(top);
if (built == null) {
// transparent wrapper — nothing to contribute
Expand Down Expand Up @@ -530,6 +601,13 @@ private boolean readCoordinateText(AsyncXMLStreamReader<AsyncByteArrayFeeder> pa
break;
case XMLStreamConstants.END_ELEMENT:
if (coordFrame.elementName.equals(parser.getLocalName())) {
if (verticalMode) {
// a 1D position is captured verbatim — a single number is not a valid coordinate
// tuple and must not reach the coordinate parser
this.verticalValue = coordFrame.textBuffer.toString().trim();
stack.pop();
return true;
}
finalizeCoordinates(coordFrame);
stack.pop();
applyCoordinates(coordFrame);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2026 interactive instruments GmbH
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package de.ii.xtraplatform.features.gml.domain;

import java.util.Map;
import java.util.Optional;
import org.immutables.value.Value;

/**
* Routing of a geometry property's positions to CRS-specific variant properties, keyed by the
* verbatim {@code srsName} on the wire. Some application schemas (AAA/NAS {@code AX_PunktortAU})
* carry exactly one position per feature, but in one of several CRSs — including realizations that
* map to the same EPSG code (so the resolved CRS cannot reproduce the original srsName) and 1D
* vertical systems (which have no representation in the geometry model). Each variant is stored in
* its own schema property; the verbatim srsName is stored alongside so the encoder can reproduce
* it.
*
* <p>All property values name sibling properties of the geometry property this instance was built
* for (same parent object, usually the feature type itself). Instances are derived at decode time
* from the {@code variants} declaration on the geometry property in the {@code FeatureSchema} and
* the {@code originalCrsIdentifiers} / {@code falseEastingDifference} of the referenced sibling
* properties.
*/
@Value.Immutable
public interface GmlGeometryVariants {

/**
* Wire {@code srsName} → geometry property for 2D/3D positions in a non-native CRS. The decoder
* routes the decoded geometry to the mapped property instead of the base geometry property; the
* resolved CRS (via {@code srsNameMappings}) tags the geometry for the storage transformation.
*/
Map<String, String> getBySrsName();

/**
* Wire {@code srsName} → the {@code falseEastingDifference} of the variant property it routes to.
* Added to the easting of the decoded position so the emitted geometry conforms to the storage
* CRS. Only srsNames with a non-zero difference are present.
*/
Map<String, Double> getShiftBySrsName();

/**
* Wire {@code srsName} → scalar (FLOAT) property for 1D positions. The single coordinate value is
* emitted verbatim at the mapped property; no geometry is built.
*/
Map<String, String> getVerticalBySrsName();

/**
* STRING property that receives the verbatim wire {@code srsName} whenever a position was routed
* via {@link #getBySrsName()} or {@link #getVerticalBySrsName()}. Unset for positions in the
* native CRS.
*/
Optional<String> getSrsNameProperty();
}
Loading
Loading