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 5c99a3add75cfe8c6ede4b73a1d7ec994ac004db Author: jsorel <[email protected]> AuthorDate: Mon Nov 2 16:11:46 2026 +0100 feat(Geometry): add DefaultLineStringOffset curve --- .../org/apache/sis/geometries/GeometryFactory.java | 4 + .../internal/shared/ArrayDataPoints.java | 52 ++- .../internal/shared/DefaultOffsetLineString.java | 471 +++++++++++++++++++++ .../sis/geometries/internal/shared/Vertex.java | 17 + .../apache/sis/geometries/mesh/MeshPrimitive.java | 15 +- .../geometries/operation/GeometryProcessor.java | 39 ++ .../internal/shared/ArrayDataPointsTest.java | 59 +++ .../shared/DefaultOffsetLineStringTest.java | 317 ++++++++++++++ .../sis/geometries/operation/OffsetCurveTest.java | 129 ++++++ .../sis/geometries/operation/ToPrimitiveTest.java | 56 +++ .../sis/referencing/dggs/AbstractDggrsTest.java | 7 +- 11 files changed, 1150 insertions(+), 16 deletions(-) diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryFactory.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryFactory.java index 6d9942180a..280c08c578 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryFactory.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/GeometryFactory.java @@ -75,6 +75,7 @@ import org.apache.sis.geometries.internal.shared.DefaultEmpty; import org.apache.sis.geometries.internal.shared.DefaultGeodesic; import org.apache.sis.geometries.internal.shared.DefaultGeometryCollection; import org.apache.sis.geometries.internal.shared.DefaultLineString; +import org.apache.sis.geometries.internal.shared.DefaultOffsetLineString; import org.apache.sis.geometries.internal.shared.DefaultLinearRing; import org.apache.sis.geometries.internal.shared.DefaultMultiPoint; import org.apache.sis.geometries.internal.shared.DefaultNurbCurve; @@ -502,6 +503,9 @@ public final class GeometryFactory { * or {@code null} if the spatial dimension is 2. */ public OffsetCurve createOffsetCurve(Curve baseCurve, Quantity<?> distance, Bearing refDirection) { + if (baseCurve instanceof LineString ls && refDirection == null) { + return new DefaultOffsetLineString(ls, distance); + } return new DefaultOffsetCurve(baseCurve, distance, refDirection); } diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/ArrayDataPoints.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/ArrayDataPoints.java index 5d02e25e6a..e77336cd61 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/ArrayDataPoints.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/ArrayDataPoints.java @@ -115,19 +115,65 @@ public final class ArrayDataPoints implements DataPoints, DataPointsType { return attributes.get(name); } + /** + * Replaces all attributes at once. + * Used when all attributes are rebuilt with a new length, which {@link #setAttribute(String, Array)} + * cannot do one attribute at a time. + * + * @param attributes the new attributes, which must contain the positions. + * @throws IllegalArgumentException if positions are missing or the arrays have different lengths. + */ + public void setAttributes(Map<String, Array> attributes) { + final Array newPositions = attributes.get(DataPointsType.ATT_POSITION); + ArgumentChecks.ensureNonNull("positions", newPositions); + ArgumentChecks.ensureNonNull("positions crs", newPositions.getCoordinateReferenceSystem()); + final long size = newPositions.getLength(); + for (Map.Entry<String,Array> entry : attributes.entrySet()) { + if (entry.getValue().getLength() != size) { + throw new IllegalArgumentException("Array must have the same length as the positions : " + entry.getKey()); + } + } + this.attributes.clear(); + this.attributes.putAll(attributes); + this.positions = newPositions; + } + + /** + * Sets or removes an attribute. + * + * <p>Once positions are defined, all attributes must have the same length as the positions. + * While positions are empty, attributes of any length are accepted, so that the attributes + * of new data points can be set in any order. Setting positions of a different length then + * requires all non-empty attributes to have that length, the empty ones are removed.</p> + * + * @param name attribute name + * @param array attribute values, or {@code null} to remove the attribute + * @throws IllegalArgumentException if the array length does not match the other attributes. + */ public void setAttribute(String name, Array array) { if (array == null) { if (DataPointsType.ATT_POSITION.equals(name)) { throw new IllegalArgumentException("Positions attribute cannot be removed"); } attributes.remove(name); - } else if (array.getLength() != size()) { - throw new IllegalArgumentException("Array must have the same length"); } else if (DataPointsType.ATT_POSITION.equals(name)) { + final long length = array.getLength(); + if (length != positions.getLength()) { + for (Map.Entry<String,Array> entry : attributes.entrySet()) { + final long l = entry.getValue().getLength(); + if (l != 0 && l != length && !DataPointsType.ATT_POSITION.equals(entry.getKey())) { + throw new IllegalArgumentException("Array must have the same length as attribute " + entry.getKey()); + } + } + //empty attributes do not match the new positions anymore + attributes.values().removeIf((Array a) -> a.getLength() == 0); + } this.positions = array; attributes.put(name, array); - } else { + } else if (array.getLength() == size() || size() == 0) { attributes.put(name, array); + } else { + throw new IllegalArgumentException("Array must have the same length"); } } diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultOffsetLineString.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultOffsetLineString.java new file mode 100644 index 0000000000..0c025798ee --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/DefaultOffsetLineString.java @@ -0,0 +1,471 @@ +/* + * 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.geometries.internal.shared; + +import java.util.HashMap; +import java.util.Map; +import javax.measure.IncommensurableException; +import javax.measure.Quantity; +import javax.measure.Unit; +import static java.lang.StrictMath.abs; +import static java.lang.StrictMath.hypot; +import static java.lang.StrictMath.max; +import static java.lang.StrictMath.min; +import org.opengis.geometry.Envelope; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.apache.sis.geometries.DataPoints; +import org.apache.sis.geometries.DataPointsType; +import org.apache.sis.geometries.GeometryFactory; +import org.apache.sis.geometries.curve.LineString; +import org.apache.sis.geometries.curve.LinearRing; +import org.apache.sis.geometries.operation.OperationException; +import org.apache.sis.maths.Array; +import org.apache.sis.maths.Cursor; +import org.apache.sis.maths.DataType; +import org.apache.sis.measure.Units; + + +/** + * A curve at a constant distance from a base line string, in the plane of the first two axes. + * + * <p>Each segment of the base line string is translated along its normal, then consecutive + * translated segments are joined at the intersection of their supporting lines. The offset + * curve has the same number of points as the base line string, a closed base line string + * gives a closed offset curve. Only the first two coordinates of each position are modified, + * other coordinates and attributes are copied unchanged.</p> + * + * <p>With a negative distance, which is the right side (generated line string with ' character) :</p> + * <pre> + * B___________ C + * | _______ \ + * | |B' C'\ \ + * | | \ \ + * | | D' D + * A A' + * </pre> + * + * <p>With a positive distance, which is the left side :</p> + * <pre> + * B'________________C' + * | B___________ C \ + * | | \ \ + * | | \ \ + * | | \ \ + * | | D D' + * A' A + * </pre> + * + * @author Remi Marechal (Geomatys) + * @author Johann Sorel (Geomatys) + */ +public final class DefaultOffsetLineString extends DefaultOffsetCurve { + + /** + * Accepted tolerance. + */ + private static final double TOLERANCE = 1E-9; + + /** + * Offset data points, computed when first requested. + */ + private DataPoints dataPoints; + + /** + * Creates a curve offset from the given line string. + * + * @param baseCurve line string from which this curve is offset. + * @param distance constant offset distance. A positive distance designates the left side + * of the base curve with respect to its direction, and a negative distance + * its right side. A dimensionless distance is in the unit of the CRS axes. + */ + public DefaultOffsetLineString(final LineString baseCurve, final Quantity<?> distance) { + super(baseCurve, distance, null); + } + + @Override + public LineString getBaseCurve() { + return (LineString) baseCurve; + } + + /** + * Returns the offset positions. + * + * @throws OperationException if the offset cannot be computed. + */ + @Override + public synchronized DataPoints getDataPoints() { + if (dataPoints == null) { + dataPoints = compute(getBaseCurve().getDataPoints(), getAxisDistance()); + } + return dataPoints; + } + + @Override + public Array getControlPoints() { + return getDataPoints().getAttributeArray(DataPointsType.ATT_POSITION); + } + + @Override + public synchronized void setCoordinateReferenceSystem(CoordinateReferenceSystem crs) throws IllegalArgumentException { + super.setCoordinateReferenceSystem(crs); + dataPoints = null; + } + + /** + * Returns the envelope of the offset positions. + */ + @Override + public Envelope getEnvelope() { + if (isEmpty()) return null; + return getDataPoints().getEnvelope(); + } + + /** + * Returns the offset positions as a line string, or a linear ring if the base curve is a linear ring. + * The arguments are ignored since this curve is already linear. + */ + @Override + public LineString asLine(final Quantity<?> spacing, final Quantity<?> offset) { + if (baseCurve instanceof LinearRing) { + return GeometryFactory.DEFAULT.createLinearRing(getDataPoints()); + } + return GeometryFactory.DEFAULT.createLineString(getDataPoints()); + } + + /** + * Returns the offset distance in the unit of the first CRS axis. + * + * @throws UnsupportedOperationException if the distance unit cannot be converted to the axis unit. + */ + private double getAxisDistance() { + final double value = distance.getValue().doubleValue(); + final Unit<?> axisUnit = baseCurve.getCoordinateReferenceSystem().getCoordinateSystem().getAxis(0).getUnit(); + if (axisUnit == null || Units.UNITY.equals(distance.getUnit())) { + return value; + } + try { + return distance.getUnit().getConverterToAny(axisUnit).convert(value); + } catch (IncommensurableException e) { + throw new UnsupportedOperationException("The offset distance in " + distance.getUnit() + + " cannot be converted to the unit of axis 0 (" + axisUnit + ").", e); + } + } + + /** + * Computes the offset positions. + * + * @param base data points of the base line string. + * @param distance offset distance, positive for the left side and negative for the right side. + * @return copy of the base data points with offset positions. + * @throws OperationException if a translation is impossible. + */ + private static DataPoints compute(DataPoints base, double distance) throws OperationException { + final ArrayDataPoints ps = copy(base); + final Array positions = ps.getAttribute(DataPointsType.ATT_POSITION); + final int nb = Math.toIntExact(positions.getLength()); + if (distance == 0 || nb < 2) return ps; + + //the algorithm translates on the right side for positive values + distance = -distance; + + final double[] xs = new double[nb]; + final double[] ys = new double[nb]; + Cursor cursor = positions.cursor(); + for (int i = 0; cursor.next(); i++) { + xs[i] = cursor.getSample(0); + ys[i] = cursor.getSample(1); + } + + final double[] dxs = new double[nb]; + final double[] dys = new double[nb]; + //previous translated segment + final double[] seg0 = new double[4]; + //current translated segment + final double[] seg1 = new double[4]; + final double[] inter = new double[2]; + + translateSegment(xs[0], ys[0], xs[1], ys[1], seg0, distance); + dxs[0] = seg0[0]; + dys[0] = seg0[1]; + if (nb == 2) { + dxs[1] = seg0[2]; + dys[1] = seg0[3]; + } else { + final boolean closed = xs[0] == xs[nb - 1] && ys[0] == ys[nb - 1]; + final int segmentNumber = nb - 1; + for (int c = 1; c < segmentNumber; c++) { + translateSegment(xs[c], ys[c], xs[c+1], ys[c+1], seg1, distance); + + //intersection between current translated segment and previously generated segment + intersectionRightLine(seg0[0], seg0[1], seg0[2], seg0[3], seg1[0], seg1[1], seg1[2], seg1[3], inter); + dxs[c] = inter[0]; + dys[c] = inter[1]; + System.arraycopy(seg1, 0, seg0, 0, 4); + + if (c == segmentNumber - 1) { + if (closed) { + //close the translated line string + intersectionRightLine(seg0[0], seg0[1], seg0[2], seg0[3], dxs[0], dys[0], dxs[1], dys[1], inter); + dxs[0] = inter[0]; + dys[0] = inter[1]; + dxs[c+1] = inter[0]; + dys[c+1] = inter[1]; + } else { + dxs[c+1] = seg1[2]; + dys[c+1] = seg1[3]; + } + } + } + } + + cursor = positions.cursor(); + for (int i = 0; cursor.next(); i++) { + cursor.setSample(0, dxs[i]); + cursor.setSample(1, dys[i]); + } + return ps; + } + + /** + * Copy all attributes, positions are converted to doubles to store the offset coordinates. + */ + private static ArrayDataPoints copy(DataPoints ps) { + final Map<String,Array> attributes = new HashMap<>(); + for (String name : ps.getType().getAttributeNames()) { + final Array array = ps.getAttributeArray(name); + attributes.put(name, DataPointsType.ATT_POSITION.equals(name) ? array.retype(DataType.DOUBLE) : array.copy()); + } + return new ArrayDataPoints(attributes); + } + + /** + * Translate a single segment along its normal. + * + * @param x0 segment start x + * @param y0 segment start y + * @param x1 segment end x + * @param y1 segment end y + * @param dst translated segment as x0,y0,x1,y1 + * @param distance offset distance, positive for the right side and negative for the left side. + */ + private static void translateSegment(double x0, double y0, double x1, double y1, double[] dst, double distance) { + final double vx = x1 - x0; + final double vy = y1 - y0; + + //orthogonal vector + double ox = vy; + double oy = -vx; + final double normO = hypot(ox, oy); + ox = ox / normO * distance; + oy = oy / normO * distance; + + dst[0] = x0 + ox; + dst[1] = y0 + oy; + dst[2] = x1 + ox; + dst[3] = y1 + oy; + } + + /** + * Find the intersection point between the two lines (AB) and (CD). + * + * @param out receives the intersection point x,y. + * @throws OperationException if the two lines do not intersect. + */ + private static void intersectionRightLine(double ax, double ay, double bx, double by, + double cx, double cy, double dx, double dy, + double[] out) throws OperationException { + + //director vector of (AB) line + final double ux = bx - ax; + final double uy = by - ay; + + //director vector of (CD) line + final double vx = dx - cx; + final double vy = dy - cy; + + //B = C + if (abs(bx - cx) < TOLERANCE && abs(by - cy) < TOLERANCE) { + set(out, bx, by); + return; + } + + //A = D, should not happen if the line string direction is respected + if (abs(ax - dx) < TOLERANCE && abs(ay - dy) < TOLERANCE) { + set(out, ax, ay); + return; + } + + //A = B, should not happen + if (abs(ax - bx) < TOLERANCE && abs(ay - by) < TOLERANCE) { + //CA vector, if colinear with CD then AB is on CD + final double wx = ax - cx; + final double wy = ay - cy; + if (abs(wx * vy - wy * vx) < TOLERANCE) { + set(out, ax, ay); + return; + } + throw noIntersection(); + } + + //C = D, should not happen + if (abs(cx - dx) < TOLERANCE && abs(cy - dy) < TOLERANCE) { + //AC vector, if colinear with AB then CD is on AB + final double wx = cx - ax; + final double wy = cy - ay; + if (abs(ux * wy - uy * wx) < TOLERANCE) { + set(out, cx, cy); + return; + } + throw noIntersection(); + } + + if (abs(vy) < TOLERANCE && abs(ux) < TOLERANCE) { + /* + * B + * | + * C __|___ D + * | + * A + */ + set(out, ax, cy); + return; + } + + if (abs(vx) < TOLERANCE && abs(uy) < TOLERANCE) { + /* + * D + * | + * A __|___ B + * | + * C + */ + set(out, cx, ay); + return; + } + + if (abs(ux) < TOLERANCE && abs(vx) < TOLERANCE) { + /* + * B + * | D + * A | + * C + */ + if (abs(ax - cx) > TOLERANCE) throw noIntersection(); + set(out, ax, getIntersectionOnOneDimension(ay, by, cy, dy)); + return; + } + + if (abs(uy) < TOLERANCE && abs(vy) < TOLERANCE) { + /* + * A_____B + * C_____D + */ + if (abs(ay - cy) > TOLERANCE) throw noIntersection(); + set(out, getIntersectionOnOneDimension(ax, bx, cx, dx), ay); + return; + } + + if (abs(ux) <= TOLERANCE) { + /* + * B D + * | / + * |/ + * /| + * / A + * C + */ + set(out, ax, (ax - cx) * (vy / vx) + cy); + return; + } + + if (abs(uy) <= TOLERANCE) { + /* + * D + * / + * A__/____B + * / + * C + */ + set(out, (ay - cy) * (vx / vy) + cx, ay); + return; + } + + if (abs(vx) <= TOLERANCE) { + /* + * D B + * | / + * |/ + * /| + * / C + * A + */ + set(out, cx, (cx - ax) * (uy / ux) + ay); + return; + } + + if (abs(vy) <= TOLERANCE) { + /* + * B + * / + * C__/____D + * / + * A + */ + set(out, (cy - ay) * (ux / uy) + ax, cy); + return; + } + + final double vx_vy = vx / vy; + + /* + * (AB) parametric equation. + * {X(t) = t * ux + A.x + * {Y(t) = t * uy + A.y + */ + final double t = ((ay - cy) * vx_vy + cx - ax) / (ux - uy * vx_vy); + final double x = t * ux + ax; + final double y = t * uy + ay; + assert abs(y - ((x - cx) / vx_vy + cy)) <= TOLERANCE : "Error computing y from lines should be equal"; + set(out, x, y); + } + + /** + * Returns the middle of the intersection between spans [a,b] and [c,d]. + * + * @throws OperationException if the spans do not intersect. + */ + private static double getIntersectionOnOneDimension(double a, double b, double c, double d) throws OperationException { + final double imin = max(a, c); + final double imax = min(b, d); + if (imax + TOLERANCE < imin - TOLERANCE) { + /* + * A_____B C____D + */ + throw noIntersection(); + } + return (imin + imax) / 2.0; + } + + private static void set(double[] out, double x, double y) { + out[0] = x; + out[1] = y; + } + + private static OperationException noIntersection() { + return new OperationException("No intersection between lines."); + } +} diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/Vertex.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/Vertex.java index 2763cf886f..255a16b738 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/Vertex.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/internal/shared/Vertex.java @@ -16,6 +16,8 @@ */ package org.apache.sis.geometries.internal.shared; +import java.util.Arrays; +import java.util.TreeSet; import org.apache.sis.geometries.DataPoints; import org.apache.sis.geometries.DataPointsType; import org.apache.sis.geometries.Point; @@ -79,4 +81,19 @@ public final class Vertex implements Point { return parent.getType(); } + /** + * Returns the index followed by the values of all attributes, sorted by name. + */ + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("V:"); + sb.append(getIndex()); + final TreeSet<String> properties = new TreeSet<>(getDataPointsType().getAttributeNames()); + for (String name : properties) { + sb.append(' '); + sb.append(name); + sb.append(Arrays.toString(getAttribute(name).toArrayDouble())); + } + return sb.toString(); + } } diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/mesh/MeshPrimitive.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/mesh/MeshPrimitive.java index e4dabfa4a7..1b7f6648eb 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/mesh/MeshPrimitive.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/mesh/MeshPrimitive.java @@ -676,14 +676,9 @@ public sealed interface MeshPrimitive extends Geometry private void validate(String name, Array ta) { - final int dimension = ta.getDimension(); final long length = ta.getLength(); - final Cursor cursor = ta.cursor(); - final Vector tuple = cursor.getSamples(); - - for (int i = 0; i <length; i++) { - cursor.moveTo(i); - if (!tuple.isFinite()) { + for (long i = 0; i < length; i++) { + if (!ta.get(i).isFinite()) { throw new IllegalArgumentException("Found a NaN on attribute " + name +" at index "+ i); } } @@ -907,11 +902,13 @@ public sealed interface MeshPrimitive extends Geometry setIndex(NDArrays.of(newIndex, 1, index.getDataType())); + //all attributes change length, they must be replaced together + final Map<String,Array> rebuilt = new HashMap<>(); for (String attName : attNames) { final Array model = dataPoints.getAttribute(attName); - final Array array = NDArrays.of(atts.get(attName), model.getSampleSystem(), model.getDataType()); - getDataPoints().setAttribute(attName, array); + rebuilt.put(attName, NDArrays.of(atts.get(attName), model.getSampleSystem(), model.getDataType())); } + dataPoints.setAttributes(rebuilt); positions = dataPoints.getAttribute(DataPointsType.ATT_POSITION); } diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/GeometryProcessor.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/GeometryProcessor.java index f0b6b46967..387630f3ab 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/GeometryProcessor.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/operation/GeometryProcessor.java @@ -35,11 +35,14 @@ import org.apache.sis.geometries.DataPointsType; import org.apache.sis.geometries.Geometries; import org.apache.sis.geometries.Geometry; import org.apache.sis.geometries.GeometryCollection; +import org.apache.sis.geometries.GeometryFactory; import org.apache.sis.geometries.GeometryType; import org.apache.sis.geometries.Point; import org.apache.sis.geometries.Prism; import org.apache.sis.geometries.curve.LineString; import org.apache.sis.geometries.curve.LinearRing; +import org.apache.sis.geometries.curve.OffsetCurve; +import org.apache.sis.geometries.cs.Bearing; import org.apache.sis.geometries.internal.shared.ArrayDataPoints; import org.apache.sis.geometries.internal.shared.Vertex; import org.apache.sis.geometries.mesh.MeshPrimitive; @@ -92,6 +95,42 @@ public final class GeometryProcessor { return fromJTS(jts(geom).buffer(radius.getValue().doubleValue()), geom); } + /** + * Returns a line string parallel to the given line string, at the given perpendicular distance. + * Each segment is translated along its normal and consecutive segments are joined + * at the intersection of their supporting lines. + * The result has the same number of points, closed line strings stay closed. + * Only the first two coordinates of each position are modified. + * + * @param geom a line string or a collection of line strings. + * @param distance offset distance in CRS units, positive for the left side and negative + * for the right side relative to the line string direction, as in {@link OffsetCurve}. + * @return offset geometry of the same type. + * @throws OperationException if the offset curve cannot be computed. + * + * @see GeometryFactory#createOffsetCurve(Curve, Quantity, Bearing) + */ + public Geometry offsetCurve(Geometry geom, double distance) throws OperationException { + if (distance == 0 || geom.isEmpty()) { + return geom; + } + if (geom instanceof LineString cdt) { + return GeometryFactory.DEFAULT.createOffsetCurve(cdt, Quantities.create(distance, Units.UNITY), null).asLine(null, null); + } else if (geom instanceof GeometryCollection<?> cdt) { + final int nb = cdt.getNumGeometries(); + final Geometry[] children = new Geometry[nb]; + for (int i = 0; i < nb; i++) { + final Geometry child = cdt.getGeometryN(i); + if (!(child instanceof LineString)) { + throw new OperationException("Only collections of line strings can be offset, found " + child.getGeometryType()); + } + children[i] = offsetCurve(child, distance); + } + return GeometryFactory.DEFAULT.createGeometryCollection(cdt.getElementType(), cdt.getCoordinateReferenceSystem(), children); + } + throw new UnsupportedOperationException(); + } + /** * Returns a geometric object that represents the convex hull of this geometric object. * Convex hulls, being dependent on straight lines, can be accurately represented in linear interpolations diff --git a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/internal/shared/ArrayDataPointsTest.java b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/internal/shared/ArrayDataPointsTest.java index 363d32f372..7fd9e64e26 100644 --- a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/internal/shared/ArrayDataPointsTest.java +++ b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/internal/shared/ArrayDataPointsTest.java @@ -16,7 +16,14 @@ */ package org.apache.sis.geometries.internal.shared; +import java.util.LinkedHashMap; +import java.util.Map; +import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.geometries.DataPoints; +import org.apache.sis.geometries.DataPointsType; +import org.apache.sis.maths.Array; +import org.apache.sis.maths.NDArrays; +import org.apache.sis.referencing.CommonCRS; // Test dependencies import static org.junit.jupiter.api.Assertions.*; @@ -41,4 +48,56 @@ public class ArrayDataPointsTest extends DataPointsTest { assertInstanceOf(ArrayDataPoints.class, points.subset(new int[] {1})); assertInstanceOf(ArrayDataPoints.class, points.subset(0, 1)); } + + private static final CoordinateReferenceSystem CRS2D = CommonCRS.WGS84.normalizedGeographic(); + + /** + * Tests that attributes of empty data points can be set before the positions. + */ + @Test + public void testAttributeBeforePositions() { + final ArrayDataPoints points = new ArrayDataPoints(NDArrays.of(CRS2D, new double[0])); + points.setAttribute("TEST", NDArrays.of(1, new double[] {1, 2, 3})); + points.setAttribute(DataPointsType.ATT_POSITION, NDArrays.of(CRS2D, new double[] {0,0, 1,1, 2,2})); + assertEquals(3, points.size()); + assertArrayEquals(new double[] {1, 2, 3}, points.getAttribute("TEST").toArrayDouble()); + } + + /** + * Tests that positions of a new length replace the positions of empty data points + * and that the empty attributes are removed. + */ + @Test + public void testPositionsOfEmpty() { + final Map<String,Array> attributes = new LinkedHashMap<>(); + attributes.put(DataPointsType.ATT_POSITION, NDArrays.of(CRS2D, new double[0])); + attributes.put("TEST", NDArrays.of(1, new double[0])); + final ArrayDataPoints points = new ArrayDataPoints(attributes); + points.setAttribute(DataPointsType.ATT_POSITION, NDArrays.of(CRS2D, new double[] {0,0, 1,1})); + assertEquals(2, points.size()); + assertNull(points.getAttribute("TEST")); + } + + /** + * Tests that attributes whose length does not match the positions are rejected. + */ + @Test + public void testLengthMismatch() { + final ArrayDataPoints points = new ArrayDataPoints(NDArrays.of(CRS2D, new double[] {0,0, 1,1})); + assertThrows(IllegalArgumentException.class, () -> points.setAttribute("TEST", NDArrays.of(1, new double[] {1, 2, 3}))); + + //positions alone can be replaced by positions of another length + points.setAttribute(DataPointsType.ATT_POSITION, NDArrays.of(CRS2D, new double[] {0,0, 1,1, 2,2})); + assertEquals(3, points.size()); + + //but not when another attribute has the old length + points.setAttribute("TEST", NDArrays.of(1, new double[] {1, 2, 3})); + assertThrows(IllegalArgumentException.class, + () -> points.setAttribute(DataPointsType.ATT_POSITION, NDArrays.of(CRS2D, new double[] {0,0, 1,1}))); + + final ArrayDataPoints empty = new ArrayDataPoints(NDArrays.of(CRS2D, new double[0])); + empty.setAttribute("TEST", NDArrays.of(1, new double[] {1, 2, 3})); + assertThrows(IllegalArgumentException.class, + () -> empty.setAttribute(DataPointsType.ATT_POSITION, NDArrays.of(CRS2D, new double[] {0,0, 1,1}))); + } } diff --git a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/internal/shared/DefaultOffsetLineStringTest.java b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/internal/shared/DefaultOffsetLineStringTest.java new file mode 100644 index 0000000000..1aa5d89704 --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/internal/shared/DefaultOffsetLineStringTest.java @@ -0,0 +1,317 @@ +/* + * 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.geometries.internal.shared; + +import static java.lang.StrictMath.sqrt; +import javax.measure.Quantity; +import org.opengis.geometry.Envelope; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.apache.sis.geometries.DataPointsType; +import org.apache.sis.geometries.Geometry; +import org.apache.sis.geometries.GeometryFactory; +import org.apache.sis.geometries.GeometryType; +import org.apache.sis.geometries.curve.LineString; +import org.apache.sis.geometries.curve.LinearRing; +import org.apache.sis.geometries.curve.OffsetCurve; +import org.apache.sis.maths.NDArrays; +import org.apache.sis.measure.Quantities; +import org.apache.sis.measure.Units; +import org.apache.sis.referencing.CommonCRS; + +// Test dependencies +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import org.junit.jupiter.api.Test; + + +/** + * Tests {@link DefaultOffsetLineString}. + * + * @author Remi Marechal (Geomatys) + * @author Johann Sorel (Geomatys) + */ +public class DefaultOffsetLineStringTest { + + private static final double TOLERANCE = 1E-9; + + private static final double SQRT_2 = sqrt(2.0); + + private static final CoordinateReferenceSystem CRS_2D = CommonCRS.WGS84.normalizedGeographic(); + + private static final CoordinateReferenceSystem CRS_3D = CommonCRS.WGS84.geographic3D(); + + /** + * Create a line string from coordinates ordered as x0, y0, ... xn, yn. + */ + private static LineString line(double ... coords) { + return GeometryFactory.DEFAULT.createLineString(new ArrayDataPoints(NDArrays.of(CRS_2D, coords))); + } + + private static OffsetCurve offset(LineString line, Quantity<?> distance) { + return GeometryFactory.DEFAULT.createOffsetCurve(line, distance, null); + } + + private static OffsetCurve offset(LineString line, double distance) { + return offset(line, Quantities.create(distance, Units.UNITY)); + } + + private static double[] positions(Geometry geom) { + return geom.getDataPoints().getAttributeArray(DataPointsType.ATT_POSITION).toArrayDouble(); + } + + /** + * Verifies the positions of the offset curve. + * + * @param distance offset distance, positive for the left side and negative for the right side. + */ + private static void assertOffset(LineString line, double distance, double ... expected) { + final OffsetCurve result = offset(line, distance); + assertInstanceOf(DefaultOffsetLineString.class, result); + assertEquals(CRS_2D, result.getCoordinateReferenceSystem()); + assertArrayEquals(expected, positions(result), TOLERANCE); + } + + /** + * Tests that the factory creates a {@link DefaultOffsetLineString} only for line strings in 2D. + */ + @Test + public void testFactory() { + final LineString line = line(2, 1, 2, 4); + final OffsetCurve result = offset(line, 3); + assertInstanceOf(DefaultOffsetLineString.class, result); + assertSame(line, result.getBaseCurve()); + assertEquals(GeometryType.OFFSETCURVE, result.getGeometryType()); + assertNull(result.getRefDirection()); + } + + /** + * Tests line strings composed of a single segment, in all directions. + */ + @Test + public void testSingleSegment() { + /* + * B + * | + * A + */ + LineString line = line(2, 1, 2, 4); + assertOffset(line, -3, 5, 1, 5, 4); + assertOffset(line, 5, -3, 1, -3, 4); + + /* + * B + * / + * A + */ + line = line(2, 1, 4, 3); + assertOffset(line, -3 * SQRT_2, 5, -2, 7, 0); + assertOffset(line, 5 * SQRT_2, -3, 6, -1, 8); + + /* + * A ____ B + */ + line = line(2, 1, 4, 1); + assertOffset(line, -3, 2, -2, 4, -2); + assertOffset(line, 7, 2, 8, 4, 8); + + /* + * A + * \ + * \ + * B + */ + line = line(-1, 3, 6, -4); + assertOffset(line, -7 * SQRT_2, -8, -4, -1, -11); + assertOffset(line, 7 * SQRT_2, 6, 10, 13, 3); + + /* + * A + * | + * B + */ + line = line(-1, 3, -1, -4); + assertOffset(line, -2, -3, 3, -3, -4); + assertOffset(line, 5, 4, 3, 4, -4); + + /* + * A + * / + * / + * B + */ + line = line(1, 3, -4, -2); + assertOffset(line, -5 * SQRT_2, -4, 8, -9, 3); + assertOffset(line, 5 * SQRT_2, 6, -2, 1, -7); + + /* + * B ____ A + */ + line = line(4, 3, -10, 3); + assertOffset(line, -2, 4, 5, -10, 5); + assertOffset(line, 5, 4, -2, -10, -2); + + /* + * B + * \ + * \ + * A + */ + line = line(7, -3, -6, 10); + assertOffset(line, -9 * SQRT_2, 16, 6, 3, 19); + assertOffset(line, 5 * SQRT_2, 2, -8, -11, 5); + } + + /** + * Tests line strings composed of several segments, closed and not closed. + */ + @Test + public void testSeveralSegments() { + /* + * B_______C + * | | + * | | + * | | + * A_______D + * + * Clockwise + */ + LineString line = line(-2, -2, -2, 3, 5, 3, 5, -2, -2, -2); + //inside + assertOffset(line, -1.5, + -0.5, -0.5, -0.5, 1.5, 3.5, 1.5, 3.5, -0.5, -0.5, -0.5); + //outside + assertOffset(line, 7.2, + -9.2, -9.2, -9.2, 10.2, 12.2, 10.2, 12.2, -9.2, -9.2, -9.2); + + /* + * D + * / \ + * / \ + * A / \ + * \ / C + * \ / + * \ / + * B + * + * Counterclockwise + */ + line = line(-1, -1, 3, -5, 7, -1, 3, 3, -1, -1); + //inside + assertOffset(line, SQRT_2, + 1, -1, 3, -3, 5, -1, 3, 1, 1, -1); + //outside + assertOffset(line, -3.3 * SQRT_2, + -7.6, -1, 3, -11.6, 13.6, -1, 3, 9.6, -7.6, -1); + + /* + * B____C_____D F K + * | \ /| / + * | \ / | / + * | \/ G J + * A E | / + * |_____/ + * H I + * + * Not closed. + */ + line = line(-2, -3, -2, 5, 1, 5, 4, 5, 6, 1, 9, 5, 9, 2, 9, -1, 12, -1, 16, 0, 20, 1); + assertOffset(line, -1, + -1, -3, + -1, 4, + 1, 4, + 3.381966011250105, 4, + 5.829179606750063, -0.8944271909999157, + 8, 2, + 8, 2, + 8, -2, + 12.12310562561766, -2, + 16.242535625036332, -0.9701425001453319, + 20.242535625036332, 0.029857499854668124); + assertOffset(line, 2, + -4, -3, + -4, 7, + 1, 7, + 5.23606797749979, 7, + 6.341640786499873, 4.7888543819998315, + 11, 11, + 11, 2, + 11, 1, + 11.753788748764679, 1, + 15.514928749927334, 1.9402850002906638, + 19.514928749927336, 2.9402850002906638); + } + + /** + * Tests that a linear ring gives a linear ring when linearized. + */ + @Test + public void testLinearRing() { + final LinearRing ring = GeometryFactory.DEFAULT.createLinearRing(new ArrayDataPoints(NDArrays.of(CRS_2D, + -2.0, -2.0, -2.0, 3.0, 5.0, 3.0, 5.0, -2.0, -2.0, -2.0))); + final LineString result = offset(ring, -1.5).asLine(null, null); + assertInstanceOf(LinearRing.class, result); + assertArrayEquals(new double[]{-0.5, -0.5, -0.5, 1.5, 3.5, 1.5, 3.5, -0.5, -0.5, -0.5}, + positions(result), TOLERANCE); + } + + /** + * Tests that coordinates after the first two are preserved. + */ + @Test + public void testThirdDimensionPreserved() { + final LineString line = GeometryFactory.DEFAULT.createLineString(new ArrayDataPoints(NDArrays.of(CRS_3D, + 2.0, 1.0, 10.0, + 2.0, 4.0, 20.0))); + final OffsetCurve result = offset(line, -3); + assertEquals(CRS_3D, result.getCoordinateReferenceSystem()); + assertArrayEquals(new double[]{5, 1, 10, 5, 4, 20}, positions(result), TOLERANCE); + } + + /** + * Tests that integer positions are not truncated. + */ + @Test + public void testIntegerPositions() { + final LineString line = GeometryFactory.DEFAULT.createLineString(new ArrayDataPoints(NDArrays.of(CRS_2D, + 2, 1, + 2, 4))); + assertArrayEquals(new double[]{3.5, 1, 3.5, 4}, positions(offset(line, -1.5)), TOLERANCE); + } + + /** + * Tests that a distance in a unit of measurement is converted to the unit of the CRS axes. + */ + @Test + public void testDistanceUnit() { + final LineString line = line(2, 1, 2, 4); + final OffsetCurve result = offset(line, Quantities.create(-3 * 60, Units.ARC_MINUTE)); + assertArrayEquals(new double[]{5, 1, 5, 4}, positions(result), TOLERANCE); + } + + /** + * Tests that the envelope is the one of the offset positions. + */ + @Test + public void testEnvelope() { + final Envelope env = offset(line(2, 1, 2, 4), -3).getEnvelope(); + assertArrayEquals(new double[]{5, 1}, env.getLowerCorner().getCoordinates(), TOLERANCE); + assertArrayEquals(new double[]{5, 4}, env.getUpperCorner().getCoordinates(), TOLERANCE); + } +} diff --git a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/operation/OffsetCurveTest.java b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/operation/OffsetCurveTest.java new file mode 100644 index 0000000000..7d466dbcf0 --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/operation/OffsetCurveTest.java @@ -0,0 +1,129 @@ +/* + * 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.geometries.operation; + +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.apache.sis.geometries.DataPointsType; +import org.apache.sis.geometries.Geometry; +import org.apache.sis.geometries.GeometryCollection; +import org.apache.sis.geometries.GeometryFactory; +import org.apache.sis.geometries.GeometryType; +import org.apache.sis.geometries.curve.LineString; +import org.apache.sis.geometries.curve.LinearRing; +import org.apache.sis.geometries.internal.shared.ArrayDataPoints; +import org.apache.sis.maths.NDArrays; +import org.apache.sis.referencing.CommonCRS; + +// Test dependencies +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import org.junit.jupiter.api.Test; + + +/** + * Tests the {@code offsetCurve} operation of {@link GeometryProcessor}. + * The offset algorithm itself is tested by {@code DefaultOffsetLineStringTest}. + * + * @author Johann Sorel (Geomatys) + */ +public class OffsetCurveTest { + + private static final double TOLERANCE = 1E-9; + + private static final CoordinateReferenceSystem CRS_2D = CommonCRS.WGS84.normalizedGeographic(); + + /** + * Create a line string from coordinates ordered as x0, y0, ... xn, yn. + */ + private static LineString line(double ... coords) { + return GeometryFactory.DEFAULT.createLineString(new ArrayDataPoints(NDArrays.of(CRS_2D, coords))); + } + + private static Geometry offset(Geometry geom, double distance) { + return new GeometryProcessor().offsetCurve(geom, distance); + } + + private static double[] positions(Geometry geom) { + return geom.getDataPoints().getAttributeArray(DataPointsType.ATT_POSITION).toArrayDouble(); + } + + /** + * Tests that a line string gives a line string, a negative distance being the right side. + */ + @Test + public void testLineString() { + final Geometry result = offset(line(2, 1, 2, 4), -3); + assertInstanceOf(LineString.class, result); + assertEquals(CRS_2D, result.getCoordinateReferenceSystem()); + assertArrayEquals(new double[]{5, 1, 5, 4}, positions(result), TOLERANCE); + } + + /** + * Tests that a zero distance returns the geometry unchanged. + */ + @Test + public void testZeroDistance() { + final LineString line = line(2, 1, 2, 4); + assertSame(line, offset(line, 0)); + } + + /** + * Tests that a linear ring stays a linear ring. + */ + @Test + public void testLinearRing() { + final LinearRing ring = GeometryFactory.DEFAULT.createLinearRing(new ArrayDataPoints(NDArrays.of(CRS_2D, + -2.0, -2.0, -2.0, 3.0, 5.0, 3.0, 5.0, -2.0, -2.0, -2.0))); + final Geometry result = offset(ring, -1.5); + assertInstanceOf(LinearRing.class, result); + assertArrayEquals(new double[]{-0.5, -0.5, -0.5, 1.5, 3.5, 1.5, 3.5, -0.5, -0.5, -0.5}, + positions(result), TOLERANCE); + } + + /** + * Tests that each line string of a collection is offset. + */ + @Test + public void testCollection() { + final GeometryCollection<LineString> lines = GeometryFactory.DEFAULT.createMultiLineString(CRS_2D, + line(2, 1, 2, 4), + line(2, 1, 4, 1)); + final Geometry result = offset(lines, -3); + final GeometryCollection<?> col = assertInstanceOf(GeometryCollection.class, result); + assertEquals(GeometryType.LINESTRING, col.getElementType()); + assertEquals(CRS_2D, col.getCoordinateReferenceSystem()); + assertEquals(2, col.getNumGeometries()); + assertInstanceOf(LineString.class, col.getGeometryN(0)); + assertArrayEquals(new double[]{5, 1, 5, 4}, positions(col.getGeometryN(0)), TOLERANCE); + assertArrayEquals(new double[]{2, -2, 4, -2}, positions(col.getGeometryN(1)), TOLERANCE); + } + + /** + * Tests that geometries other than line strings are rejected. + */ + @Test + public void testUnsupported() { + assertThrows(UnsupportedOperationException.class, + () -> offset(GeometryFactory.DEFAULT.createPoint(CRS_2D, 1.0, 2.0), 1)); + final GeometryCollection<?> mixed = GeometryFactory.DEFAULT.createGeometryCollection(CRS_2D, + GeometryFactory.DEFAULT.createPoint(CRS_2D, 1.0, 2.0)); + assertThrows(OperationException.class, () -> offset(mixed, 1)); + } +} diff --git a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/operation/ToPrimitiveTest.java b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/operation/ToPrimitiveTest.java index 1bd3e9c2c6..71d17b02b0 100644 --- a/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/operation/ToPrimitiveTest.java +++ b/incubator/src/org.apache.sis.geometry/test/org/apache/sis/geometries/operation/ToPrimitiveTest.java @@ -544,4 +544,60 @@ public class ToPrimitiveTest { final MeshPrimitive primitive1 = (MeshPrimitive) result; assertEquals(MeshPrimitive.Type.POINTS, primitive1.getType()); } + + /** + * Creates two-dimensional data points with a color and a temperature attribute, + * as in the geometry builder samples. The attribute order shall not matter. + */ + private static DataPoints attributedPoints(final double... coordinates) { + final int n = coordinates.length / 2; + final Map<String,Array> attributes = new LinkedHashMap<>(); + attributes.put(DataPointsType.ATT_COLOR, NDArrays.of(4, new float[n * 4])); + attributes.put("TEMPERATURE", NDArrays.of(1, new double[n])); + attributes.put(DataPointsType.ATT_POSITION, NDArrays.of(CRS2D, coordinates)); + return GeometryFactory.DEFAULT.createDataPoints(attributes); + } + + /** + * Verifies that the mesh has the given number of vertices and keeps the attributes. + */ + private static void assertAttributes(final MeshPrimitive mesh, final int count) { + assertEquals(count, mesh.getPositions().getLength()); + assertEquals(count, mesh.getAttribute(DataPointsType.ATT_COLOR).getLength()); + assertEquals(count, mesh.getAttribute("TEMPERATURE").getLength()); + } + + /** + * Tests conversion of points and line strings having attributes other than positions. + */ + @Test + public void testAttributes() { + final GeometryFactory f = GeometryFactory.DEFAULT; + final GeometryProcessor processor = new GeometryProcessor(); + + Geometry result = processor.toPrimitive(f.createPoint(attributedPoints(1, 2)), null, null); + assertAttributes((MeshPrimitive) result, 1); + + result = processor.toPrimitive(f.createMultiPoint(attributedPoints(0,0, 1,1, 2,2)), null, null); + assertAttributes((MeshPrimitive) result, 3); + + result = processor.toPrimitive(f.createLineString(attributedPoints(0,0, 1,1, 2,0)), null, null); + assertAttributes((MeshPrimitive) result, 3); + + //only lines of 2 points, a single primitive of lines + result = processor.toPrimitive(f.createMultiLineString(CRS2D, + f.createLineString(attributedPoints(0,0, 1,1)), + f.createLineString(attributedPoints(2,0, 3,1))), null, null); + assertAttributes((MeshPrimitive) result, 4); + + //longer lines, one line strip per line + result = processor.toPrimitive(f.createMultiLineString(CRS2D, + f.createLineString(attributedPoints(0,0, 1,1, 2,0)), + f.createLineString(attributedPoints(3,0, 4,1, 5,0))), null, null); + final MultiMeshPrimitive<?> multi = assertInstanceOf(MultiMeshPrimitive.class, result); + assertEquals(2, multi.getNumGeometries()); + for (int i = 0; i < 2; i++) { + assertAttributes((MeshPrimitive) multi.getGeometryN(i), 3); + } + } } diff --git a/incubator/src/org.apache.sis.referencing.dggs/test/org/apache/sis/referencing/dggs/AbstractDggrsTest.java b/incubator/src/org.apache.sis.referencing.dggs/test/org/apache/sis/referencing/dggs/AbstractDggrsTest.java index 0e50026b91..0f90aeac49 100644 --- a/incubator/src/org.apache.sis.referencing.dggs/test/org/apache/sis/referencing/dggs/AbstractDggrsTest.java +++ b/incubator/src/org.apache.sis.referencing.dggs/test/org/apache/sis/referencing/dggs/AbstractDggrsTest.java @@ -37,7 +37,6 @@ import org.apache.sis.geometries.curve.LinearRing; import org.apache.sis.geometries.surface.Polygon; import org.apache.sis.maths.Vector2D; import org.apache.sis.maths.NDArrays; -import org.apache.sis.maths.Vectors; import org.apache.sis.maths.Vector; import org.apache.sis.referencing.GeodeticCalculator; import org.apache.sis.referencing.CommonCRS; @@ -192,15 +191,15 @@ public abstract class AbstractDggrsTest { final Vector corner = ps.getPosition(i); calculator.setStartPoint(center); - calculator.setEndPoint(Vectors.asDirectPostion(corner, ps.getCoordinateReferenceSystem())); + calculator.setEndPoint(corner.asDirectPostion(ps.getCoordinateReferenceSystem())); double distance = calculator.getGeodesicDistance(); calculator.setGeodesicDistance(distance * 0.99); final Vector2D.Double candidateInside = new Vector2D.Double(calculator.getEndPoint().getCoordinates()); calculator.setGeodesicDistance(distance * 1.01); final Vector2D.Double candidateOutside = new Vector2D.Double(calculator.getEndPoint().getCoordinates()); - final String candidateInsideHash = coder.encode(Vectors.asDirectPostion(candidateInside)); - final String candidateOutsideHash = coder.encode(Vectors.asDirectPostion(candidateOutside)); + final String candidateInsideHash = coder.encode(candidateInside.asDirectPostion(null)); + final String candidateOutsideHash = coder.encode(candidateOutside.asDirectPostion(null)); System.out.println(z.getGeographicExtent().toString()); System.out.println(candidateInside + " " +candidateOutside); System.out.println(candidateInsideHash + " " +candidateOutsideHash);
