This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit c4cb4db1cade5191a99942ed8c0fc07be94f9ed0 Author: Martin Desruisseaux <[email protected]> AuthorDate: Wed Jun 10 20:22:34 2026 +0200 Move the code that search for Z and M values in a separated classes. It make easier to manage the ranges, the flags about Z and M presence and the dimensions all together. --- .../sis/geometry/wrapper/jts/GeometryWalker.java | 184 +++++++++++++++++++++ .../apache/sis/geometry/wrapper/jts/Wrapper.java | 112 +++---------- 2 files changed, 209 insertions(+), 87 deletions(-) diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/GeometryWalker.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/GeometryWalker.java new file mode 100644 index 0000000000..96033f0ad8 --- /dev/null +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/GeometryWalker.java @@ -0,0 +1,184 @@ +/* + * 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.geometry.wrapper.jts; + +import org.locationtech.jts.geom.CoordinateSequence; +import org.locationtech.jts.geom.Geometry; +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.apache.sis.geometry.GeneralEnvelope; +import org.apache.sis.util.resources.Errors; + + +/** + * Walks through the component of a geometry for finding the number of dimensions + * and the range of <abbr>z</abbr> and <abbr>m</abbr> values. + * + * @author Johann Sorel (Geomatys) + * @author Martin Desruisseaux (Geomatys) + */ +final class GeometryWalker { + /** + * Whether to search for ranges of <var>z</var> and <var>M</var> values. + * If {@code false}, this method will check only for the number of dimensions. + */ + private boolean wantZM; + + /** + * Whether the coordinate sequence declares to have <var>z</var> or <var>M</var> values. + */ + private boolean hasZ, hasM; + + /** + * Range of <var>z</var> values. + */ + private double minZ, maxZ; + + /** + * Range of <var>M</var> values. + */ + private double minM, maxM; + + /** + * The maximal number of <em>vertex</em> dimensions found. Note that this is different than the + * {@linkplain Geometry#getDimension() geometry topological dimension}, which can be 0, 1 or 2. + * The <abbr>JTS</abbr> vertex dimension can be 2, 3 or 4. + */ + private int dimension; + + /** + * Creates a new walker. + * By default, the walker does <em>not</em> search for ranges of <var>z</var> and <var>M</var> values. + * If this search is desired, the {@link #wantZM()} method must be invoked after construction. + */ + GeometryWalker() { + } + + /** + * Requests the search of ranges of <var>z</var> and <var>M</var> values. + */ + final void wantZM() { + wantZM = true; + minZ = minM = Double.POSITIVE_INFINITY; + maxZ = maxM = Double.NEGATIVE_INFINITY; + } + + /** + * Returns the number of dimensions found by the calls to {@link #scan(Geometry)}. + */ + final int dimension() { + if (hasZ & hasM) return Math.max(4, dimension); + if (hasZ | hasM) return Math.max(3, dimension); + return dimension; + } + + /** + * Sets the <var>z</var> and <var>M</var> coordinates in the given envelope. + * + * @param env the envelope where to set the coordinates. + */ + final void setZM(final GeneralEnvelope env) { + if (wantZM) { + final int limit = env.getDimension(); + int target = Factory.BIDIMENSIONAL; + if (target < limit) { + if (minZ <= maxZ) { + env.setRange(target, minZ, maxZ); + } + if (minM <= maxM) { + if (hasZ) { + // Increment even if the range of Z was undefined. + target = Factory.TRIDIMENSIONAL; + if (target >= limit) return; + } + env.setRange(target, minM, maxM); + } + } + } + } + + /** + * Scans the given geometry for its number of dimensions and range of <var>z</var> and <var>M</var> values. + * This method may invoke itself recursively for walking through components of the given geometry. + * + * @param geometry the geometry to inspect. + * @throws IllegalArgumentException if the type of the given geometry is not recognized. + */ + final void scan(final Geometry geometry) { + if (geometry instanceof Point) { + // Most efficient method (no allocation) in JTS 1.18. + addDimensionAndRanges(((Point) geometry).getCoordinateSequence()); + } else if (geometry instanceof LineString) { + // Most efficient method (no allocation) in JTS 1.18. + addDimensionAndRanges(((LineString) geometry).getCoordinateSequence()); + } else if (geometry instanceof Polygon) { + final var polygon = (Polygon) geometry; + scan(polygon.getExteriorRing()); + if (wantZM) { + final int n = polygon.getNumInteriorRing(); + for (int i=0; i<n; i++) { + scan(polygon.getInteriorRingN(i)); + } + } + } else if (geometry instanceof GeometryCollection) { + final var gc = (GeometryCollection) geometry; + final int n = gc.getNumGeometries(); + if (n > 0) { + for (int i=0; i<n; i++) { + scan(gc.getGeometryN(i)); + } + } else if (dimension == 0) { + // Undefined coordinates, JTS assumes 3 for empty geometries. + dimension = Factory.TRIDIMENSIONAL; + } + } else { + throw new IllegalArgumentException(Errors.format(Errors.Keys.UnknownType_1, geometry.getGeometryType())); + } + } + + /** + * Updates the number of dimensions and the range of <var>z</var> and <var>M</var> values + * from the given coordinate sequence. + * + * @param cs the coordinate sequence from which to get the number of dimensions + * and the range of (<var>z</var>, <var>M</var>) values. + */ + private void addDimensionAndRanges(final CoordinateSequence cs) { + dimension = Math.max(dimension, cs.getDimension()); + if (wantZM) { + final int n = cs.size(); + if (cs.hasZ()) { + hasZ = true; + for (int i=0; i<n; i++) { + double z = cs.getZ(i); + if (z < minZ) minZ = z; + if (z > maxZ) maxZ = z; + } + } + if (cs.hasM()) { + hasM = true; + for (int i=0; i<n; i++) { + double m = cs.getM(i); + if (m < minM) minM = m; + if (m > maxM) maxM = m; + } + } + } + } +} diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java index cd670f0c8f..4f77c2e54d 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/geometry/wrapper/jts/Wrapper.java @@ -26,7 +26,6 @@ import java.util.function.BiFunction; import java.util.function.BiPredicate; import java.util.function.IntFunction; import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.CoordinateSequence; import org.locationtech.jts.geom.Envelope; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryCollection; @@ -68,7 +67,7 @@ import org.opengis.filter.DistanceOperatorName; /** - * The wrapper of Java Topology Suite (JTS) geometries. + * The wrapper of Java Topology Suite (<abbr>JTS</abbr>) geometries. * * @author Johann Sorel (Geomatys) * @author Martin Desruisseaux (Geomatys) @@ -147,72 +146,13 @@ final class Wrapper extends GeometryWrapper { */ @Override public int getCoordinateDimension() { - return getCoordinatesDimension(geometry, null); - } - - /** - * Gets the number of dimensions of geometry vertex (sequence of coordinate tuples), which can be 2 or 3. - * Note that this is different than the {@linkplain Geometry#getDimension() geometry topological dimension}, - * which can be 0, 1 or 2. - * - * @param geometry the geometry for which to get <em>vertex</em> (not topological) dimension. - * @param bounds if defined, compute bounds, array must be of size 8 for [minX,minY,minZ,minM,maxX,maxY,maxZ,maxM] - * @return vertex dimension of the given geometry. - * @throws IllegalArgumentException if the type of the given geometry is not recognized. - */ - private static int getCoordinatesDimension(final Geometry geometry, Double[] bounds) { - int dim = 0; - if (geometry instanceof Point) { - // Most efficient method (no allocation) in JTS 1.18. - dim = getCoordinatesDimension(dim, ((Point) geometry).getCoordinateSequence(), bounds); - } else if (geometry instanceof LineString) { - // Most efficient method (no allocation) in JTS 1.18. - dim = getCoordinatesDimension(dim, ((LineString) geometry).getCoordinateSequence(), bounds); - } else if (geometry instanceof Polygon) { - final Polygon polygon = (Polygon) geometry; - dim = getCoordinatesDimension(dim, ((LineString) polygon.getExteriorRing()).getCoordinateSequence(), bounds); - for (int i = 0, n = polygon.getNumInteriorRing(); i < n; i++) { - dim = getCoordinatesDimension(dim, ((LineString) polygon.getInteriorRingN(i)).getCoordinateSequence(), bounds); - } - - } else if (geometry instanceof GeometryCollection) { - final GeometryCollection gc = (GeometryCollection) geometry; - final int n = gc.getNumGeometries(); - if (n == 0) { - dim = Factory.TRIDIMENSIONAL;// Undefined coordinates, JTS assumes 3 for empty geometries. - } - for (int i = 0; i < n; i++) { - dim = Math.max(dim, getCoordinatesDimension(gc.getGeometryN(i), bounds)); - } - } else { - throw new IllegalArgumentException(Errors.format(Errors.Keys.UnknownType_1, geometry.getGeometryType())); + int dimension = CRS.getDimensionOrZero(crs); + if (dimension == 0) { + final var walker = new GeometryWalker(); + walker.scan(geometry); + dimension = walker.dimension(); } - return dim; - } - - /** - * Get dimension and bounds of coordinate sequence. - * - * @param dim previously computed dimension - * @param cs the coordinate sequence for which to get <em>vertex</em> (not topological) dimension and bounds - * @param bounds if defined, compute bounds, array must be of size 8 for [minX,minY,minZ,minM,maxX,maxY,maxZ,maxM] - * @return coordinate sequence dimension or previous dimension, returning the largest one. - */ - private static int getCoordinatesDimension(int dim, CoordinateSequence cs, Double[] bounds) { - final int dimension = cs.getDimension(); - if (bounds != null) { - for (int i = 0, d = 0, n = cs.size(); i < n; d++) { - if (d == dimension) {d = -1; i++; continue;} - final double val = cs.getOrdinate(i, d); - //check min - if (bounds[d] == null) bounds[d] = val; - else if (!bounds[d].isNaN()) bounds[d] = Double.min(bounds[d], val); - //check max - if (bounds[d+4] == null) bounds[d+4] = val; - else if (!bounds[d+4].isNaN()) bounds[d+4] = Double.max(bounds[d+4], val); - } - } - return Math.max(dim, dimension); + return dimension; } /** @@ -223,29 +163,27 @@ final class Wrapper extends GeometryWrapper { */ @Override public GeneralEnvelope getEnvelope() { - if (crs != null && crs.getCoordinateSystem().getDimension() == 2) { - //we can use JTS envelope internal - final Envelope bounds = geometry.getEnvelopeInternal(); - final var env = new GeneralEnvelope(crs); - env.setToNaN(); - if (!bounds.isNull()) { - env.setRange(0, bounds.getMinX(), bounds.getMaxX()); - env.setRange(1, bounds.getMinY(), bounds.getMaxY()); + final var walker = new GeometryWalker(); + final GeneralEnvelope env; + if (crs != null) { + env = new GeneralEnvelope(crs); + if (env.getDimension() >= Factory.TRIDIMENSIONAL) { + walker.wantZM(); } - return env; + walker.scan(geometry); } else { - //geometry has unknown or more then 2 dimensions - final Double[] bounds = new Double[8]; - final int dim = getCoordinatesDimension(geometry, bounds); - final var env = (crs != null) ? new GeneralEnvelope(crs) : new GeneralEnvelope(dim); - env.setToNaN(); - for (int i = 0, n = Math.min(dim, env.getDimension()); i < n; i++) { - if (bounds[i] != null){ - env.setRange(i, bounds[i], bounds[i+4]); - } - } - return env; + walker.wantZM(); + walker.scan(geometry); + env = new GeneralEnvelope(walker.dimension()); + } + env.setToNaN(); + final Envelope bounds = geometry.getEnvelopeInternal(); + if (!bounds.isNull()) { + env.setRange(0, bounds.getMinX(), bounds.getMaxX()); + env.setRange(1, bounds.getMinY(), bounds.getMaxY()); } + walker.setZM(env); + return env; } /**
