sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 02/04: Add a Geometries.formatWKT(…) method.
Date Tue, 12 Feb 2019 15:50:42 GMT
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 <martin.desruisseaux@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;


Mime
View raw message