Skip to content

Commit

Permalink
Fix RelateNG to cache in A-L cases
Browse files Browse the repository at this point in the history
  • Loading branch information
dr-jts committed Nov 24, 2024
1 parent 6c99023 commit 031980f
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,22 +118,38 @@ 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
* where a self-crossing and mutual crossing occur at the same logical location.
* 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:
* <ul>
* <li>A geoms which require self-noding (lines or GCs, except for single-polygon GCs)
* <li>B geoms which are mixed A/L GCs
* </ul>
* 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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}


Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}


}
24 changes: 23 additions & 1 deletion modules/tests/src/test/resources/testxml/misc/TestRelateGC.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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
<test><op name="overlaps" arg1="A" arg2="B"> false </op></test>
<test><op name="touches" arg1="A" arg2="B"> false </op></test>
<test><op name="within" arg1="A" arg2="B"> true </op></test>

<test><op name="contains" arg1="B" arg2="A"> true </op></test>
<test><op name="coveredBy" arg1="B" arg2="A"> true </op></test>
<test><op name="covers" arg1="B" arg2="A"> true </op></test>
<test><op name="crosses" arg1="B" arg2="A"> false </op></test>
<test><op name="disjoint" arg1="B" arg2="A"> false </op></test>
<test><op name="equalsTopo" arg1="B" arg2="A"> true </op></test>
<test><op name="intersects" arg1="B" arg2="A"> true </op></test>
<test><op name="overlaps" arg1="B" arg2="A"> false </op></test>
<test><op name="touches" arg1="B" arg2="A"> false </op></test>
<test><op name="within" arg1="B" arg2="A"> true </op></test>
</case>

<case>
<desc>GC:AL/A - polygon with overlapping line equal to polygon </desc>
<desc>GC:AL/A - polygon with line in boundary and interior equal to polygon </desc>
<a>
GEOMETRYCOLLECTION (POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0)),
LINESTRING (0 2, 0 5, 5 5))
Expand All @@ -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
<test><op name="overlaps" arg1="A" arg2="B"> false </op></test>
<test><op name="touches" arg1="A" arg2="B"> false </op></test>
<test><op name="within" arg1="A" arg2="B"> true </op></test>

<test><op name="contains" arg1="B" arg2="A"> true </op></test>
<test><op name="coveredBy" arg1="B" arg2="A"> true </op></test>
<test><op name="covers" arg1="B" arg2="A"> true </op></test>
<test><op name="crosses" arg1="B" arg2="A"> false </op></test>
<test><op name="disjoint" arg1="B" arg2="A"> false </op></test>
<test><op name="equalsTopo" arg1="B" arg2="A"> true </op></test>
<test><op name="intersects" arg1="B" arg2="A"> true </op></test>
<test><op name="overlaps" arg1="B" arg2="A"> false </op></test>
<test><op name="touches" arg1="B" arg2="A"> false </op></test>
<test><op name="within" arg1="B" arg2="A"> true </op></test>
</case>

</run>

0 comments on commit 031980f

Please sign in to comment.