You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2019/02/12 15:50:42 UTC
[sis] 02/04: Add a Geometries.formatWKT(…) method.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit 6feea3d54a21dc165b194d1568f0f8aad6b43f47
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue Feb 12 12:07:09 2019 +0100
Add a Geometries.formatWKT(…) method.
---
.../java/org/apache/sis/internal/feature/ESRI.java | 13 ++
.../apache/sis/internal/feature/Geometries.java | 24 +++
.../java/org/apache/sis/internal/feature/JTS.java | 8 +
.../org/apache/sis/internal/feature/Java2D.java | 13 ++
.../sis/internal/feature/j2d/ShapeProperties.java | 167 +++++++++++++++++++++
.../sis/internal/feature}/j2d/package-info.java | 15 +-
.../org/apache/sis/internal/feature/ESRITest.java | 16 ++
.../sis/internal/feature/GeometriesTestCase.java | 22 +++
.../internal/feature/j2d/ShapePropertiesTest.java | 66 ++++++++
.../apache/sis/test/suite/FeatureTestSuite.java | 1 +
.../sis/internal/referencing/j2d/package-info.java | 7 +-
11 files changed, 343 insertions(+), 9 deletions(-)
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/ESRI.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/ESRI.java
index 73ab775..e46b73c 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/ESRI.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/ESRI.java
@@ -26,7 +26,9 @@ import com.esri.core.geometry.Point;
import com.esri.core.geometry.Point2D;
import com.esri.core.geometry.Point3D;
import com.esri.core.geometry.WktImportFlags;
+import com.esri.core.geometry.WktExportFlags;
import com.esri.core.geometry.OperatorImportFromWkt;
+import com.esri.core.geometry.OperatorExportToWkt;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.setup.GeometryLibrary;
import org.apache.sis.math.Vector;
@@ -199,4 +201,15 @@ final class ESRI extends Geometries<Geometry> {
public Object parseWKT(final String wkt) {
return OperatorImportFromWkt.local().execute(WktImportFlags.wktImportDefaults, Geometry.Type.Unknown, wkt, null);
}
+
+ /**
+ * If the given object is an ESRI geometry, returns its WKT representation.
+ */
+ @Override
+ final String tryFormatWKT(final Object geometry, final double flatness) {
+ if (geometry instanceof Geometry) {
+ return OperatorExportToWkt.local().execute(WktExportFlags.wktExportDefaults, (Geometry) geometry, null);
+ }
+ return null;
+ }
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java
index 0c83667..c6950d7 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Geometries.java
@@ -223,6 +223,30 @@ public abstract class Geometries<G> {
}
/**
+ * If the given object is one of the recognized types, formats that object in Well Known Text (WKT).
+ * Otherwise returns {@code null}. If the geometry contains curves, then the {@code flatness} parameter
+ * specifies the maximum distance that the line segments used in the Well Known Text are allowed to deviate
+ * from any point on the original curve. This parameter is ignored if the geometry does not contain curves.
+ *
+ * @param geometry the geometry to format in Well Known Text.
+ * @param flatness maximal distance between the approximated WKT and any point on the curve.
+ * @return the Well Known Text for the given geometry, or {@code null} if the given object is unrecognized.
+ */
+ public static String formatWKT(Object geometry, double flatness) {
+ for (Geometries<?> g = implementation; g != null; g = g.fallback) {
+ String wkt = g.tryFormatWKT(geometry, flatness);
+ if (wkt != null) return wkt;
+ }
+ return null;
+ }
+
+ /**
+ * If the given geometry is the type supported by this {@code Geometries} instance,
+ * returns its WKT representation. Otherwise returns {@code null}.
+ */
+ abstract String tryFormatWKT(Object geometry, double flatness);
+
+ /**
* Parses the given WKT.
*
* @param wkt the WKT to parse.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java
index 9234435..4636cea 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java
@@ -74,6 +74,14 @@ final class JTS extends Geometries<Geometry> {
}
/**
+ * If the given object is a JTS geometry, returns its WKT representation.
+ */
+ @Override
+ final String tryFormatWKT(final Object geometry, final double flatness) {
+ return (geometry instanceof Geometry) ? ((Geometry) geometry).toText() : null;
+ }
+
+ /**
* If the given object is a JTS geometry, returns a short string representation of the class name.
*/
@Override
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Java2D.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Java2D.java
index 0febd47..5734ca7 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Java2D.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/Java2D.java
@@ -24,6 +24,7 @@ import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.setup.GeometryLibrary;
+import org.apache.sis.internal.feature.j2d.ShapeProperties;
import org.apache.sis.internal.referencing.j2d.ShapeUtilities;
import org.apache.sis.math.Vector;
import org.apache.sis.util.Classes;
@@ -214,6 +215,18 @@ final class Java2D extends Geometries<Shape> {
}
/**
+ * If the given object is a Java2D shape, builds its WKT representation.
+ * Current implementation assumes that all closed shapes are polygons and that polygons have no hole
+ * (i.e. if a polygon is followed by more data, this method assumes that the additional data is a disjoint polygon).
+ *
+ * @see <a href="https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry">Well-known text on Wikipedia</a>
+ */
+ @Override
+ final String tryFormatWKT(final Object geometry, final double flatness) {
+ return (geometry instanceof Shape) ? new ShapeProperties((Shape) geometry).toWKT(flatness) : null;
+ }
+
+ /**
* Parses the given WKT.
*/
@Override
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/ShapeProperties.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/ShapeProperties.java
new file mode 100644
index 0000000..ecfcfc4
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/ShapeProperties.java
@@ -0,0 +1,167 @@
+/*
+ * 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.internal.feature.j2d;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.awt.Shape;
+import java.awt.geom.PathIterator;
+import java.awt.geom.IllegalPathStateException;
+import org.apache.sis.util.StringBuilders;
+
+
+/**
+ * Provides some information about a {@link Shape} object.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since 1.0
+ * @module
+ */
+public final class ShapeProperties {
+ /**
+ * The geometry.
+ */
+ private final Shape geometry;
+
+ /**
+ * {@code true} if last call to {@link #coordinates(double)} found only closed shapes.
+ * In such case, the shapes are presumed polygons. Note that we do not verify if some
+ * polygons are actually holes inside other polygons.
+ */
+ private boolean isPolygon;
+
+ /**
+ * Creates a new inspector for the given geometry.
+ *
+ * @param geometry the shape for which to inspect properties.
+ */
+ public ShapeProperties(final Shape geometry) {
+ this.geometry = geometry;
+ }
+
+ /**
+ * Appends the two first coordinates of {@code source} at the end of {@code target}, expanding the target array if necessary.
+ *
+ * @param source array of coordinates to add. Only the two first values are used.
+ * @param target where to add the two coordinates.
+ * @param index index in {@code target} where to add the two coordinates.
+ * @return {@code target}, possible as a new array if it was necessary to expand it.
+ */
+ private static double[] addPoint(final double[] source, double[] target, final int index) {
+ if (index >= target.length) {
+ target = Arrays.copyOf(target, index*2);
+ }
+ System.arraycopy(source, 0, target, index, 2);
+ return target;
+ }
+
+ /**
+ * Returns coordinates of the given geometry as a list of (<var>x</var>,<var>y</var>) tuples in {@code double[]} arrays.
+ * This method guarantees that all arrays have at least 2 points (4 coordinates). It should be invoked only for small or
+ * medium shapes. For large shapes, the path iterator should be used directly without copy to arrays.
+ *
+ * @param flatness maximal distance between the approximated segments and any point on the curve.
+ * @return coordinate tuples. They are presumed polygons if {@link #isPolygon} is {@code true}.
+ */
+ private List<double[]> coordinates(final double flatness) {
+ final List<double[]> polylines = new ArrayList<>();
+ isPolygon = true;
+ double[] polyline = new double[10];
+ final double[] coords = new double[6];
+ int i = 0;
+ for (final PathIterator it = geometry.getPathIterator(null, flatness); !it.isDone(); it.next()) {
+ switch (it.currentSegment(coords)) {
+ case PathIterator.SEG_MOVETO: {
+ if (i > 2) {
+ isPolygon = false; // MOVETO without CLOSE: this is a linestring instead than a polygon.
+ polylines.add(Arrays.copyOf(polyline, i));
+ }
+ System.arraycopy(coords, 0, polyline, 0, 2);
+ i = 2;
+ break;
+ }
+ case PathIterator.SEG_LINETO: {
+ polyline = addPoint(coords, polyline, i);
+ i += 2;
+ break;
+ }
+ case PathIterator.SEG_CLOSE: {
+ if (i > 2) {
+ if (polyline[0] != polyline[i-2] || polyline[1] != polyline[i-1]) {
+ polyline = addPoint(polyline, polyline, i);
+ i += 2;
+ }
+ polylines.add(Arrays.copyOf(polyline, i));
+ }
+ i = 0;
+ break;
+ }
+ default: throw new IllegalPathStateException();
+ }
+ }
+ if (i > 2) {
+ isPolygon = false; // LINETO without CLOSE: this is a linestring instead than a polygon.
+ polylines.add(Arrays.copyOf(polyline, i));
+ }
+ return polylines;
+ }
+
+ /**
+ * Returns a WKT representation of the geometry.
+ * Current implementation assumes that all closed shapes are polygons and that polygons have no hole
+ * (i.e. if a polygon is followed by more data, this method assumes that the additional data is a disjoint polygon).
+ *
+ * @param flatness maximal distance between the approximated segments and any point on the curve.
+ * @return Well Known Text representation of the geometry.
+ *
+ * @see <a href="https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry">Well-known text on Wikipedia</a>
+ */
+ public String toWKT(final double flatness) {
+ final List<double[]> polylines = coordinates(flatness);
+ final boolean isMulti;
+ switch (polylines.size()) {
+ case 0: return "POLYGON EMPTY";
+ case 1: isMulti = false; break;
+ default: isMulti = true; break;
+ }
+ final StringBuilder buffer = new StringBuilder(80);
+ if (isMulti) {
+ buffer.append("MULTI");
+ }
+ buffer.append(isPolygon ? "POLYGON" : "LINESTRING").append(' ');
+ if (isMulti) buffer.append('(');
+ for (int j=0; j<polylines.size(); j++) {
+ final double[] polyline = polylines.get(j);
+ if (j != 0) buffer.append(", ");
+ buffer.append('(');
+ if (isPolygon) buffer.append('(');
+ for (int i=0; i<polyline.length; i++) {
+ if (i != 0) {
+ if ((i & 1) == 0) buffer.append(',');
+ buffer.append(' ');
+ }
+ StringBuilders.trimFractionalPart(buffer.append(polyline[i]));
+ }
+ if (isPolygon) buffer.append(')');
+ buffer.append(')');
+ }
+ if (isMulti) buffer.append(')');
+ return buffer.toString();
+ }
+}
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/package-info.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/package-info.java
similarity index 76%
copy from core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/package-info.java
copy to core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/package-info.java
index bf5e5bd..747ae56 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/package-info.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/j2d/package-info.java
@@ -16,18 +16,19 @@
*/
/**
- * A set of helper classes having a dependency to Java2D.
- * We keep those classes in a separated package for making easier to identify
- * which parts may need to be replaced in a JavaFX applications.
+ * Methods specific to the Java2D.
*
- * <strong>Do not use!</strong>
+ * <STRONG>Do not use!</STRONG>
*
* This package is for internal use by SIS only. Classes in this package
* may change in incompatible ways in any future version without notice.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 0.3
- * @since 0.3
+ * @version 1.0
+ *
+ * @see org.apache.sis.internal.referencing.j2d
+ *
+ * @since 1.0
* @module
*/
-package org.apache.sis.internal.referencing.j2d;
+package org.apache.sis.internal.feature.j2d;
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/ESRITest.java b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/ESRITest.java
index 2f749d0..62ae691 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/ESRITest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/ESRITest.java
@@ -17,6 +17,7 @@
package org.apache.sis.internal.feature;
import com.esri.core.geometry.Polyline;
+import org.apache.sis.util.StringBuilders;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -59,4 +60,19 @@ public final strictfp class ESRITest extends GeometriesTestCase {
final Polyline poly = (Polyline) geometry;
assertEquals("pathCount", 3, poly.getPathCount());
}
+
+ /**
+ * Verifies that a WKT is equal to the expected one. This method modifies the expected WKT
+ * by transforming single geometries into multi-geometries, since the ESRI library formats
+ * geometries that way (at least with the objects that we use).
+ */
+ @Override
+ void assertWktEquals(String expected, final String actual) {
+ assertTrue(actual.startsWith("MULTI"));
+ final StringBuilder b = new StringBuilder(expected.length() + 7).append("MULTI").append(expected);
+ StringBuilders.replace(b, "(", "((");
+ StringBuilders.replace(b, ")", "))");
+ expected = b.toString();
+ super.assertWktEquals(expected, actual);
+ }
}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/GeometriesTestCase.java b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/GeometriesTestCase.java
index eaf0608..cbef788 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/GeometriesTestCase.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/GeometriesTestCase.java
@@ -20,6 +20,7 @@ import java.util.Arrays;
import java.util.Iterator;
import org.apache.sis.math.Vector;
import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.test.DependsOnMethod;
import org.apache.sis.test.TestCase;
import org.junit.Test;
@@ -119,4 +120,25 @@ public abstract strictfp class GeometriesTestCase extends TestCase {
assertEquals("xmax", 15, env.getUpper(0), STRICT);
assertEquals("ymax", 12, env.getUpper(1), STRICT);
}
+
+ /**
+ * Tests {@link Geometries#tryFormatWKT(Object, double)}.
+ */
+ @Test
+ @DependsOnMethod("testCreatePolyline")
+ public void testTryFormatWKT() {
+ geometry = factory.createPolyline(2, Vector.create(new double[] {4,5, 7,9, 9,3, -1,-6}));
+ final String text = factory.tryFormatWKT(geometry, 0);
+ assertNotNull(text);
+ assertWktEquals("LINESTRING (4 5, 7 9, 9 3, -1 -6)", text);
+ }
+
+ /**
+ * Verifies that a WKT is equal to the expected one. If the actual WKT is a multi-lines or multi-polygons,
+ * then this method may modify the expected WKT accordingly. This adjustment is done for the ESRI case by
+ * overriding this method.
+ */
+ void assertWktEquals(String expected, final String actual) {
+ assertEquals(expected, actual);
+ }
}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/j2d/ShapePropertiesTest.java b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/j2d/ShapePropertiesTest.java
new file mode 100644
index 0000000..367b833
--- /dev/null
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/j2d/ShapePropertiesTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.internal.feature.j2d;
+
+import java.awt.geom.Line2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Rectangle2D;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests {@link ShapeProperties}.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since 1.0
+ * @module
+ */
+public final strictfp class ShapePropertiesTest extends TestCase {
+ /**
+ * Tests with a line segment.
+ */
+ @Test
+ public void testLinestring() {
+ ShapeProperties p = new ShapeProperties(new Line2D.Double(10, 20, 50, 45));
+ assertEquals("LINESTRING (10 20, 50 45)", p.toWKT(0.1));
+ }
+
+ /**
+ * Tests with a rectangle.
+ */
+ @Test
+ public void testPolygon() {
+ ShapeProperties p = new ShapeProperties(new Rectangle2D.Double(-10, -20, 30, 50));
+ assertEquals("POLYGON ((-10 -20, 20 -20, 20 30, -10 30, -10 -20))", p.toWKT(0.1));
+ }
+
+ /**
+ * Tests with a two rectangles.
+ */
+ @Test
+ public void testMultiPolygon() {
+ Path2D.Double path = new Path2D.Double();
+ path.append(new Rectangle2D.Double(-1, -2, 3, 6), false);
+ path.append(new Rectangle2D.Double(5, 6, 2, 1), false);
+ ShapeProperties p = new ShapeProperties(path);
+ assertEquals("MULTIPOLYGON (((-1 -2, 2 -2, 2 4, -1 4, -1 -2)), ((5 6, 7 6, 7 7, 5 7, 5 6)))", p.toWKT(0.1));
+ }
+}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
index 48a59a6..c54f1c8 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
@@ -61,6 +61,7 @@ import org.junit.BeforeClass;
org.apache.sis.filter.DefaultMultiplyTest.class,
org.apache.sis.filter.DefaultSubtractTest.class,
org.apache.sis.internal.feature.AttributeConventionTest.class,
+ org.apache.sis.internal.feature.j2d.ShapePropertiesTest.class,
org.apache.sis.internal.feature.Java2DTest.class,
org.apache.sis.internal.feature.ESRITest.class,
org.apache.sis.internal.feature.JTSTest.class,
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/package-info.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/package-info.java
index bf5e5bd..b20274d 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/package-info.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/package-info.java
@@ -26,8 +26,11 @@
* may change in incompatible ways in any future version without notice.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 0.3
- * @since 0.3
+ * @version 0.8
+ *
+ * @see org.apache.sis.internal.feature.j2d
+ *
+ * @since 0.3
* @module
*/
package org.apache.sis.internal.referencing.j2d;