From 6da579a4f20611c7961a35ddf5f72a719c0f063b Mon Sep 17 00:00:00 2001 From: Martin Davis Date: Thu, 27 Aug 2020 15:06:00 -0700 Subject: [PATCH 01/10] Improve TestBuilder palette behaviour Signed-off-by: Martin Davis --- .../locationtech/jtstest/testbuilder/ui/style/Palette.java | 4 +--- .../main/java/org/locationtech/jtstest/util/HSBPalette.java | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/app/src/main/java/org/locationtech/jtstest/testbuilder/ui/style/Palette.java b/modules/app/src/main/java/org/locationtech/jtstest/testbuilder/ui/style/Palette.java index a59a737e11..59b071e90d 100644 --- a/modules/app/src/main/java/org/locationtech/jtstest/testbuilder/ui/style/Palette.java +++ b/modules/app/src/main/java/org/locationtech/jtstest/testbuilder/ui/style/Palette.java @@ -12,11 +12,9 @@ public class Palette { public static final int TYPE_RAINBOW = 3; public static final int TYPE_RAINBOW_RANDOM = 4; - private static final HSBPalette PAL_RAINBOW_INCREMENTAL = HSBPalette.createRainbowIncremental(0.396f, 0.4f, 1); - public static HSBPalette customPalette(int paletteType, Color clrBase, int numHues) { HSBPalette pal = null; - float sat = 0.6f; //ColorUtil.getSaturation(clrBase); + float sat = ColorUtil.getSaturation(clrBase); float bright = ColorUtil.getBrightness(clrBase); if (TYPE_VARY == paletteType) { float hue = ColorUtil.getHue(clrBase); diff --git a/modules/app/src/main/java/org/locationtech/jtstest/util/HSBPalette.java b/modules/app/src/main/java/org/locationtech/jtstest/util/HSBPalette.java index 2a9e5858b4..363a94ea46 100644 --- a/modules/app/src/main/java/org/locationtech/jtstest/util/HSBPalette.java +++ b/modules/app/src/main/java/org/locationtech/jtstest/util/HSBPalette.java @@ -6,8 +6,10 @@ public class HSBPalette { + private static final float HUE_WRAP_MAX = 1 - 0.08333f; + public static HSBPalette createRainbow(int numHue, float s, float b) { - return new HSBPalette(numHue, 0, 1 - 0.08333f, + return new HSBPalette(numHue, 0, HUE_WRAP_MAX, 1, s, s, 1, b, b ); From 23de3b4022c246945ee51a1f05483ae5d7fd8b29 Mon Sep 17 00:00:00 2001 From: Jody Garnett Date: Thu, 27 Aug 2020 15:13:01 -0700 Subject: [PATCH 02/10] Remove enforceArrayConsistency from CoordianteSequenceArray constructor (#584) Utility method provided for client code working with inconsistent content in the same array Signed-off-by: Jody Garnett --- .../jts/geom/CoordinateArrays.java | 99 +++++++++++++++++++ .../locationtech/jts/geom/Coordinates.java | 2 +- .../geom/impl/CoordinateArraySequence.java | 51 ++-------- .../jts/geom/CoordinateArraysTest.java | 25 +++++ 4 files changed, 131 insertions(+), 46 deletions(-) diff --git a/modules/core/src/main/java/org/locationtech/jts/geom/CoordinateArrays.java b/modules/core/src/main/java/org/locationtech/jts/geom/CoordinateArrays.java index 65dc732132..edbc577586 100644 --- a/modules/core/src/main/java/org/locationtech/jts/geom/CoordinateArrays.java +++ b/modules/core/src/main/java/org/locationtech/jts/geom/CoordinateArrays.java @@ -13,6 +13,7 @@ */ package org.locationtech.jts.geom; +import java.lang.reflect.Array; import java.util.Collection; import java.util.Comparator; @@ -64,6 +65,104 @@ public static int measures(Coordinate[] pts) { return measures; } + + /** + * Utility method ensuring array contents are of consistent dimension and measures. + *

+ * Array is modified in place if required, coordinates are replaced in the array as required + * to ensure all coordinates have the same dimension and measures. The final dimension and + * measures used are the maximum found when checking the array. + *

+ * + * @param array Modified in place to coordinates of consistent dimension and measures. + */ + public static void enforceConsistency(Coordinate[] array) + { + if (array == null) { + return; + } + // step one check + int maxDimension = -1; + int maxMeasures = -1; + boolean isConsistent = true; + for (int i = 0; i < array.length; i++) { + Coordinate coordinate = array[i]; + if (coordinate != null) { + int d = Coordinates.dimension(coordinate); + int m = Coordinates.measures(coordinate); + if( maxDimension == -1){ + maxDimension = d; + maxMeasures = m; + continue; + } + if( d != maxDimension || m != maxMeasures ){ + isConsistent = false; + maxDimension = Math.max(maxDimension, d); + maxMeasures = Math.max(maxMeasures, m); + } + } + } + if (!isConsistent) { + // step two fix + Coordinate sample = Coordinates.create(maxDimension, maxMeasures); + Class type = sample.getClass(); + + for (int i = 0; i < array.length; i++) { + Coordinate coordinate = array[i]; + if (coordinate != null && !coordinate.getClass().equals(type)) { + Coordinate duplicate = Coordinates.create(maxDimension, maxMeasures); + duplicate.setCoordinate(coordinate); + array[i] = duplicate; + } + } + } + } + + /** + * Utility method ensuring array contents are of the specified dimension and measures. + *

+ * Array is returned unmodified if consistent, or a copy of the array is made with + * each inconsistent coordinate duplicated into an instance of the correct dimension and measures. + *

+ * + * @param array coordinate array + * @param dimension + * @param measures + * @return array returned, or copy created if required to enforce consistency. + */ + public static Coordinate[] enforceConsistency(Coordinate[] array,int dimension, int measures) + { + Coordinate sample = Coordinates.create(dimension,measures); + Class type = sample.getClass(); + boolean isConsistent = true; + for (int i = 0; i < array.length; i++) { + Coordinate coordinate = array[i]; + if (coordinate != null && !coordinate.getClass().equals(type)) { + isConsistent = false; + break; + } + } + if (isConsistent) { + return array; + } + else { + Class coordinateType = sample.getClass(); + Coordinate copy[] = (Coordinate[]) Array.newInstance(coordinateType, array.length); + for (int i = 0; i < copy.length; i++) { + Coordinate coordinate = array[i]; + if (coordinate != null && !coordinate.getClass().equals(type)) { + Coordinate duplicate = Coordinates.create(dimension,measures); + duplicate.setCoordinate(coordinate); + copy[i] = duplicate; + } + else { + copy[i] = coordinate; + } + } + return copy; + } + } + /** * Tests whether an array of {@link Coordinate}s forms a ring, * by checking length and closure. diff --git a/modules/core/src/main/java/org/locationtech/jts/geom/Coordinates.java b/modules/core/src/main/java/org/locationtech/jts/geom/Coordinates.java index fe117ac0c3..71f5b186f9 100644 --- a/modules/core/src/main/java/org/locationtech/jts/geom/Coordinates.java +++ b/modules/core/src/main/java/org/locationtech/jts/geom/Coordinates.java @@ -46,7 +46,7 @@ public static Coordinate create(int dimension, int measures) } return new Coordinate(); } - + /** * Determine dimension based on subclass of {@link Coordinate}. * diff --git a/modules/core/src/main/java/org/locationtech/jts/geom/impl/CoordinateArraySequence.java b/modules/core/src/main/java/org/locationtech/jts/geom/impl/CoordinateArraySequence.java index 6b1620165d..c47499aa07 100644 --- a/modules/core/src/main/java/org/locationtech/jts/geom/impl/CoordinateArraySequence.java +++ b/modules/core/src/main/java/org/locationtech/jts/geom/impl/CoordinateArraySequence.java @@ -75,8 +75,11 @@ public CoordinateArraySequence(Coordinate[] coordinates, int dimension) { /** * Constructs a sequence based on the given array - * of {@link Coordinate}s (the - * array is not copied). + * of {@link Coordinate}s (the array is not copied). + *

+ * It is your responsibility to ensure the array contains Coordinates of the + * indicated dimension and measures (See + * {@link CoordinateArrays#enforceConsistency(Coordinate[])} ).

* * @param coordinates the coordinate array that will be referenced. * @param dimension the dimension of the coordinates @@ -89,7 +92,7 @@ public CoordinateArraySequence(Coordinate[] coordinates, int dimension, int meas this.coordinates = new Coordinate[0]; } else { - this.coordinates = enforceArrayConsistency(coordinates); + this.coordinates = coordinates; } } @@ -158,48 +161,6 @@ public CoordinateArraySequence(CoordinateSequence coordSeq) } } - /** - * Ensure array contents of the same type, making use of {@link #createCoordinate()} as needed. - *

- * A new array will be created if needed to return a consistent result. - *

- * - * @param array array containing consistent coordinate instances - */ - protected Coordinate[] enforceArrayConsistency(Coordinate[] array) - { - Coordinate sample = createCoordinate(); - Class type = sample.getClass(); - boolean isConsistent=true; - for( int i = 0; i < array.length; i++) { - Coordinate coordinate = array[i]; - if( coordinate != null && !coordinate.getClass().equals(type)) { - isConsistent = false; - break; - } - } - if( isConsistent ){ - return array; - } - else { - Class coordinateType = sample.getClass(); - Coordinate copy[] = (Coordinate[]) Array.newInstance(coordinateType, array.length); - for ( int i = 0; i < copy.length; i++){ - Coordinate coordinate = array[i]; - if( coordinate != null && !coordinate.getClass().equals(type)){ - Coordinate duplicate = createCoordinate(); - duplicate.setCoordinate(coordinate); - copy[i] = duplicate; - } - else { - copy[i] = coordinate; - } - } - return copy; - } - } - - /** * @see org.locationtech.jts.geom.CoordinateSequence#getDimension() */ diff --git a/modules/core/src/test/java/org/locationtech/jts/geom/CoordinateArraysTest.java b/modules/core/src/test/java/org/locationtech/jts/geom/CoordinateArraysTest.java index 29177de653..2145c6c5ba 100644 --- a/modules/core/src/test/java/org/locationtech/jts/geom/CoordinateArraysTest.java +++ b/modules/core/src/test/java/org/locationtech/jts/geom/CoordinateArraysTest.java @@ -120,6 +120,31 @@ public void testScroll() { } } + public void testEnforceConsistency(){ + Coordinate array[] = new Coordinate[]{ + new Coordinate(1.0, 1.0, 0.0), + new CoordinateXYM(2.0,2.0,1.0) + }; + Coordinate array2[] = new Coordinate[]{ + new CoordinateXY(1.0, 1.0), + new CoordinateXY(2.0,2.0) + }; + // process into array with dimension 4 and measures 1 + CoordinateArrays.enforceConsistency(array); + assertEquals( 3, CoordinateArrays.dimension(array)); + assertEquals( 1, CoordinateArrays.measures(array)); + + CoordinateArrays.enforceConsistency(array2); + + Coordinate fixed[] = CoordinateArrays.enforceConsistency(array2,2,0); + assertSame( fixed, array2); // no processing required + + fixed = CoordinateArrays.enforceConsistency(array,3,0); + assertTrue( fixed != array); // copied into new array + assertTrue( array[0] != fixed[0] ); // processing needed to CoordinateXYZM + assertTrue( array[1] != fixed[1] ); // processing needed to CoordinateXYZM + } + private static void checkCoordinateAt(Coordinate[] seq1, int pos1, Coordinate[] seq2, int pos2) { Coordinate c1 = seq1[pos1], c2 = seq2[pos2]; From 383d33249710e98f4d138c17f3225bf14ba5111c Mon Sep 17 00:00:00 2001 From: Jim Hughes Date: Thu, 27 Aug 2020 18:20:48 -0400 Subject: [PATCH 03/10] Preparing to cut the 1.17.1 bug fix release. Signed-off-by: Jim Hughes --- doc/JTS_Version_History.md | 4 ++++ .../core/src/main/java/org/locationtech/jts/JTSVersion.java | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/JTS_Version_History.md b/doc/JTS_Version_History.md index bda2f662dc..74c089f797 100644 --- a/doc/JTS_Version_History.md +++ b/doc/JTS_Version_History.md @@ -21,6 +21,10 @@ Distributions for older JTS versions can be obtained at the *Release Date: TBD* +# Version 1.17.1 + +*Release Date: August 27, 2020* + *Java Version: 1.8* ### Functionality Improvements diff --git a/modules/core/src/main/java/org/locationtech/jts/JTSVersion.java b/modules/core/src/main/java/org/locationtech/jts/JTSVersion.java index bab29ed7a0..d17a259c9d 100644 --- a/modules/core/src/main/java/org/locationtech/jts/JTSVersion.java +++ b/modules/core/src/main/java/org/locationtech/jts/JTSVersion.java @@ -36,17 +36,17 @@ public class JTSVersion { /** * The minor version number. */ - public static final int MINOR = 18; + public static final int MINOR = 17; /** * The patch version number. */ - public static final int PATCH = 0; + public static final int PATCH = 1; /** * An optional string providing further release info (such as "alpha 1"); */ - private static final String RELEASE_INFO = "SNAPSHOT"; + private static final String RELEASE_INFO = ""; /** * Prints the current JTS version to stdout. From 69ce3dba2f8171bf0227721b37022012b7b3a15a Mon Sep 17 00:00:00 2001 From: Jim Hughes Date: Thu, 27 Aug 2020 18:24:52 -0400 Subject: [PATCH 04/10] [maven-release-plugin] prepare release jts-1.17.1 --- modules/app/pom.xml | 2 +- modules/core/pom.xml | 2 +- modules/example/pom.xml | 2 +- modules/io/common/pom.xml | 2 +- modules/io/pom.xml | 2 +- modules/lab/pom.xml | 2 +- modules/pom.xml | 2 +- modules/tests/pom.xml | 2 +- pom.xml | 4 ++-- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/app/pom.xml b/modules/app/pom.xml index 348b10cca6..74567d3e3e 100644 --- a/modules/app/pom.xml +++ b/modules/app/pom.xml @@ -3,7 +3,7 @@ org.locationtech.jts jts-modules - 1.18.0-SNAPSHOT + 1.17.1 jts-app ${project.groupId}:${project.artifactId} diff --git a/modules/core/pom.xml b/modules/core/pom.xml index 2a2babf21d..9f1c68324f 100644 --- a/modules/core/pom.xml +++ b/modules/core/pom.xml @@ -3,7 +3,7 @@ org.locationtech.jts jts-modules - 1.18.0-SNAPSHOT + 1.17.1 jts-core ${project.groupId}:${project.artifactId} diff --git a/modules/example/pom.xml b/modules/example/pom.xml index 2e07b267f4..1bfc97f319 100644 --- a/modules/example/pom.xml +++ b/modules/example/pom.xml @@ -3,7 +3,7 @@ org.locationtech.jts jts-modules - 1.18.0-SNAPSHOT + 1.17.1 jts-example ${project.groupId}:${project.artifactId} diff --git a/modules/io/common/pom.xml b/modules/io/common/pom.xml index f5e49a8f98..4203bd0da3 100644 --- a/modules/io/common/pom.xml +++ b/modules/io/common/pom.xml @@ -3,7 +3,7 @@ org.locationtech.jts jts-io - 1.18.0-SNAPSHOT + 1.17.1 org.locationtech.jts.io jts-io-common diff --git a/modules/io/pom.xml b/modules/io/pom.xml index 2a5e3deece..ea6bf3d001 100644 --- a/modules/io/pom.xml +++ b/modules/io/pom.xml @@ -3,7 +3,7 @@ org.locationtech.jts jts-modules - 1.18.0-SNAPSHOT + 1.17.1 jts-io ${project.groupId}:${project.artifactId} diff --git a/modules/lab/pom.xml b/modules/lab/pom.xml index 9a0de3de76..6c50762798 100644 --- a/modules/lab/pom.xml +++ b/modules/lab/pom.xml @@ -3,7 +3,7 @@ org.locationtech.jts jts-modules - 1.18.0-SNAPSHOT + 1.17.1 jts-lab ${project.groupId}:${project.artifactId} diff --git a/modules/pom.xml b/modules/pom.xml index 17788e92b9..2063e0eea4 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -3,7 +3,7 @@ org.locationtech.jts jts - 1.18.0-SNAPSHOT + 1.17.1 jts-modules ${project.groupId}:${project.artifactId} diff --git a/modules/tests/pom.xml b/modules/tests/pom.xml index 13c7cb6973..fb1a5fb393 100644 --- a/modules/tests/pom.xml +++ b/modules/tests/pom.xml @@ -3,7 +3,7 @@ org.locationtech.jts jts-modules - 1.18.0-SNAPSHOT + 1.17.1 jts-tests ${project.groupId}:${project.artifactId} diff --git a/pom.xml b/pom.xml index fba65c56ef..827618982a 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.locationtech.jts jts - 1.18.0-SNAPSHOT + 1.17.1 pom JTS Topology Suite @@ -74,7 +74,7 @@ scm:git::git@github.com:locationtech/jts.git scm:git:git@github.com:locationtech/jts.git https://github.com/locationtech/jts - HEAD + jts-1.17.1 From 96b9512e6ef7aefbd8bcd0731e64992889a7dbcd Mon Sep 17 00:00:00 2001 From: Jim Hughes Date: Thu, 27 Aug 2020 18:24:59 -0400 Subject: [PATCH 05/10] [maven-release-plugin] prepare for next development iteration --- modules/app/pom.xml | 2 +- modules/core/pom.xml | 2 +- modules/example/pom.xml | 2 +- modules/io/common/pom.xml | 2 +- modules/io/pom.xml | 2 +- modules/lab/pom.xml | 2 +- modules/pom.xml | 2 +- modules/tests/pom.xml | 2 +- pom.xml | 4 ++-- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/app/pom.xml b/modules/app/pom.xml index 74567d3e3e..348b10cca6 100644 --- a/modules/app/pom.xml +++ b/modules/app/pom.xml @@ -3,7 +3,7 @@ org.locationtech.jts jts-modules - 1.17.1 + 1.18.0-SNAPSHOT jts-app ${project.groupId}:${project.artifactId} diff --git a/modules/core/pom.xml b/modules/core/pom.xml index 9f1c68324f..2a2babf21d 100644 --- a/modules/core/pom.xml +++ b/modules/core/pom.xml @@ -3,7 +3,7 @@ org.locationtech.jts jts-modules - 1.17.1 + 1.18.0-SNAPSHOT jts-core ${project.groupId}:${project.artifactId} diff --git a/modules/example/pom.xml b/modules/example/pom.xml index 1bfc97f319..2e07b267f4 100644 --- a/modules/example/pom.xml +++ b/modules/example/pom.xml @@ -3,7 +3,7 @@ org.locationtech.jts jts-modules - 1.17.1 + 1.18.0-SNAPSHOT jts-example ${project.groupId}:${project.artifactId} diff --git a/modules/io/common/pom.xml b/modules/io/common/pom.xml index 4203bd0da3..f5e49a8f98 100644 --- a/modules/io/common/pom.xml +++ b/modules/io/common/pom.xml @@ -3,7 +3,7 @@ org.locationtech.jts jts-io - 1.17.1 + 1.18.0-SNAPSHOT org.locationtech.jts.io jts-io-common diff --git a/modules/io/pom.xml b/modules/io/pom.xml index ea6bf3d001..2a5e3deece 100644 --- a/modules/io/pom.xml +++ b/modules/io/pom.xml @@ -3,7 +3,7 @@ org.locationtech.jts jts-modules - 1.17.1 + 1.18.0-SNAPSHOT jts-io ${project.groupId}:${project.artifactId} diff --git a/modules/lab/pom.xml b/modules/lab/pom.xml index 6c50762798..9a0de3de76 100644 --- a/modules/lab/pom.xml +++ b/modules/lab/pom.xml @@ -3,7 +3,7 @@ org.locationtech.jts jts-modules - 1.17.1 + 1.18.0-SNAPSHOT jts-lab ${project.groupId}:${project.artifactId} diff --git a/modules/pom.xml b/modules/pom.xml index 2063e0eea4..17788e92b9 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -3,7 +3,7 @@ org.locationtech.jts jts - 1.17.1 + 1.18.0-SNAPSHOT jts-modules ${project.groupId}:${project.artifactId} diff --git a/modules/tests/pom.xml b/modules/tests/pom.xml index fb1a5fb393..13c7cb6973 100644 --- a/modules/tests/pom.xml +++ b/modules/tests/pom.xml @@ -3,7 +3,7 @@ org.locationtech.jts jts-modules - 1.17.1 + 1.18.0-SNAPSHOT jts-tests ${project.groupId}:${project.artifactId} diff --git a/pom.xml b/pom.xml index 827618982a..fba65c56ef 100755 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.locationtech.jts jts - 1.17.1 + 1.18.0-SNAPSHOT pom JTS Topology Suite @@ -74,7 +74,7 @@ scm:git::git@github.com:locationtech/jts.git scm:git:git@github.com:locationtech/jts.git https://github.com/locationtech/jts - jts-1.17.1 + HEAD From 3cc0da99cd40bd9820729679de64ff8c9f10cf3d Mon Sep 17 00:00:00 2001 From: Martin Davis Date: Thu, 27 Aug 2020 15:52:47 -0700 Subject: [PATCH 06/10] Improve TestBuilder varying palette Signed-off-by: Martin Davis --- .../jtstest/testbuilder/ui/style/Palette.java | 9 ++++++--- .../java/org/locationtech/jtstest/util/HSBPalette.java | 8 +++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/modules/app/src/main/java/org/locationtech/jtstest/testbuilder/ui/style/Palette.java b/modules/app/src/main/java/org/locationtech/jtstest/testbuilder/ui/style/Palette.java index 59b071e90d..de9faa7a1c 100644 --- a/modules/app/src/main/java/org/locationtech/jtstest/testbuilder/ui/style/Palette.java +++ b/modules/app/src/main/java/org/locationtech/jtstest/testbuilder/ui/style/Palette.java @@ -12,15 +12,18 @@ public class Palette { public static final int TYPE_RAINBOW = 3; public static final int TYPE_RAINBOW_RANDOM = 4; + private static final float BRIGHT_RANGE = 0.1f; + private static final float SAT_RANGE = 0.2f; + public static HSBPalette customPalette(int paletteType, Color clrBase, int numHues) { HSBPalette pal = null; float sat = ColorUtil.getSaturation(clrBase); float bright = ColorUtil.getBrightness(clrBase); if (TYPE_VARY == paletteType) { float hue = ColorUtil.getHue(clrBase); - pal = new HSBPalette(5, hue, 0.1f, - 3, 0.3f, 0.7f, - 3, 0.8f, 0.9f + pal = new HSBPalette(5, hue, HSBPalette.HUE_WIDTH / 2, + 3, sat - SAT_RANGE/2, sat + SAT_RANGE/2, + 3, bright - BRIGHT_RANGE/2, bright + BRIGHT_RANGE/2 ); } else if (TYPE_RAINBOW == paletteType) { diff --git a/modules/app/src/main/java/org/locationtech/jtstest/util/HSBPalette.java b/modules/app/src/main/java/org/locationtech/jtstest/util/HSBPalette.java index 363a94ea46..dbc62cbce1 100644 --- a/modules/app/src/main/java/org/locationtech/jtstest/util/HSBPalette.java +++ b/modules/app/src/main/java/org/locationtech/jtstest/util/HSBPalette.java @@ -2,11 +2,13 @@ import java.awt.Color; +import org.locationtech.jts.math.MathUtil; import org.locationtech.jtstest.testbuilder.ui.ColorUtil; public class HSBPalette { - private static final float HUE_WRAP_MAX = 1 - 0.08333f; + public static final float HUE_WIDTH = 0.08333f; + private static final float HUE_WRAP_MAX = 1 - HUE_WIDTH; public static HSBPalette createRainbow(int numHue, float s, float b) { return new HSBPalette(numHue, 0, HUE_WRAP_MAX, @@ -79,8 +81,8 @@ public Color color(int index, int alpha) { int iS = iSB / numB; int iB = iSB - iS * numB; float h = (h1 + iH * hInc) % 1.0f; - float s = sLo + iS * sInc; - float b = bLo + iB * bInc; + float s = (float) MathUtil.clamp(sLo + iS * sInc, 0, 1); + float b = (float) MathUtil.clamp(bLo + iB * bInc, 0, 1); Color chsb = Color.getHSBColor(h, s, b); return ColorUtil.setAlpha(chsb, alpha); } From 7d07a693a8b64760f21d30d7d4fc12e64297557b Mon Sep 17 00:00:00 2001 From: Jim Hughes Date: Thu, 27 Aug 2020 20:36:34 -0400 Subject: [PATCH 07/10] Resetting JTSVersion to 1.18.0-SNAPSHOT. Signed-off-by: Jim Hughes --- .../core/src/main/java/org/locationtech/jts/JTSVersion.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/core/src/main/java/org/locationtech/jts/JTSVersion.java b/modules/core/src/main/java/org/locationtech/jts/JTSVersion.java index d17a259c9d..bab29ed7a0 100644 --- a/modules/core/src/main/java/org/locationtech/jts/JTSVersion.java +++ b/modules/core/src/main/java/org/locationtech/jts/JTSVersion.java @@ -36,17 +36,17 @@ public class JTSVersion { /** * The minor version number. */ - public static final int MINOR = 17; + public static final int MINOR = 18; /** * The patch version number. */ - public static final int PATCH = 1; + public static final int PATCH = 0; /** * An optional string providing further release info (such as "alpha 1"); */ - private static final String RELEASE_INFO = ""; + private static final String RELEASE_INFO = "SNAPSHOT"; /** * Prints the current JTS version to stdout. From be18a8a797763116346ca605f8d7aedc66153773 Mon Sep 17 00:00:00 2001 From: Martin Davis Date: Fri, 28 Aug 2020 11:01:48 -0700 Subject: [PATCH 08/10] Improve Orientation isCCW to handle topology collapse (#588) * Improve Orientation isCCW algorithm to handle flat topology collapse Signed-off-by: Martin Davis --- .../jts/algorithm/Orientation.java | 233 ++++++++---------- .../locationtech/jts/algorithm/IsCCWTest.java | 108 ++++++-- 2 files changed, 180 insertions(+), 161 deletions(-) diff --git a/modules/core/src/main/java/org/locationtech/jts/algorithm/Orientation.java b/modules/core/src/main/java/org/locationtech/jts/algorithm/Orientation.java index 409f98439b..db98ee1259 100644 --- a/modules/core/src/main/java/org/locationtech/jts/algorithm/Orientation.java +++ b/modules/core/src/main/java/org/locationtech/jts/algorithm/Orientation.java @@ -13,6 +13,7 @@ import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.CoordinateSequence; +import org.locationtech.jts.geom.impl.CoordinateArraySequence; /** * Functions to compute the orientation of basic geometric structures @@ -107,104 +108,45 @@ public static int index(Coordinate p1, Coordinate p2, Coordinate q) * oriented counter-clockwise. *
    *
  • The list of points is assumed to have the first and last points equal. - *
  • This will handle coordinate lists which contain repeated points. + *
  • This handles coordinate lists which contain repeated points. + *
  • This handles rings which contain collapsed segments + * (in particular, along the top of the ring). *
- * This algorithm is only guaranteed to work with valid rings. If the - * ring is invalid (e.g. self-crosses or touches), the computed result may not - * be correct. + * This algorithm is guaranteed to work with valid rings. + * It also works with "mildly invalid" rings + * which contain collapsed (coincident) flat segments along the top of the ring. + * If the ring is "more" invalid (e.g. self-crosses or touches), + * the computed result may not be correct. * - * @param ring - * an array of Coordinates forming a ring + * @param ring an array of Coordinates forming a ring (with first and last point identical) * @return true if the ring is oriented counter-clockwise. - * @throws IllegalArgumentException - * if there are too few points to determine orientation (< 4) + * @throws IllegalArgumentException if there are too few points to determine orientation (< 4) */ public static boolean isCCW(Coordinate[] ring) { - // # of points without closing endpoint - int nPts = ring.length - 1; - // sanity check - if (nPts < 3) - throw new IllegalArgumentException( - "Ring has fewer than 4 points, so orientation cannot be determined"); - - // find highest point - Coordinate hiPt = ring[0]; - int hiIndex = 0; - for (int i = 1; i <= nPts; i++) { - Coordinate p = ring[i]; - if (p.y > hiPt.y) { - hiPt = p; - hiIndex = i; - } - } - - // find distinct point before highest point - int iPrev = hiIndex; - do { - iPrev = iPrev - 1; - if (iPrev < 0) - iPrev = nPts; - } while (ring[iPrev].equals2D(hiPt) && iPrev != hiIndex); - - // find distinct point after highest point - int iNext = hiIndex; - do { - iNext = (iNext + 1) % nPts; - } while (ring[iNext].equals2D(hiPt) && iNext != hiIndex); - - Coordinate prev = ring[iPrev]; - Coordinate next = ring[iNext]; - - /* - * This check catches cases where the ring contains an A-B-A configuration - * of points. This can happen if the ring does not contain 3 distinct points - * (including the case where the input array has fewer than 4 elements), or - * it contains coincident line segments. - */ - if (prev.equals2D(hiPt) || next.equals2D(hiPt) || prev.equals2D(next)) - return false; - - int disc = Orientation.index(prev, hiPt, next); - - /* - * If disc is exactly 0, lines are collinear. There are two possible cases: - * (1) the lines lie along the x axis in opposite directions (2) the lines - * lie on top of one another - * - * (1) is handled by checking if next is left of prev ==> CCW (2) will never - * happen if the ring is valid, so don't check for it (Might want to assert - * this) - */ - boolean isCCW; - if (disc == 0) { - // poly is CCW if prev x is right of next x - isCCW = (prev.x > next.x); - } - else { - // if area is positive, points are ordered CCW - isCCW = (disc > 0); - } - return isCCW; + // wrap with an XY CoordinateSequence + return isCCW(new CoordinateArraySequence(ring, 2, 0)); } /** - * Computes whether a ring defined by an {@link CoordinateSequence} is + * Computes whether a ring defined by a {@link CoordinateSequence} is * oriented counter-clockwise. *
    *
  • The list of points is assumed to have the first and last points equal. - *
  • This will handle coordinate lists which contain repeated points. + *
  • This handles coordinate lists which contain repeated points. + *
  • This handles rings which contain collapsed segments + * (in particular, along the top of the ring). *
- * This algorithm is only guaranteed to work with valid rings. If the - * ring is invalid (e.g. self-crosses or touches), the computed result may not - * be correct. - * - * @param ring - * a CoordinateSequence forming a ring + * This algorithm is guaranteed to work with valid rings. + * It also works with "mildly invalid" rings + * which contain collapsed (coincident) flat segments along the top of the ring. + * If the ring is "more" invalid (e.g. self-crosses or touches), + * the computed result may not be correct. + * + * @param ring a CoordinateSequence forming a ring (with first and last point identical) * @return true if the ring is oriented counter-clockwise. - * @throws IllegalArgumentException - * if there are too few points to determine orientation (< 4) - */ + * @throws IllegalArgumentException if there are too few points to determine orientation (< 4) + */ public static boolean isCCW(CoordinateSequence ring) { // # of points without closing endpoint @@ -212,67 +154,84 @@ public static boolean isCCW(CoordinateSequence ring) // sanity check if (nPts < 3) throw new IllegalArgumentException( - "Ring has fewer than 4 points, so orientation cannot be determined"); - - // find highest point - Coordinate hiPt = ring.getCoordinate(0); - int hiIndex = 0; + "Ring has fewer than 4 points, so orientation cannot be determined"); + + /** + * Find first highest point after a lower point, if one exists + * (e.g. a rising segment) + * If one does not exist, hiIndex will remain 0 + * and the ring must be flat. + * Note this relies on the convention that + * rings have the same start and end point. + */ + Coordinate upHiPt = ring.getCoordinate(0); + double prevY = upHiPt.y; + Coordinate upLowPt = null; + int iUpHi = 0; for (int i = 1; i <= nPts; i++) { - Coordinate p = ring.getCoordinate(i); - if (p.y > hiPt.y) { - hiPt = p; - hiIndex = i; + double py = ring.getOrdinate(i, Coordinate.Y); + /** + * If segment is upwards and endpoint is higher, record it + */ + if (py > prevY && py >= upHiPt.y) { + upHiPt = ring.getCoordinate(i); + iUpHi = i; + upLowPt = ring.getCoordinate(i-1); } + prevY = py; } - - // find distinct point before highest point - Coordinate prev; - int iPrev = hiIndex; - do { - iPrev = iPrev - 1; - if (iPrev < 0) - iPrev = nPts; - prev = ring.getCoordinate(iPrev); - } while (prev.equals2D(hiPt) && iPrev != hiIndex); - - // find distinct point after highest point - Coordinate next; - int iNext = hiIndex; - do { - iNext = (iNext + 1) % nPts; - next = ring.getCoordinate(iNext); - } while (next.equals2D(hiPt) && iNext != hiIndex); - - /* - * This check catches cases where the ring contains an A-B-A configuration - * of points. This can happen if the ring does not contain 3 distinct points - * (including the case where the input array has fewer than 4 elements), or - * it contains coincident line segments. + /** + * Check if ring is flat and return default value if so */ - if (prev.equals2D(hiPt) || next.equals2D(hiPt) || prev.equals2D(next)) - return false; - - int disc = Orientation.index(prev, hiPt, next); + if (iUpHi == 0) return false; + + /** + * Find the next lower point after the high point + * (e.g. a falling segment). + * This must exist since ring is not flat. + */ + int iDownLow = iUpHi; + do { + iDownLow = (iDownLow + 1) % nPts; + } while (iDownLow != iUpHi && ring.getOrdinate(iDownLow, Coordinate.Y) == upHiPt.y ); - /* - * If disc is exactly 0, lines are collinear. There are two possible cases: - * (1) the lines lie along the x axis in opposite directions (2) the lines - * lie on top of one another - * - * (1) is handled by checking if next is left of prev ==> CCW (2) will never - * happen if the ring is valid, so don't check for it (Might want to assert - * this) + Coordinate downLowPt = ring.getCoordinate(iDownLow); + int iDownHi = iDownLow > 0 ? iDownLow - 1 : nPts - 1; + Coordinate downHiPt = ring.getCoordinate(iDownHi); + + /** + * Two cases can occur: + * 1) the hiPt and the downPrevPt are the same. + * This is the general position case of a "pointed cap". + * The ring orientation is determined by the orientation of the cap + * 2) The hiPt and the downPrevPt are different. + * In this case the top of the cap is flat. + * The ring orientation is given by the direction of the flat segment */ - boolean isCCW; - if (disc == 0) { - // poly is CCW if prev x is right of next x - isCCW = (prev.x > next.x); + if (upHiPt.equals2D(downHiPt)) { + /** + * Check for the case where the cap has configuration A-B-A. + * This can happen if the ring does not contain 3 distinct points + * (including the case where the input array has fewer than 4 elements), or + * it contains coincident line segments. + */ + if (upLowPt.equals2D(upHiPt) || downLowPt.equals2D(upHiPt) || upLowPt.equals2D(downLowPt)) + return false; + + /** + * It can happen that the top segments are coincident. + * This is an invalid ring, which cannot be computed correctly. + * In this case the orientation is 0, and the result is false. + */ + int index = index(upLowPt, upHiPt, downLowPt); + return index == COUNTERCLOCKWISE; } else { - // if area is positive, points are ordered CCW - isCCW = (disc > 0); + /** + * Flat cap - direction of flat top determines orientation + */ + double delX = downHiPt.x - upHiPt.x; + return delX < 0; } - return isCCW; } - } diff --git a/modules/core/src/test/java/org/locationtech/jts/algorithm/IsCCWTest.java b/modules/core/src/test/java/org/locationtech/jts/algorithm/IsCCWTest.java index 5deaf5d4bf..0744e52e34 100644 --- a/modules/core/src/test/java/org/locationtech/jts/algorithm/IsCCWTest.java +++ b/modules/core/src/test/java/org/locationtech/jts/algorithm/IsCCWTest.java @@ -20,14 +20,13 @@ import junit.framework.TestCase; import junit.textui.TestRunner; +import test.jts.GeometryTestCase; /** * Tests CGAlgorithms.isCCW * @version 1.7 */ -public class IsCCWTest extends TestCase { - - private WKTReader reader = new WKTReader(); +public class IsCCWTest extends GeometryTestCase { public static void main(String args[]) { TestRunner.run(IsCCWTest.class); @@ -35,35 +34,96 @@ public static void main(String args[]) { public IsCCWTest(String name) { super(name); } - public void testCCW() throws Exception - { - Coordinate[] pts = getCoordinates("POLYGON ((60 180, 140 240, 140 240, 140 240, 200 180, 120 120, 60 180))"); - assertEquals(false, Orientation.isCCW(pts)); - CoordinateSequence seq = getCoordinateSequence("POLYGON ((60 180, 140 240, 140 240, 140 240, 200 180, 120 120, 60 180))"); - assertEquals(false, Orientation.isCCW(seq)); - - Coordinate[] pts2 = getCoordinates("POLYGON ((60 180, 140 120, 100 180, 140 240, 60 180))"); - assertEquals(true, Orientation.isCCW(pts2)); - CoordinateSequence seq2 = getCoordinateSequence("POLYGON ((60 180, 140 120, 100 180, 140 240, 60 180))"); - assertEquals(true, Orientation.isCCW(seq2)); - - // same pts list with duplicate top point - check that isCCW still works - Coordinate[] pts2x = getCoordinates( "POLYGON ((60 180, 140 120, 100 180, 140 240, 140 240, 60 180))"); - assertEquals(true, Orientation.isCCW(pts2x) ); - CoordinateSequence seq2x = getCoordinateSequence("POLYGON ((60 180, 140 120, 100 180, 140 240, 140 240, 60 180))"); - assertEquals(true, Orientation.isCCW(seq2x) ); + public void testTooFewPoints() { + Coordinate[] pts = new Coordinate[] { + new Coordinate(0, 0), + new Coordinate(1, 1), + new Coordinate(2, 2) + }; + boolean hasError = false; + try { + boolean isCCW = Orientation.isCCW(pts); + } + catch (IllegalArgumentException ex) { + hasError = true; + } + assertTrue(hasError); + } + + public void testCCW() { + checkOrientationCCW(true, "POLYGON ((60 180, 140 120, 100 180, 140 240, 60 180))"); + } + + public void testRingCW() { + checkOrientationCCW(false, "POLYGON ((60 180, 140 240, 100 180, 140 120, 60 180))"); + } + + public void testCCWSmall() { + checkOrientationCCW(true, "POLYGON ((1 1, 9 1, 5 9, 1 1))"); + } + + public void testDuplicateTopPoint() { + checkOrientationCCW(true, "POLYGON ((60 180, 140 120, 100 180, 140 240, 140 240, 60 180))"); + } + + public void testFlatTopSegment() { + checkOrientationCCW(false, "POLYGON ((100 200, 200 200, 200 100, 100 100, 100 200))"); + } + + public void testFlatMultipleTopSegment() { + checkOrientationCCW(false, "POLYGON ((100 200, 127 200, 151 200, 173 200, 200 200, 100 100, 100 200))"); + } + + public void testDegenerateRingHorizontal() { + checkOrientationCCW(false, "POLYGON ((100 200, 100 200, 200 200, 100 200))"); + } + + public void testDegenerateRingAngled() { + checkOrientationCCW(false, "POLYGON ((100 100, 100 100, 200 200, 100 100))"); + } + + public void testDegenerateRingVertical() { + checkOrientationCCW(false, "POLYGON ((200 100, 200 100, 200 200, 200 100))"); + } + + /** + * This case is an invalid ring, so answer is a default value + */ + public void testTopAngledSegmentCollapse() { + checkOrientationCCW(false, "POLYGON ((10 20, 61 20, 20 30, 50 60, 10 20))"); + } + + public void testABATopFlatSegmentCollapse() { + checkOrientationCCW(true, "POLYGON ((71 0, 40 40, 70 40, 40 40, 20 0, 71 0))"); + } + + public void testABATopFlatSegmentCollapseMiddleStart() { + checkOrientationCCW(true, "POLYGON ((90 90, 50 90, 10 10, 90 10, 50 90, 90 90))"); + } + + public void testMultipleTopFlatSegmentCollapseSinglePoint() { + checkOrientationCCW(true, "POLYGON ((100 100, 200 100, 150 200, 170 200, 200 200, 100 200, 150 200, 100 100))"); + } + + public void testMultipleTopFlatSegmentCollapseFlatTop() { + checkOrientationCCW(true, "POLYGON ((10 10, 90 10, 70 70, 90 70, 10 70, 30 70, 50 70, 10 10))"); + } + + private void checkOrientationCCW(boolean expectedCCW, String wkt) { + Coordinate[] pts2x = getCoordinates(wkt); + assertEquals("Coordinate array isCCW: ", expectedCCW, Orientation.isCCW(pts2x) ); + CoordinateSequence seq2x = getCoordinateSequence(wkt); + assertEquals("CoordinateSequence isCCW: ", expectedCCW, Orientation.isCCW(seq2x) ); } private Coordinate[] getCoordinates(String wkt) - throws ParseException { - Geometry geom = reader.read(wkt); + Geometry geom = read(wkt); return geom.getCoordinates(); } private CoordinateSequence getCoordinateSequence(String wkt) - throws ParseException { - Geometry geom = reader.read(wkt); + Geometry geom = read(wkt); if (geom.getGeometryType() != "Polygon") throw new IllegalArgumentException("wkt"); Polygon poly = (Polygon)geom; From 3d74c0827c55c380ec3da1441f405210480fc9a1 Mon Sep 17 00:00:00 2001 From: Martin Davis Date: Fri, 28 Aug 2020 11:10:49 -0700 Subject: [PATCH 09/10] Update JTS_Version_History.md --- doc/JTS_Version_History.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/JTS_Version_History.md b/doc/JTS_Version_History.md index 74c089f797..445cf1993e 100644 --- a/doc/JTS_Version_History.md +++ b/doc/JTS_Version_History.md @@ -21,6 +21,12 @@ Distributions for older JTS versions can be obtained at the *Release Date: TBD* +### Functionality Improvements + +* Improve Orientation.isCCW to handle flat topology collapse (#588) + + + # Version 1.17.1 *Release Date: August 27, 2020* From 85b9cef39b1de0811c8f0997623f17cd1ecfc70b Mon Sep 17 00:00:00 2001 From: Martin Davis Date: Fri, 28 Aug 2020 16:10:48 -0700 Subject: [PATCH 10/10] Javadoc for WKBWriter Signed-off-by: Martin Davis --- .../org/locationtech/jts/io/WKBWriter.java | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/modules/core/src/main/java/org/locationtech/jts/io/WKBWriter.java b/modules/core/src/main/java/org/locationtech/jts/io/WKBWriter.java index 9619f88fe6..71f8ab9d31 100644 --- a/modules/core/src/main/java/org/locationtech/jts/io/WKBWriter.java +++ b/modules/core/src/main/java/org/locationtech/jts/io/WKBWriter.java @@ -33,23 +33,36 @@ * with arbitrary byte stream sinks. *

* The WKB format is specified in the - * OGC Simple Features for SQL - * specification. - * This implementation also supports the Extended WKB - * standard. Extended WKB allows writing 3-dimensional coordinates - * and including the geometry SRID value. - * The presence of 3D coordinates is signified - * by setting the high bit of the wkbType word. - * The presence of an SRID is signified - * by setting the third bit of the wkbType word. - * EWKB format is upward compatible with the original SFS WKB format. + * OGC Simple Features for SQL + * specification (section 3.3.2.6). *

- * Empty Points are output as a Point with NaN X and Y ordinate values. + * There are a few cases which are not specified in the standard. + * The implementation uses a representation which is compatible with + * other common spatial systems (notably, PostGIS). + *

    + *
  • {@link LinearRing}s are written as {@link LineString}s
  • + *
  • Empty geometries are output as follows: + *
      + *
    • Point: a WKBPoint with NaN ordinate values
    • + *
    • LineString: a WKBLineString with zero points
    • + *
    • Polygon: currently output as a WKBPolygon with one LinearRing with zero points. + * Note: This is different to other systems. It will change to a WKBPolygon with zero LinearRings. + *
    • + *
    • Multi geometries: a WKBMulti geometry of appropriate type with zero elements
    • + *
    • GeometryCollections: a WKBGeometryCollection with zero elements
    • + *
  • + *
*

- * The WKB specification does not support representing {@link LinearRing}s; - * they will be written as {@link LineString}s. + * This implementation supports the Extended WKB standard. + * Extended WKB allows writing 3-dimensional coordinates + * and the geometry SRID value. + * The presence of 3D coordinates is indicated + * by setting the high bit of the wkbType word. + * The presence of a SRID is indicated + * by setting the third bit of the wkbType word. + * EWKB format is upward-compatible with the original SFS WKB format. *

- * This class is designed to support reuse of a single instance to read multiple + * This class supports reuse of a single instance to read multiple * geometries. This class is not thread-safe; each thread should create its own * instance. *