From 031980f067b6be186a229ad2591d3d35e2465640 Mon Sep 17 00:00:00 2001 From: Martin Davis Date: Sat, 23 Nov 2024 19:34:13 -0800 Subject: [PATCH] Fix RelateNG to cache in A-L cases --- .../operation/relateng/RelateGeometry.java | 7 + .../operation/relateng/TopologyComputer.java | 28 +++- .../operation/relateng/RelateNGGCTest.java | 8 + ...lateNGPolygonLinesOverlappingPerfTest.java | 157 ++++++++++++++++++ .../resources/testxml/misc/TestRelateGC.xml | 24 ++- 5 files changed, 217 insertions(+), 7 deletions(-) create mode 100644 modules/core/src/test/java/test/jts/perf/operation/relateng/RelateNGPolygonLinesOverlappingPerfTest.java diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateGeometry.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateGeometry.java index 84d2d57f8e..1961798c04 100644 --- a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateGeometry.java +++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateGeometry.java @@ -184,6 +184,10 @@ public boolean hasDimension(int dim) { return false; } + public boolean hasAreaAndLine() { + return hasAreas && hasLines; + } + /** * Gets the actual non-empty dimension of the geometry. * Zero-length LineStrings are treated as Points. @@ -264,6 +268,9 @@ public boolean isSelfNodingRequired() { //-- a GC with a single polygon does not need noding if (hasAreas && geom.getNumGeometries() == 1) return false; + //-- GCs with only points do not need noding + if (! hasAreas && ! hasLines) + return false; return true; } diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyComputer.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyComputer.java index 3fc6982960..339c69bebc 100644 --- a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyComputer.java +++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyComputer.java @@ -118,7 +118,8 @@ public boolean isAreaArea() { * Indicates whether the input geometries require self-noding * for correct evaluation of specific spatial predicates. * Self-noding is required for geometries which may - * have self-crossing linework. + * have self-crossing linework, + * or may have lines lying in the boundary of an area. * This causes the coordinates of nodes created by * crossing segments to be computed explicitly. * This ensures that node locations match in situations @@ -126,14 +127,29 @@ public boolean isAreaArea() { * The canonical example is a self-crossing line tested against a single segment * identical to one of the crossed segments. * + * Currently, requiring self-noding prevents noder caching. + * So it it important to limit the cases which require self-noding. + * Currently self-noding is required for: + * + * Note that linear B inputs do not require self-noding in all cases. + * In particular, if A is polygonal then predicates with linear B do not require self-noding. + * * @return true if self-noding is required */ public boolean isSelfNodingRequired() { - if (predicate.requireSelfNoding()) { - if (geomA.isSelfNodingRequired() - || geomB.isSelfNodingRequired()) - return true; - } + if (! predicate.requireSelfNoding()) + return false; + + if (geomA.isSelfNodingRequired()) + return true; + + //-- if B is a mixed GC with A and L require full noding + if (geomB.hasAreaAndLine()) + return true; + return false; } diff --git a/modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelateNGGCTest.java b/modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelateNGGCTest.java index b17a098cae..fef3eb510a 100644 --- a/modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelateNGGCTest.java +++ b/modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelateNGGCTest.java @@ -224,12 +224,20 @@ public void testPolygonContainingLineInBoundary() { String a = "POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))"; String b = "GEOMETRYCOLLECTION (POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0)), LINESTRING (0 2, 0 5))"; checkEquals(a, b, true); + checkContainsWithin(a, b, true); + checkCoversCoveredBy(a, b, true); + checkContainsWithin(b, a, true); + checkCoversCoveredBy(b, a, true); } public void testPolygonContainingLineInBoundaryAndInterior() { String a = "POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0))"; String b = "GEOMETRYCOLLECTION (POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0)), LINESTRING (0 2, 0 5, 5 5))"; checkEquals(a, b, true); + checkContainsWithin(a, b, true); + checkCoversCoveredBy(a, b, true); + checkContainsWithin(b, a, true); + checkCoversCoveredBy(b, a, true); } diff --git a/modules/core/src/test/java/test/jts/perf/operation/relateng/RelateNGPolygonLinesOverlappingPerfTest.java b/modules/core/src/test/java/test/jts/perf/operation/relateng/RelateNGPolygonLinesOverlappingPerfTest.java new file mode 100644 index 0000000000..e688b3e13b --- /dev/null +++ b/modules/core/src/test/java/test/jts/perf/operation/relateng/RelateNGPolygonLinesOverlappingPerfTest.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2022 Martin Davis. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * and Eclipse Distribution License v. 1.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * + * http://www.eclipse.org/org/documents/edl-v10.php. + */ +package test.jts.perf.operation.relateng; + +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.prep.PreparedGeometry; +import org.locationtech.jts.geom.prep.PreparedGeometryFactory; +import org.locationtech.jts.geom.util.SineStarFactory; +import org.locationtech.jts.operation.relateng.RelateNG; +import org.locationtech.jts.operation.relateng.RelatePredicate; + +import test.jts.perf.PerformanceTestCase; +import test.jts.perf.PerformanceTestRunner; + +public class RelateNGPolygonLinesOverlappingPerfTest +extends PerformanceTestCase +{ + + public static void main(String args[]) { + PerformanceTestRunner.run(RelateNGPolygonLinesOverlappingPerfTest.class); + } + + private static final int N_ITER = 1; + + static double ORG_X = 100; + static double ORG_Y = ORG_X; + static double SIZE = 2 * ORG_X; + static int N_ARMS = 6; + static double ARM_RATIO = 0.3; + + static int GRID_SIZE = 100; + static double GRID_CELL_SIZE = SIZE / GRID_SIZE; + + static int NUM_CASES = GRID_SIZE * GRID_SIZE; + + private static final int B_SIZE_FACTOR = 20; + private static final GeometryFactory factory = new GeometryFactory(); + + private Geometry geomA; + + private Geometry[] geomB; + + public RelateNGPolygonLinesOverlappingPerfTest(String name) { + super(name); + setRunSize(new int[] { 100, 1000, 10000, 100000, + 200000 }); + //setRunSize(new int[] { 200000 }); + setRunIterations(N_ITER); + } + + public void setUp() + { + System.out.println("RelateNG Polygon overlapping Lines perf test"); + System.out.println("SineStar: origin: (" + + ORG_X + ", " + ORG_Y + ") size: " + SIZE + + " # arms: " + N_ARMS + " arm ratio: " + ARM_RATIO); + System.out.println("# Iterations: " + N_ITER); + System.out.println("# B geoms: " + NUM_CASES); + } + + public void startRun(int npts) + { + Geometry sineStar = SineStarFactory.create(new Coordinate(ORG_X, ORG_Y), SIZE, npts, N_ARMS, ARM_RATIO); + geomA = sineStar; + + int nptsB = npts * B_SIZE_FACTOR / NUM_CASES; + if (nptsB < 10 ) nptsB = 10; + + geomB = createSineStarGrid(NUM_CASES, nptsB); + //geomB = createCircleGrid(NUM_CASES, nptsB); + + System.out.println("\n------- Running with A: polygon # pts = " + npts + + " B # pts = " + nptsB + " x " + NUM_CASES + " lines"); + + /* + if (npts == 999) { + System.out.println(geomA); + + for (Geometry g : geomB) { + System.out.println(g); + } + } +*/ + } + + public void runContainsOld() + { + for (Geometry b : geomB) { + geomA.contains(b); + } + } + + public void runContainsOldPrep() + { + PreparedGeometry pgA = PreparedGeometryFactory.prepare(geomA); + for (Geometry b : geomB) { + pgA.contains(b); + } + } + + public void runContainsNG() + { + for (Geometry b : geomB) { + RelateNG.relate(geomA, b, RelatePredicate.contains()); + } + } + + public void runContainsNGPrep() + { + RelateNG rng = RelateNG.prepare(geomA); + for (Geometry b : geomB) { + rng.evaluate(b, RelatePredicate.contains()); + } + } + + private Geometry[] createSineStarGrid(int nGeoms, int npts) { + Geometry[] geoms = new Geometry[ NUM_CASES ]; + int index = 0; + for (int i = 0; i < GRID_SIZE; i++) { + for (int j = 0; j < GRID_SIZE; j++) { + double x = GRID_CELL_SIZE/2 + i * GRID_CELL_SIZE; + double y = GRID_CELL_SIZE/2 + j * GRID_CELL_SIZE; + Geometry geom = SineStarFactory.create(new Coordinate(x, y), GRID_CELL_SIZE, npts, N_ARMS, ARM_RATIO); + geoms[index++] = geom.getBoundary(); + } + } + return geoms; + } + + private Geometry[] createCircleGrid(int nGeoms, int npts) { + Geometry[] geoms = new Geometry[ NUM_CASES ]; + int index = 0; + for (int i = 0; i < GRID_SIZE; i++) { + for (int j = 0; j < GRID_SIZE; j++) { + double x = GRID_CELL_SIZE/2 + i * GRID_CELL_SIZE; + double y = GRID_CELL_SIZE/2 + j * GRID_CELL_SIZE; + Coordinate p = new Coordinate(x, y); + Geometry geom = factory.createPoint(p).buffer(GRID_CELL_SIZE / 2.0); + geoms[index++] = geom; + } + } + return geoms; + } + + +} diff --git a/modules/tests/src/test/resources/testxml/misc/TestRelateGC.xml b/modules/tests/src/test/resources/testxml/misc/TestRelateGC.xml index f67b789a0a..9312e44140 100644 --- a/modules/tests/src/test/resources/testxml/misc/TestRelateGC.xml +++ b/modules/tests/src/test/resources/testxml/misc/TestRelateGC.xml @@ -574,10 +574,21 @@ GEOMETRYCOLLECTION (POLYGON ((1 9, 5 9, 6 6, 1 5, 1 9), (2 6, 4 8, 6 6, 2 6)), P false false true + + true + true + true + false + false + true + true + false + false + true - GC:AL/A - polygon with overlapping line equal to polygon + GC:AL/A - polygon with line in boundary and interior equal to polygon GEOMETRYCOLLECTION (POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0)), LINESTRING (0 2, 0 5, 5 5)) @@ -596,6 +607,17 @@ GEOMETRYCOLLECTION (POLYGON ((1 9, 5 9, 6 6, 1 5, 1 9), (2 6, 4 8, 6 6, 2 6)), P false false true + + true + true + true + false + false + true + true + false + false + true