Skip to content
Merged
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
@@ -0,0 +1,51 @@
/*
* 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
*
* https://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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.apache.jena.geosparql.geof.topological.filter_functions.geometry_property;

import org.apache.jena.datatypes.DatatypeFormatException;
import org.apache.jena.geosparql.implementation.GeometryWrapper;
import org.apache.jena.sparql.expr.ExprEvalException;
import org.apache.jena.sparql.expr.ExprEvalTypeException;
import org.apache.jena.sparql.expr.NodeValue;
import org.apache.jena.sparql.function.FunctionBase2;

/** Implements geof:geometryN. */
public class GeometryNFF extends FunctionBase2 {

@Override
public NodeValue exec(NodeValue value, NodeValue index) {
try {
GeometryWrapper geometry = GeometryWrapper.extract(value);
return geometry.getGeometryN(memberIndex(index)).asNodeValue();
} catch (DatatypeFormatException ex) {
throw new ExprEvalException(ex.getMessage(), ex);
} catch (IllegalArgumentException | ArithmeticException ex) {
throw new ExprEvalException(ex.getMessage(), ex);
}
}

private static int memberIndex(NodeValue index) {
if (!index.isInteger()) {
throw new ExprEvalTypeException("Geometry member index must be an xsd:integer: " + index);
}
return index.getInteger().intValueExact();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@

import org.apache.jena.geosparql.implementation.jts.CoordinateSequenceDimensions;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.CoordinateXY;
import org.locationtech.jts.geom.Geometry;

Expand Down Expand Up @@ -90,6 +95,31 @@ public static int findCoordinateDimension(CoordinateSequenceDimensions dims) {
}
}

/**
* Finds dimension metadata from coordinate sequences, preserving Z/M layouts
* even when the sequence is empty. Collections use their first member's
* layout, layout retained on an empty aggregate, or the fallback when they
* have no members.
*/
public static DimensionInfo find(Geometry geometry, CoordinateSequenceDimensions fallback) {
CoordinateSequence sequence = null;
if (geometry instanceof Point point) {
sequence = point.getCoordinateSequence();
} else if (geometry instanceof LineString line) {
sequence = line.getCoordinateSequence();
} else if (geometry instanceof Polygon polygon) {
sequence = polygon.getExteriorRing().getCoordinateSequence();
} else if (geometry instanceof GeometryCollection collection && collection.getNumGeometries() > 0) {
fallback = find(collection.getGeometryN(0), fallback).getDimensions();
} else if (geometry.getUserData() instanceof CoordinateSequenceDimensions retained) {
// WKTReader stores parsed layout here; empty aggregates have no sequence.
fallback = retained;
}
CoordinateSequenceDimensions dimensions = sequence == null ? fallback
: findCoordinateSequenceDimensions(sequence.getDimension(), sequence.getDimension() - sequence.getMeasures());
return new DimensionInfo(dimensions, geometry.getDimension());
}

public static DimensionInfo find(Coordinate coordinate, Geometry geometry) {
// Empty geometries have no coordinate; use the existing XY default.
CoordinateSequenceDimensions coordDims = CoordinateSequenceDimensions.find(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import org.locationtech.jts.geom.CoordinateXY;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.IntersectionMatrix;
import org.locationtech.jts.geom.Point;
Expand Down Expand Up @@ -475,6 +476,26 @@ public double getMaxZ() {
return GeometryCoordinateExtrema.maxZ(this);
}

/**
* Selects a direct geometry member using a one-based index, without flattening
* nested collections. Index 1 selects an atomic geometry itself, including
* an empty atomic geometry. Retains the source datatype and SRS and uses the
* selected member's coordinate layout.
*
* @throws IllegalArgumentException if the index is outside the member range.
*/
public GeometryWrapper getGeometryN(int index) {
if (index < 1 || index > parsingGeometry.getNumGeometries()) {
throw new IllegalArgumentException("Geometry member index is out of range: " + index);
}
if (!(parsingGeometry instanceof GeometryCollection)) {
return this;
}
Geometry member = parsingGeometry.getGeometryN(index - 1);
DimensionInfo dimensions = DimensionInfo.find(member, dimensionInfo.getDimensions());
return new GeometryWrapper(member, getSrsURI(), geometryDatatypeURI, dimensions);
}

/**
*
* @return GeometryDatatype of the literal.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.apache.jena.geosparql.geo.topological.property_functions.geometry_property.SpatialDimensionPF;
import org.apache.jena.geosparql.geof.topological.filter_functions.geometry_property.CoordinateDimensionFF;
import org.apache.jena.geosparql.geof.topological.filter_functions.geometry_property.DimensionFF;
import org.apache.jena.geosparql.geof.topological.filter_functions.geometry_property.GeometryNFF;
import org.apache.jena.geosparql.geof.topological.filter_functions.geometry_property.Is3DFF;
import org.apache.jena.geosparql.geof.topological.filter_functions.geometry_property.IsEmptyFF;
import org.apache.jena.geosparql.geof.topological.filter_functions.geometry_property.IsMeasuredFF;
Expand Down Expand Up @@ -70,7 +71,7 @@ public static void loadPropertyFunctions(PropertyFunctionRegistry registry) {
/**
* Registers {@code geof:} expression functions with the supplied registry.
*
* @param registry - the FunctionRegistry to be used
* @param registry the FunctionRegistry to use
*/
public static void loadFilterFunctions(FunctionRegistry registry) {

Expand All @@ -83,13 +84,14 @@ public static void loadFilterFunctions(FunctionRegistry registry) {
registry.put(Geof.IS_SIMPLE, IsSimpleFF.class);
registry.put(Geof.IS_EMPTY, IsEmptyFF.class);
registry.put(Geof.IS_VALID, IsValidFF.class);

registry.put(Geof.MIN_X, MinXFF.class);
registry.put(Geof.MIN_Y, MinYFF.class);
registry.put(Geof.MIN_Z, MinZFF.class);
registry.put(Geof.MAX_X, MaxXFF.class);
registry.put(Geof.MAX_Y, MaxYFF.class);
registry.put(Geof.MAX_Z, MaxZFF.class);

registry.put(Geof.GEOMETRY_N, GeometryNFF.class);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ private static Polygon buildPolygon(Element gmlElement, CoordinateSequenceDimens
Element exteriorLinearRingElement = exteriorElement.getChild("LinearRing", GML_NAMESPACE);
exteriorLinearRing = buildLinearRing(exteriorLinearRingElement, dims);
} else {
exteriorLinearRing = GEOMETRY_FACTORY.createLinearRing();
exteriorLinearRing = GEOMETRY_FACTORY.createLinearRing(new CustomCoordinateSequence(dims));
}
//Interior shell - [0..*]
List<Element> interiorElements = gmlElement.getChildren("interior", GML_NAMESPACE);
Expand Down Expand Up @@ -536,7 +536,7 @@ private static Polygon buildSurface(Element gmlElement, CoordinateSequenceDimens
Geometry exteriorGeom = buildSurfacePatch(exteriorElement, dims, srsInfo);
exteriorLinearRing = GEOMETRY_FACTORY.createLinearRing(exteriorGeom.getCoordinates());
} else {
exteriorLinearRing = GEOMETRY_FACTORY.createLinearRing();
exteriorLinearRing = GEOMETRY_FACTORY.createLinearRing(new CustomCoordinateSequence(dims));
}

//Interior shell - [0..*]
Expand All @@ -559,6 +559,12 @@ private static Polygon buildSurface(Element gmlElement, CoordinateSequenceDimens
polys.add(polygon);
}

// The union of empty patches is a GeometryCollection in JTS.
// Return an empty patch directly so its coordinate layout is retained.
if (polys.stream().allMatch(Polygon::isEmpty)) {
return polys.get(0);
}

//Unionise all the polygons on the surface together.
Geometry unionGeom = CascadedPolygonUnion.union(polys);
Polygon unionPolygon;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ private static Element buildMultiPoint(final MultiPoint multiPoint, final String
Element gmlRoot = new Element(multiPoint.getGeometryType(), GML_NAMESPACE);
gmlRoot.setAttribute("srsName", srsName);

if (!multiPoint.isEmpty()) {
if (multiPoint.getNumGeometries() > 0) {

int geomCount = multiPoint.getNumGeometries();
for (int i = 0; i < geomCount; i++) {
Expand All @@ -206,7 +206,7 @@ private static Element buildMultiLineString(final MultiLineString multiLineStrin
Element gmlRoot = new Element("MultiCurve", GML_NAMESPACE);
gmlRoot.setAttribute("srsName", srsName);

if (!multiLineString.isEmpty()) {
if (multiLineString.getNumGeometries() > 0) {

int geomCount = multiLineString.getNumGeometries();
for (int i = 0; i < geomCount; i++) {
Expand All @@ -231,7 +231,7 @@ private static Element buildMultiPolygon(final MultiPolygon multiPolygon, final
Element gmlRoot = new Element("MultiSurface", GML_NAMESPACE);
gmlRoot.setAttribute("srsName", srsName);

if (!multiPolygon.isEmpty()) {
if (multiPolygon.getNumGeometries() > 0) {

int geomCount = multiPolygon.getNumGeometries();
for (int i = 0; i < geomCount; i++) {
Expand All @@ -256,7 +256,7 @@ private static Element buildMultiGeometry(final GeometryCollection geometryColle
Element gmlRoot = new Element("MultiGeometry", GML_NAMESPACE);
gmlRoot.setAttribute("srsName", srsName);

if (!geometryCollection.isEmpty()) {
if (geometryCollection.getNumGeometries() > 0) {

int geomCount = geometryCollection.getNumGeometries();
for (int i = 0; i < geomCount; i++) {
Expand Down
Loading
Loading