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:44 UTC
[sis] 04/04: Preserve the single-precision versus double-precision
nature of Shape instances when possible.
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 50fe4696ab1e2c30f89b5b2421e6d9164e949c96
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue Feb 12 15:54:27 2019 +0100
Preserve the single-precision versus double-precision nature of Shape instances when possible.
---
.../java/org/apache/sis/internal/feature/ESRI.java | 44 ++++----
.../java/org/apache/sis/internal/feature/JTS.java | 48 ++++-----
.../org/apache/sis/internal/feature/Java2D.java | 65 ++++++------
.../sis/internal/feature/j2d/ShapeProperties.java | 111 +++++++++++++++++++--
.../internal/referencing/j2d/ShapeUtilities.java | 32 ++++--
.../sis/internal/referencing/j2d/package-info.java | 2 +-
.../referencing/j2d/ShapeUtilitiesTest.java | 13 ++-
7 files changed, 221 insertions(+), 94 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 e46b73c..b7acd42 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
@@ -124,8 +124,7 @@ final class ESRI extends Geometries<Geometry> {
/**
* Creates a polyline from the given ordinate values.
- * Each {@link Double#NaN} ordinate value start a new path.
- * The implementation returned by this method must be an instance of {@link #rootClass}.
+ * Each {@link Double#NaN} ordinate value starts a new path.
*/
@Override
public Geometry createPolyline(final int dimension, final Vector... ordinates) {
@@ -166,30 +165,31 @@ final class ESRI extends Geometries<Geometry> {
}
final Polyline path = new Polyline();
boolean lineTo = false;
- for (;; next = polylines.next()) {
- if (next != null) {
- if (next instanceof Point) {
- final Point pt = (Point) next;
- if (pt.isEmpty()) {
- lineTo = false;
+add: for (;;) {
+ if (next instanceof Point) {
+ final Point pt = (Point) next;
+ if (pt.isEmpty()) {
+ lineTo = false;
+ } else {
+ final double x = ((Point) next).getX();
+ final double y = ((Point) next).getY();
+ if (lineTo) {
+ path.lineTo(x, y);
} else {
- final double x = ((Point) next).getX();
- final double y = ((Point) next).getY();
- if (lineTo) {
- path.lineTo(x, y);
- } else {
- path.startPath(x, y);
- lineTo = true;
- }
+ path.startPath(x, y);
+ lineTo = true;
}
- } else {
- path.add((MultiPath) next, false);
- lineTo = false;
}
+ } else {
+ path.add((MultiPath) next, false);
+ lineTo = false;
}
- if (!polylines.hasNext()) { // Should be part of the 'for' instruction, but we need
- break; // to skip this condition during the first iteration.
- }
+ /*
+ * 'polylines.hasNext()' check is conceptually part of 'for' instruction,
+ * except that we need to skip this condition during the first iteration.
+ */
+ do if (!polylines.hasNext()) break add;
+ while ((next = polylines.next()) == null);
}
return path;
}
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 4636cea..39c998f 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
@@ -150,8 +150,7 @@ final class JTS extends Geometries<Geometry> {
/**
* Creates a polyline from the given ordinate values.
- * Each {@link Double#NaN} ordinate value start a new path.
- * The implementation returned by this method must be an instance of {@link #rootClass}.
+ * Each {@link Double#NaN} ordinate value starts a new path.
*
* @return the geometric object for the given points.
*/
@@ -232,34 +231,35 @@ final class JTS extends Geometries<Geometry> {
}
final List<Coordinate> coordinates = new ArrayList<>();
final List<LineString> lines = new ArrayList<>();
- for (;; next = polylines.next()) {
- if (next != null) {
- if (next instanceof Point) {
- final Coordinate pt = ((Point) next).getCoordinate();
- if (!Double.isNaN(pt.x) && !Double.isNaN(pt.y)) {
- coordinates.add(pt);
+add: for (;;) {
+ if (next instanceof Point) {
+ final Coordinate pt = ((Point) next).getCoordinate();
+ if (!Double.isNaN(pt.x) && !Double.isNaN(pt.y)) {
+ coordinates.add(pt);
+ } else {
+ toLineString(coordinates, lines);
+ coordinates.clear();
+ }
+ } else {
+ final Geometry g = (Geometry) next;
+ final int n = g.getNumGeometries();
+ for (int i=0; i<n; i++) {
+ final LineString ls = (LineString) g.getGeometryN(i);
+ if (coordinates.isEmpty()) {
+ lines.add(ls);
} else {
+ coordinates.addAll(Arrays.asList(ls.getCoordinates()));
toLineString(coordinates, lines);
coordinates.clear();
}
- } else {
- final Geometry g = (Geometry) next;
- final int n = g.getNumGeometries();
- for (int i=0; i<n; i++) {
- final LineString ls = (LineString) g.getGeometryN(i);
- if (coordinates.isEmpty()) {
- lines.add(ls);
- } else {
- coordinates.addAll(Arrays.asList(ls.getCoordinates()));
- toLineString(coordinates, lines);
- coordinates.clear();
- }
- }
}
}
- if (!polylines.hasNext()) { // Should be part of the 'for' instruction, but we need
- break; // to skip this condition during the first iteration.
- }
+ /*
+ * 'polylines.hasNext()' check is conceptually part of 'for' instruction,
+ * except that we need to skip this condition during the first iteration.
+ */
+ do if (!polylines.hasNext()) break add;
+ while ((next = polylines.next()) == null);
}
toLineString(coordinates, lines);
return toGeometry(lines);
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 5734ca7..7691b60 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
@@ -28,6 +28,7 @@ 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;
+import org.apache.sis.util.Numbers;
/**
@@ -36,7 +37,7 @@ import org.apache.sis.util.Classes;
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
* @since 0.7
* @module
*/
@@ -111,8 +112,9 @@ final class Java2D extends Geometries<Shape> {
/**
* Creates a path from the given ordinate values.
- * Each {@link Double#NaN} ordinate value start a new path.
- * The implementation returned by this method must be an instance of {@link #rootClass}.
+ * Each {@link Double#NaN} ordinate value starts a new path.
+ * The geometry may be backed by {@code float} or {@code double} primitive type,
+ * depending on the type used by the given vectors.
*/
@Override
public Shape createPolyline(final int dimension, final Vector... ordinates) {
@@ -120,8 +122,8 @@ final class Java2D extends Geometries<Shape> {
throw unsupported(dimension);
}
/*
- * Computes the total length of all vectors and verifies if all values
- * can be casted to float without precision lost.
+ * Computes the total length of all vectors and verifies if any vector
+ * requires double-precision numbers instead of single-precision.
*/
int length = 0;
boolean isFloat = true;
@@ -129,13 +131,7 @@ final class Java2D extends Geometries<Shape> {
if (v != null) {
length = Math.addExact(length, v.size());
if (isFloat) {
- for (int i=v.size(); --i >= 0;) {
- final double value = v.doubleValue(i);
- if (Double.doubleToRawLongBits(value) != Double.doubleToRawLongBits((float) value)) {
- isFloat = false;
- break;
- }
- }
+ isFloat = Numbers.getEnumConstant(v.getElementType()) <= Numbers.FLOAT;
}
}
}
@@ -187,28 +183,37 @@ final class Java2D extends Geometries<Shape> {
if (!(next instanceof Shape || next instanceof Point2D)) {
return null;
}
- final Path2D path = new Path2D.Double();
+ boolean isFloat = ShapeUtilities.isFloat(next);
+ Path2D path = isFloat ? new Path2D.Float() : new Path2D.Double();
boolean lineTo = false;
- for (;; next = polylines.next()) {
- if (next != null) {
- if (next instanceof Point2D) {
- final double x = ((Point2D) next).getX();
- final double y = ((Point2D) next).getY();
- if (Double.isNaN(x) || Double.isNaN(y)) {
- lineTo = false;
- } else if (lineTo) {
- path.lineTo(x, y);
- } else {
- path.moveTo(x, y);
- lineTo = true;
- }
- } else {
- path.append((Shape) next, false);
+add: for (;;) {
+ if (next instanceof Point2D) {
+ final double x = ((Point2D) next).getX();
+ final double y = ((Point2D) next).getY();
+ if (Double.isNaN(x) || Double.isNaN(y)) {
lineTo = false;
+ } else if (lineTo) {
+ path.lineTo(x, y);
+ } else {
+ path.moveTo(x, y);
+ lineTo = true;
}
+ } else {
+ path.append((Shape) next, false);
+ lineTo = false;
}
- if (!polylines.hasNext()) { // Should be part of the 'for' instruction, but we need
- break; // to skip this condition during the first iteration.
+ /*
+ * 'polylines.hasNext()' check is conceptually part of 'for' instruction,
+ * except that we need to skip this condition during the first iteration.
+ */
+ do if (!polylines.hasNext()) break add;
+ while ((next = polylines.next()) == null);
+ /*
+ * Convert the path from single-precision to double-precision if needed.
+ */
+ if (isFloat && !ShapeUtilities.isFloat(next)) {
+ path = new Path2D.Double(path);
+ isFloat = false;
}
}
return ShapeUtilities.toPrimitive(path);
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
index ecfcfc4..dcc2a0d 100644
--- 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
@@ -19,9 +19,11 @@ package org.apache.sis.internal.feature.j2d;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
+import java.lang.reflect.Array;
import java.awt.Shape;
import java.awt.geom.PathIterator;
import java.awt.geom.IllegalPathStateException;
+import org.apache.sis.internal.referencing.j2d.ShapeUtilities;
import org.apache.sis.util.StringBuilders;
@@ -72,20 +74,102 @@ public final class ShapeProperties {
}
/**
- * 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.
+ * Same as {@link #addPoint(double[], double[], int)} but for single-precision numbers.
+ */
+ private static float[] addPoint(final float[] source, float[] 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 float[]}
+ * or {@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<>();
+ private List<?> coordinates(final double flatness) {
+ final PathIterator it = geometry.getPathIterator(null, flatness);
isPolygon = true;
+ if (ShapeUtilities.isFloat(geometry)) {
+ return coordinatesAsFloats(it);
+ } else {
+ return coordinatesAsDoubles(it);
+ }
+ }
+
+ /**
+ * {@link #coordinates(double)} implementation for the double-precision case.
+ * The {@link #isPolygon} field needs to be set before to invoke this method.
+ */
+ private List<double[]> coordinatesAsDoubles(final PathIterator it) {
+ final List<double[]> polylines = new ArrayList<>();
double[] polyline = new double[10];
final double[] coords = new double[6];
+ /*
+ * Double-precision variant of this method. Source code below is identical to the single-precision variant,
+ * but the methods invoked are different because of method overloading. Trying to have a common code is too
+ * complex (too many code are different despite looking the same).
+ */
+ int i = 0;
+ while (!it.isDone()) {
+ 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();
+ }
+ it.next();
+ }
+ if (i > 2) {
+ isPolygon = false; // LINETO without CLOSE: this is a linestring instead than a polygon.
+ polylines.add(Arrays.copyOf(polyline, i));
+ }
+ return polylines;
+ }
+
+ /**
+ * {@link #coordinates(double)} implementation for the single-precision case.
+ * The {@link #isPolygon} field needs to be set before to invoke this method.
+ */
+ private List<float[]> coordinatesAsFloats(final PathIterator it) {
+ final List<float[]> polylines = new ArrayList<>();
+ float[] polyline = new float[10];
+ final float[] coords = new float[6];
+ /*
+ * Single-precision variant of this method. Source code below is identical to the double-precision variant,
+ * but the methods invoked are different because of method overloading. Trying to have a common code is too
+ * complex (too many code are different despite looking the same).
+ */
int i = 0;
- for (final PathIterator it = geometry.getPathIterator(null, flatness); !it.isDone(); it.next()) {
+ while (!it.isDone()) {
switch (it.currentSegment(coords)) {
case PathIterator.SEG_MOVETO: {
if (i > 2) {
@@ -114,6 +198,7 @@ public final class ShapeProperties {
}
default: throw new IllegalPathStateException();
}
+ it.next();
}
if (i > 2) {
isPolygon = false; // LINETO without CLOSE: this is a linestring instead than a polygon.
@@ -133,7 +218,7 @@ public final class ShapeProperties {
* @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 List<?> polylines = coordinates(flatness);
final boolean isMulti;
switch (polylines.size()) {
case 0: return "POLYGON EMPTY";
@@ -147,16 +232,22 @@ public final class ShapeProperties {
buffer.append(isPolygon ? "POLYGON" : "LINESTRING").append(' ');
if (isMulti) buffer.append('(');
for (int j=0; j<polylines.size(); j++) {
- final double[] polyline = polylines.get(j);
+ final Object polyline = polylines.get(j);
if (j != 0) buffer.append(", ");
buffer.append('(');
if (isPolygon) buffer.append('(');
- for (int i=0; i<polyline.length; i++) {
+ final int length = Array.getLength(polyline);
+ for (int i=0; i<length; i++) {
if (i != 0) {
if ((i & 1) == 0) buffer.append(',');
buffer.append(' ');
}
- StringBuilders.trimFractionalPart(buffer.append(polyline[i]));
+ if (polyline instanceof double[]) {
+ buffer.append(((double[]) polyline)[i]);
+ } else {
+ buffer.append(((float[]) polyline)[i]);
+ }
+ StringBuilders.trimFractionalPart(buffer);
}
if (isPolygon) buffer.append(')');
buffer.append(')');
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/ShapeUtilities.java b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/ShapeUtilities.java
index 6478f98..49a1f7b 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/ShapeUtilities.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/j2d/ShapeUtilities.java
@@ -32,7 +32,7 @@ import static java.lang.Math.*;
* Static methods operating on shapes from the {@link java.awt.geom} package.
*
* @author Martin Desruisseaux (MPO, IRD, Geomatys)
- * @version 0.5
+ * @version 1.0
* @since 0.5
* @module
*/
@@ -336,7 +336,7 @@ public final class ShapeUtilities extends Static {
y2 = (px*rx2 + py*ry2) / x2; // use 'y2' as a temporary variable for 'x1'
py = (py*rx2 - px*ry2) / x2;
px = y2;
- y2 = 0; // set as a matter of principle (but not used).
+// y2 = 0; // Could be set to that value, but not used.
/*
* Now compute the control point coordinates in our new coordinate system axis.
*/
@@ -405,10 +405,18 @@ public final class ShapeUtilities extends Static {
final int code = it.currentSegment(buffer);
it.next();
if (it.isDone()) {
- switch (code) {
- case PathIterator.SEG_LINETO: return new Line2D.Double(x1,y1, buffer[0], buffer[1]);
- case PathIterator.SEG_QUADTO: return new QuadCurve2D.Double(x1,y1, buffer[0], buffer[1], buffer[2], buffer[3]);
- case PathIterator.SEG_CUBICTO: return new CubicCurve2D.Double(x1,y1, buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5]);
+ if (isFloat(path)) {
+ switch (code) {
+ case PathIterator.SEG_LINETO: return new Line2D.Float((float) x1, (float) y1, (float) buffer[0], (float) buffer[1]);
+ case PathIterator.SEG_QUADTO: return new QuadCurve2D.Float((float) x1, (float) y1, (float) buffer[0], (float) buffer[1], (float) buffer[2], (float) buffer[3]);
+ case PathIterator.SEG_CUBICTO: return new CubicCurve2D.Float((float) x1, (float) y1, (float) buffer[0], (float) buffer[1], (float) buffer[2], (float) buffer[3], (float) buffer[4], (float) buffer[5]);
+ }
+ } else {
+ switch (code) {
+ case PathIterator.SEG_LINETO: return new Line2D.Double(x1,y1, buffer[0], buffer[1]);
+ case PathIterator.SEG_QUADTO: return new QuadCurve2D.Double(x1,y1, buffer[0], buffer[1], buffer[2], buffer[3]);
+ case PathIterator.SEG_CUBICTO: return new CubicCurve2D.Double(x1,y1, buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5]);
+ }
}
}
}
@@ -416,4 +424,16 @@ public final class ShapeUtilities extends Static {
}
return path;
}
+
+ /**
+ * Returns {@code true} if the given shape is presumed backed by primitive {@code float} values.
+ * The given object should be an instance of {@link Shape} or {@link Point2D}.
+ * This method use heuristic rules based on class name used in Java2D library.
+ *
+ * @param path the shape for which to determine the backing primitive type.
+ * @return {@code true} if the given shape is presumed backed by {@code float} type.
+ */
+ public static boolean isFloat(final Object path) {
+ return path.getClass().getSimpleName().equals("Float");
+ }
}
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 b20274d..1df2079 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,7 +26,7 @@
* may change in incompatible ways in any future version without notice.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
*
* @see org.apache.sis.internal.feature.j2d
*
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/j2d/ShapeUtilitiesTest.java b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/j2d/ShapeUtilitiesTest.java
index 3f0c800..bb63bdf 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/j2d/ShapeUtilitiesTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/j2d/ShapeUtilitiesTest.java
@@ -33,7 +33,7 @@ import static org.opengis.test.Assert.*;
* Values in this test were determined empirically by running {@link ShapeUtilitiesViewer}.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 0.5
+ * @version 1.0
* @since 0.5
* @module
*/
@@ -166,4 +166,15 @@ public final strictfp class ShapeUtilitiesTest extends TestCase {
assertEquals("CtrlP2", new Point2D.Double(8, 6), ((CubicCurve2D) p).getCtrlP2());
assertEquals("P2", new Point2D.Double(9, 4), ((CubicCurve2D) p).getP2());
}
+
+ /**
+ * Tests {@link ShapeUtilities#isFloat(Object)}.
+ */
+ @Test
+ public void test() {
+ assertTrue (ShapeUtilities.isFloat(new Point2D.Float()));
+ assertFalse(ShapeUtilities.isFloat(new Point2D.Double()));
+ assertTrue (ShapeUtilities.isFloat(new Line2D.Float()));
+ assertFalse(ShapeUtilities.isFloat(new Line2D.Double()));
+ }
}