From a7ad1ffee47368b3647494439d4f3a8ee6fb2cc7 Mon Sep 17 00:00:00 2001 From: Martin Davis Date: Tue, 30 Jan 2024 15:02:28 -0800 Subject: [PATCH] Imrove GC point locating --- .../relateng/EdgeSegmentIntersector.java | 52 ++++---- .../operation/relateng/RelateGeometry.java | 92 ++++++-------- .../jts/operation/relateng/RelateNG.java | 18 +-- .../relateng/RelatePointLocator.java | 112 +++++++++++------- .../relateng/RelateSegmentString.java | 32 +++++ .../operation/relateng/TopologyBuilder.java | 75 ++++++------ .../operation/relateng/RelateNGGCTest.java | 6 + .../relateng/RelatePointLocatorTest.java | 76 ++++++++++++ .../resources/testxml/misc/TestRelateGC.xml | 22 ++++ 9 files changed, 317 insertions(+), 168 deletions(-) create mode 100644 modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateSegmentString.java create mode 100644 modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelatePointLocatorTest.java diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/EdgeSegmentIntersector.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/EdgeSegmentIntersector.java index bb873e0a34..9f48b45d05 100644 --- a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/EdgeSegmentIntersector.java +++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/EdgeSegmentIntersector.java @@ -13,9 +13,9 @@ import org.locationtech.jts.algorithm.RobustLineIntersector; import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Dimension; import org.locationtech.jts.noding.SegmentIntersector; import org.locationtech.jts.noding.SegmentString; -import org.locationtech.jts.noding.SegmentStringUtil; class EdgeSegmentIntersector implements SegmentIntersector { @@ -61,29 +61,36 @@ private void processSelfIntersection(SegmentString ss0, int segIndex0, SegmentSt if (! li.hasIntersection()) return; + //-- both segStrings are in same geom boolean isA = isA(ss0); + int dim0 = dim(ss0); + int dim1 = dim(ss1); if (li.getIntersectionNum() == 2) { //-- intersection is collinear - topoBuilder.addSelfIntersectionCollinear(isA, p00, p01, p10, p11, li.getIntersection(0), li.getIntersection(1)); + topoBuilder.addSelfIntersectionCollinear(isA, dim0, p00, p01, dim1, p10, p11, li.getIntersection(0), li.getIntersection(1)); } else if (li.isProper()) { //-- intersection is proper - topoBuilder.addSelfIntersectionProper(isA, p00, p01, p10, p11, li.getIntersection(0)); + topoBuilder.addSelfIntersectionProper(isA, dim0, p00, p01, dim1, p10, p11, li.getIntersection(0)); } else { //-- non-proper intersection (at least one segment intersects at endpoint) - addSelfIntersectionNonProper(isA, ss0, segIndex0, ss1, segIndex1, li.getIntersection(0)); + addSelfIntersectionNonProper(isA, dim0, ss0, segIndex0, dim1, ss1, segIndex1, li.getIntersection(0)); } } - private boolean isAB(SegmentString ss0, SegmentString ss1) { - RelateGeometry geom0 = (RelateGeometry) ss0.getData(); - RelateGeometry geom1 = (RelateGeometry) ss1.getData(); + private int dim(SegmentString ss) { + return ((RelateSegmentString) ss).getDimension(); + } + + private static boolean isAB(SegmentString ss0, SegmentString ss1) { + RelateGeometry geom0 = ((RelateSegmentString) ss0).getGeometry(); + RelateGeometry geom1 = ((RelateSegmentString) ss1).getGeometry(); return geom0 != geom1; } - private boolean isA(SegmentString ss0) { - return topoBuilder.isA((RelateGeometry) ss0.getData()); + private boolean isA(SegmentString ss) { + return topoBuilder.isA(((RelateSegmentString) ss).getGeometry()); } private void processIntersectionAB(SegmentString ssA, int segIndexA, SegmentString ssB, int segIndexB) { @@ -98,45 +105,48 @@ private void processIntersectionAB(SegmentString ssA, int segIndexA, SegmentStri if (! li.hasIntersection()) return; + int dimA = dim(ssA); + int dimB = dim(ssB); + if (li.getIntersectionNum() == 2) { //-- intersection is collinear - topoBuilder.addIntersectionCollinear(a0, a1, b0, b1, li.getIntersection(0), li.getIntersection(1)); + topoBuilder.addABIntersectionCollinear(dimA, a0, a1, dimB, b0, b1, li.getIntersection(0), li.getIntersection(1)); } else if (li.isProper()) { //-- intersection is proper - topoBuilder.addIntersectionProper(a0, a1, b0, b1, li.getIntersection(0)); + topoBuilder.addABIntersectionProper(dimA, a0, a1, dimB, b0, b1, li.getIntersection(0)); } else { //-- non-proper intersection (at least one segment intersects at endpoint) - addIntersectionNonProper(ssA, segIndexA, ssB, segIndexB, li.getIntersection(0)); + addABIntersectionNonProper(dimA, ssA, segIndexA, dimB, ssB, segIndexB, li.getIntersection(0)); } } - private void addSelfIntersectionNonProper(boolean isA, SegmentString ss0, int segIndex0, SegmentString ss1, int segIndex1, Coordinate intPt) { + private void addSelfIntersectionNonProper(boolean isA, int dim0, SegmentString ss0, int segIndex0, int dim1, SegmentString ss1, int segIndex1, Coordinate intPt) { //-- this handles A/L, L/A, and L/L - addIntersectionEdges(isA, ss0, segIndex0, intPt); - addIntersectionEdges(isA, ss1, segIndex1, intPt); + addIntersectionEdges(isA, dim0, ss0, segIndex0, intPt); + addIntersectionEdges(isA, dim1, ss1, segIndex1, intPt); } - private void addIntersectionNonProper(SegmentString ssA, int segIndexA, SegmentString ssB, int segIndexB, Coordinate intPt) { + private void addABIntersectionNonProper(int dimA, SegmentString ssA, int segIndexA, int dimB, SegmentString ssB, int segIndexB, Coordinate intPt) { if (topoBuilder.isAreaArea()) { addNonProperAreaArea(ssA, segIndexA, ssB, segIndexB, intPt); return; } //-- this handles A/L, L/A, and L/L - addIntersectionEdges(RelateGeometry.GEOM_A, ssA, segIndexA, intPt); - addIntersectionEdges(RelateGeometry.GEOM_B, ssB, segIndexB, intPt); + addIntersectionEdges(RelateGeometry.GEOM_A, dimA, ssA, segIndexA, intPt); + addIntersectionEdges(RelateGeometry.GEOM_B, dimB, ssB, segIndexB, intPt); //TODO: more logic required? //TODO: move to topoBuilder topoBuilder.addEdgeIntersectionNode(intPt); } - private void addIntersectionEdges(boolean isA, SegmentString ss, int segIndex, Coordinate intPt) { + private void addIntersectionEdges(boolean isA, int dim, SegmentString ss, int segIndex, Coordinate intPt) { Coordinate nextVertex = nextVertex(ss, segIndex, intPt); - topoBuilder.addEdge(isA, intPt, nextVertex, RelateEdge.IS_FORWARD); + topoBuilder.addEdge(isA, dim, intPt, nextVertex, RelateEdge.IS_FORWARD); Coordinate prevVertex = prevVertex(ss, segIndex, intPt); - topoBuilder.addEdge(isA, intPt, prevVertex, RelateEdge.IS_REVERSE); + topoBuilder.addEdge(isA, dim, intPt, prevVertex, RelateEdge.IS_REVERSE); } /** 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 09c967921d..1eacb7d3c9 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 @@ -128,20 +128,12 @@ public boolean isZeroLength() { } public int locateNode(Coordinate pt) { - switch (dim) { - case Dimension.P: - return Location.INTERIOR; - case Dimension.L: - return lineBoundary.isBoundary(pt) ? Location.BOUNDARY : Location.INTERIOR; - case Dimension.A: - return Location.BOUNDARY; - } - // Assert: should never reach here - return Location.NONE; + RelatePointLocator locator = new RelatePointLocator(geom); + return locator.locateNode(pt); } public int locate(Coordinate pt) { - //TODO: provide an indexed option + //TODO: provide an indexed option - or just always use indexed? RelatePointLocator locator = new RelatePointLocator(geom); int loc = locator.locate(pt); /* @@ -150,7 +142,7 @@ public int locate(Coordinate pt) { throw new IllegalStateException("locations do not match"); } */ - return RelatePointLocator.getLocation(loc); + return loc; } public int locateWithDim(Coordinate pt) { @@ -189,7 +181,7 @@ private int locateOnLine(Coordinate pt) { return lineLocator.locate(pt, geom); } - public int locateOnArea(Coordinate pt) { + private int locateOnArea(Coordinate pt) { //TODO: only create index after N queries if (areaLocator == null) { if (isPrepared) { @@ -201,6 +193,33 @@ public int locateOnArea(Coordinate pt) { } return areaLocator.locate(pt); } + + public boolean isPointsOrPolygons() { + return geom instanceof Point + || geom instanceof MultiPoint + || geom instanceof Polygon + || geom instanceof MultiPolygon; + } + + public boolean hasAnyPoints() { + return getDimension() == Dimension.P; + } + + public Set getLineEnds() { + return lineBoundary.getEndPoints(); + } + + public int locateLineEnd(Coordinate p) { + return lineBoundary.isBoundary(p) ? Location.BOUNDARY : Location.INTERIOR; + } + + public boolean isEmpty() { + return geom.isEmpty(); + } + + public boolean hasBoundary() { + return lineBoundary.hasBoundary(); + } private Set createUniquePoints() { //TODO: make more efficient (ie by scanning geometry?) @@ -241,18 +260,6 @@ public List getPoints() { return ptList; } - public List extractSegmentStringsOLD(Envelope env) { - List lines = LinearComponentExtracter.getLines(geom); - List segStrings = new ArrayList(); - for (LineString line : lines) { - if (env == null || env.intersects(line.getEnvelopeInternal())) { - SegmentString ss = new BasicSegmentString(line.getCoordinates(), null); - segStrings.add(ss); - } - } - return segStrings; - } - public List extractSegmentStrings(Envelope env) { List segStrings = new ArrayList(); for (int i = 0; i < geom.getNumGeometries(); i++) { @@ -270,7 +277,7 @@ private void extractSegmentStrings(Geometry geom, Envelope env, List getLineEnds() { - return lineBoundary.getEndPoints(); - } - - public int locateLineEnd(Coordinate p) { - return lineBoundary.isBoundary(p) ? Location.BOUNDARY : Location.INTERIOR; - } - - public boolean isEmpty() { - return geom.isEmpty(); - } - - public boolean hasBoundary() { - return lineBoundary.hasBoundary(); - } - public String toString() { return geom.toString(); } diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateNG.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateNG.java index a2f7d9d219..f59fceaff5 100644 --- a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateNG.java +++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateNG.java @@ -222,10 +222,8 @@ private boolean computePoints(RelateGeometry geom, boolean isA, RelateGeometry g Coordinate p = pt.getCoordinate(); //TODO: break when all possible topo locations have been found int locDimTarget = geomTarget.locateWithDim(p); - int locTarget = RelatePointLocator.getLocation(locDimTarget); - int dimTarget = locTarget == Location.EXTERIOR - ? topoBuilder.dim(! isA) - : RelatePointLocator.dimension(locDimTarget); + int locTarget = RelatePointLocator.location(locDimTarget); + int dimTarget = RelatePointLocator.dimension(locDimTarget, topoBuilder.dim(! isA)); topoBuilder.addPointOnGeometry(isA, locTarget, dimTarget, p); if (topoBuilder.isResultKnown()) { return true; @@ -245,10 +243,8 @@ private boolean computeLineEnds(RelateGeometry geom, boolean isA, RelateGeometry //TODO: break when all possible locations have been found? int locLineEnd = geom.locateLineEnd(p); int locDimTarget = geomTarget.locateWithDim(p); - int locTarget = RelatePointLocator.getLocation(locDimTarget); - int dimTarget = locTarget == Location.EXTERIOR - ? topoBuilder.dim(! isA) - : RelatePointLocator.dimension(locDimTarget); + int locTarget = RelatePointLocator.location(locDimTarget); + int dimTarget = RelatePointLocator.dimension(locDimTarget, topoBuilder.dim(! isA)); topoBuilder.addLineEndOnGeometry(isA, locLineEnd, locTarget, dimTarget, p); if (topoBuilder.isResultKnown()) { return true; @@ -289,10 +285,8 @@ private boolean computeAreaNodes(RelateGeometry geom, boolean isA, RelateGeometr private void computeAreaNode(LinearRing ring, boolean isA, RelateGeometry geomTarget, TopologyBuilder topoBuilder) { Coordinate pt = ring.getCoordinate(); int locDimTarget = geomTarget.locateWithDim(pt); - int locTarget = RelatePointLocator.getLocation(locDimTarget); - int dimTarget = locTarget == Location.EXTERIOR - ? topoBuilder.dim(! isA) - : RelatePointLocator.dimension(locDimTarget); + int locTarget = RelatePointLocator.location(locDimTarget); + int dimTarget = RelatePointLocator.dimension(locDimTarget, topoBuilder.dim(! isA)); //-- don't need to test against points, since they have already been tested against the area topoBuilder.addAreaVertex(isA, locTarget, dimTarget, pt); } diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelatePointLocator.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelatePointLocator.java index a1da8f26aa..12f41cc686 100644 --- a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelatePointLocator.java +++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelatePointLocator.java @@ -47,8 +47,8 @@ class RelatePointLocator { public static final int AREA_INTERIOR = 120; public static final int AREA_BOUNDARY = 121; - public static int getLocation(int relateLoc) { - switch (relateLoc) { + public static int location(int dimLoc) { + switch (dimLoc) { case POINT_INTERIOR: case LINE_INTERIOR: case AREA_INTERIOR: @@ -60,8 +60,8 @@ public static int getLocation(int relateLoc) { return Location.EXTERIOR; } - public static int dimension(int relateLoc) { - switch (relateLoc) { + public static int dimension(int dimLoc) { + switch (dimLoc) { case POINT_INTERIOR: return Dimension.P; case LINE_INTERIOR: @@ -74,6 +74,12 @@ public static int dimension(int relateLoc) { return Dimension.FALSE; } + public static int dimension(int dimLoc, int geomDim) { + if (location(dimLoc) == Location.EXTERIOR) + return geomDim; + return dimension(dimLoc); + } + private Geometry geom; private BoundaryNodeRule boundaryRule; @@ -103,33 +109,50 @@ public boolean intersects(Coordinate p) } public int locate(Coordinate p) { - return getLocation(locateWithDim(p)); + return location(locateWithDim(p)); + } + + /** + * Locates a point which is known to be a node of the geometry + * (i.e. a point or on an edge). + * + * @param p the node point to locate + * @return the location of the node point + */ + public int locateNode(Coordinate p) { + return location(locateWithDim(p, true)); + } + + /** + * Computes the topological location ({@link Location}) of a single point + * in a Geometry, as well as the dimension of the geometry element the point + * is located in (if not in the Exterior). + * It handles both single-element and multi-element Geometries. + * The algorithm for multi-part Geometries + * takes into account the SFS Boundary Determination Rule. + * + * @param p the point to locate + * @return the {@link Location} of the point relative to the input Geometry + */ + public int locateWithDim(Coordinate p) { + return locateWithDim(p, false); } /** - * Computes the topological relationship ({@link Location}) of a single point - * to a Geometry. - * It handles both single-element - * and multi-element Geometries. + * Computes the topological location ({@link Location}) of a single point + * in a Geometry, as well as the dimension of the geometry element the point + * is located in (if not in the Exterior). + * It handles both single-element and multi-element Geometries. * The algorithm for multi-part Geometries * takes into account the SFS Boundary Determination Rule. * * @return the {@link Location} of the point relative to the input Geometry */ - public int locateWithDim(Coordinate p) + private int locateWithDim(Coordinate p, boolean isNode) { if (geom.isEmpty()) return Location.EXTERIOR; - - /* - if (geom instanceof LineString) { - return locateOnLineString(p, (LineString) geom); - } - else if (geom instanceof Polygon) { - return locateInPolygon(p, (Polygon) geom); - } -*/ - computeLocation(p, geom); + computeLocation(p, isNode, geom); if (elementLocation == Location.EXTERIOR) return Location.EXTERIOR; @@ -153,7 +176,7 @@ else if (geom instanceof Polygon) { return Location.EXTERIOR; } - private void computeLocation(Coordinate p, Geometry geom) + private void computeLocation(Coordinate p, boolean isNode, Geometry geom) { if (geom.isEmpty()) return; @@ -161,24 +184,24 @@ private void computeLocation(Coordinate p, Geometry geom) if (geom instanceof Point) { updateLocationInfo(locateOnPoint(p, (Point) geom), Dimension.P); } - if (geom instanceof LineString) { - updateLocationInfo(locateOnLineString(p, (LineString) geom), Dimension.L); + else if (geom instanceof LineString) { + updateLocationInfo(locateOnLineString(p, isNode, (LineString) geom), Dimension.L); } else if (geom instanceof Polygon) { - updateLocationInfo(locateInPolygon(p, (Polygon) geom), Dimension.A); + updateLocationInfo(locateInPolygon(p, isNode, (Polygon) geom), Dimension.A); } else if (geom instanceof MultiLineString) { MultiLineString ml = (MultiLineString) geom; for (int i = 0; i < ml.getNumGeometries(); i++) { LineString l = (LineString) ml.getGeometryN(i); - updateLocationInfo(locateOnLineString(p, l), Dimension.L); + updateLocationInfo(locateOnLineString(p, isNode, l), Dimension.L); } } else if (geom instanceof MultiPolygon) { MultiPolygon mpoly = (MultiPolygon) geom; for (int i = 0; i < mpoly.getNumGeometries(); i++) { Polygon poly = (Polygon) mpoly.getGeometryN(i); - updateLocationInfo(locateInPolygon(p, poly), Dimension.A); + updateLocationInfo(locateInPolygon(p, isNode, poly), Dimension.A); } } else if (geom instanceof GeometryCollection) { @@ -186,7 +209,7 @@ else if (geom instanceof GeometryCollection) { while (geomi.hasNext()) { Geometry g2 = (Geometry) geomi.next(); if (g2 != geom) - computeLocation(p, g2); + computeLocation(p, isNode, g2); } } } @@ -196,7 +219,7 @@ private void updateLocationInfo(int loc, int dimension) if (loc == Location.EXTERIOR) { return; } - + //-- locations in same or higher dimensions take precedence if (dimension < elementDim) return; @@ -225,35 +248,33 @@ private int locateOnPoint(Coordinate p, Point pt) return Location.EXTERIOR; } - private int locateOnLineString(Coordinate p, LineString l) + private int locateOnLineString(Coordinate p, boolean isNode, LineString l) { - // bounding-box check - if (! l.getEnvelopeInternal().intersects(p)) return Location.EXTERIOR; - CoordinateSequence seq = l.getCoordinateSequence(); if (p.equals(seq.getCoordinate(0)) || p.equals(seq.getCoordinate(seq.size() - 1)) ) { int boundaryCount = l.isClosed() ? 2 : 1; - int loc = boundaryRule.isInBoundary(boundaryCount) ? Location.BOUNDARY : Location.INTERIOR; + int loc = boundaryRule.isInBoundary(boundaryCount) + ? Location.BOUNDARY : Location.INTERIOR; return loc; } + if (isNode) + return Location.INTERIOR; + + // bounding-box check + if (! l.getEnvelopeInternal().intersects(p)) + return Location.EXTERIOR; + if (PointLocation.isOnLine(p, seq)) { return Location.INTERIOR; } return Location.EXTERIOR; } - private int locateInPolygonRing(Coordinate p, LinearRing ring) - { - // bounding-box check - if (! ring.getEnvelopeInternal().intersects(p)) return Location.EXTERIOR; - - return PointLocation.locateInRing(p, ring.getCoordinates()); - } - - private int locateInPolygon(Coordinate p, Polygon poly) + private int locateInPolygon(Coordinate p, boolean isNode, Polygon poly) { if (poly.isEmpty()) return Location.EXTERIOR; + //if (isNode) return Location.BOUNDARY; LinearRing shell = poly.getExteriorRing(); @@ -270,5 +291,12 @@ private int locateInPolygon(Coordinate p, Polygon poly) return Location.INTERIOR; } + private int locateInPolygonRing(Coordinate p, LinearRing ring) + { + // bounding-box check + if (! ring.getEnvelopeInternal().intersects(p)) + return Location.EXTERIOR; + return PointLocation.locateInRing(p, ring.getCoordinates()); + } } diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateSegmentString.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateSegmentString.java new file mode 100644 index 0000000000..47af4d4b99 --- /dev/null +++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateSegmentString.java @@ -0,0 +1,32 @@ +package org.locationtech.jts.operation.relateng; + +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.noding.BasicSegmentString; + +class RelateSegmentString extends BasicSegmentString { + + private int dimension; + private RelateGeometry geometry; + + public RelateSegmentString(Coordinate[] pts, int dimension, RelateGeometry geometry) { + super(pts, null); + this.dimension = dimension; + this.geometry = geometry; + } + + /** + * Gets the dimension of the parent geometry element containing this edge. + * Edges from mixed GeometryCollections may + * be either Dimension.L or Dimension.A. + * + * @return the dimension of the parent + */ + public int getDimension() { + return dimension; + } + + public RelateGeometry getGeometry() { + return geometry; + } + +} diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyBuilder.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyBuilder.java index 3254f217fb..5e389a7d3c 100644 --- a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyBuilder.java +++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyBuilder.java @@ -47,7 +47,6 @@ public TopologyBuilder(TopologyPredicate predicate, RelateGeometry geomA, Relate */ private void initExteriorDims() { int dimAEff = geomA.getDimensionEffective(); - //int dimABdyEff = geomA.getEffectiveDimension(); int dimBEff = geomB.getDimensionEffective(); /** @@ -104,7 +103,6 @@ private void initExteriorEmpty(boolean geomNonEmpty) { updateDim(geomNonEmpty, Location.BOUNDARY, Location.EXTERIOR, Dimension.L); break; } - } public int dim(boolean isA) { @@ -168,7 +166,7 @@ private RelateNode getNode(Coordinate intPt) { return node; } - public void addIntersectionProper(Coordinate a0, Coordinate a1, Coordinate b0, Coordinate b1, Coordinate intPt) { + public void addABIntersectionProper(int dimA, Coordinate a0, Coordinate a1, int dimB, Coordinate b0, Coordinate b1, Coordinate intPt) { if (dimA == 2 && dimB == 2) { //- a proper edge intersection is an edge cross. addAreaAreaIntersection(true, a0, a1, b0, b1, intPt); @@ -177,7 +175,7 @@ else if (dimA == 2 && dimB == 1) { addAreaLineCross(RelateGeometry.GEOM_A, a0, a1, b0, b1, intPt); } else if (dimA == 1 && dimB == 2) { - addAreaLineCross(RelateGeometry.GEOM_B, a0, a1, b0, b1, intPt); + addAreaLineCross(RelateGeometry.GEOM_B, b0, b1, a0, a1, intPt); } else if (dimA == 1 && dimB == 1) { addLineLineCross(a0, a1, b0, b1, intPt); @@ -188,14 +186,14 @@ else if (dimA == 1 && dimB == 1) { } public void addSelfIntersectionProper(boolean isA, - Coordinate p00, Coordinate p01, - Coordinate p10, Coordinate p11, + int dim0, Coordinate p00, Coordinate p01, + int dim1, Coordinate p10, Coordinate p11, Coordinate intPt) { //-- create a node at the crossing - addEdge(isA, intPt, p00, RelateEdge.IS_REVERSE); - addEdge(isA, intPt, p01, RelateEdge.IS_FORWARD); - addEdge(isA, intPt, p10, RelateEdge.IS_REVERSE); - addEdge(isA, intPt, p11, RelateEdge.IS_FORWARD); + addEdge(isA, dim0, intPt, p00, RelateEdge.IS_REVERSE); + addEdge(isA, dim0, intPt, p01, RelateEdge.IS_FORWARD); + addEdge(isA, dim1, intPt, p10, RelateEdge.IS_REVERSE); + addEdge(isA, dim1, intPt, p11, RelateEdge.IS_FORWARD); } private void addLineLineCross( @@ -205,28 +203,31 @@ private void addLineLineCross( //-- create a node at the crossing addEdgeIntersectionNode(intPt); - addEdge(RelateGeometry.GEOM_A, intPt, a0, RelateEdge.IS_REVERSE); - addEdge(RelateGeometry.GEOM_A, intPt, a1, RelateEdge.IS_FORWARD); - addEdge(RelateGeometry.GEOM_B, intPt, b0, RelateEdge.IS_REVERSE); - addEdge(RelateGeometry.GEOM_B, intPt, b1, RelateEdge.IS_FORWARD); + addEdge(RelateGeometry.GEOM_A, Dimension.L, intPt, a0, RelateEdge.IS_REVERSE); + addEdge(RelateGeometry.GEOM_A, Dimension.L, intPt, a1, RelateEdge.IS_FORWARD); + addEdge(RelateGeometry.GEOM_B, Dimension.L, intPt, b0, RelateEdge.IS_REVERSE); + addEdge(RelateGeometry.GEOM_B, Dimension.L, intPt, b1, RelateEdge.IS_FORWARD); } - private void addAreaLineCross(boolean geomArea, Coordinate a0, Coordinate a1, Coordinate b0, Coordinate b1, Coordinate intPt) { + private void addAreaLineCross(boolean geomArea, Coordinate a0, Coordinate a1, Coordinate l0, Coordinate l1, Coordinate intPt) { /** * A proper crossing of a line and and area * provides limited topological information, * since the area edge intersection point * may also be a node of a hole, or of another shell, or both. - * Full topology is determined when the node topology is computed. + * Full topology is determined when the node topology is evaluated. */ - int locLine = getGeometry(! geomArea).locateNode(intPt); - updateDim(geomArea, Location.BOUNDARY, locLine, Dimension.P); + boolean geomLine = ! geomArea; + int locLine = getGeometry(geomLine).locateNode(intPt); + //TODO: handle mixed GC (area node may be in interior) + int locArea = Location.BOUNDARY; + updateDim(geomArea, locArea, locLine, Dimension.P); //-- create a node to allow full topology computation - addEdge(RelateGeometry.GEOM_A, intPt, a0, RelateEdge.IS_REVERSE); - addEdge(RelateGeometry.GEOM_A, intPt, a1, RelateEdge.IS_FORWARD); - addEdge(RelateGeometry.GEOM_B, intPt, b0, RelateEdge.IS_REVERSE); - addEdge(RelateGeometry.GEOM_B, intPt, b1, RelateEdge.IS_FORWARD); + addEdge(geomArea, Dimension.A, intPt, a0, RelateEdge.IS_REVERSE); + addEdge(geomArea, Dimension.A, intPt, a1, RelateEdge.IS_FORWARD); + addEdge(geomLine, Dimension.L, intPt, l0, RelateEdge.IS_REVERSE); + addEdge(geomLine, Dimension.L, intPt, l1, RelateEdge.IS_FORWARD); } public void addAreaAreaIntersection(boolean isProper, Coordinate a0, Coordinate a2, Coordinate b0, @@ -261,26 +262,26 @@ public void addAreaAreaIntersection(boolean isProper, Coordinate a0, Coordinate //System.out.println(node); } - public void addIntersectionCollinear(Coordinate a0, Coordinate a1, Coordinate b0, Coordinate b1, Coordinate int0, Coordinate int1) { - addEdgesAtNode(RelateGeometry.GEOM_A, a0, a1, int0); - addEdgesAtNode(RelateGeometry.GEOM_A, a0, a1, int1); - addEdgesAtNode(RelateGeometry.GEOM_B, b0, b1, int0); - addEdgesAtNode(RelateGeometry.GEOM_B, b0, b1, int1); + public void addABIntersectionCollinear(int dimA, Coordinate a0, Coordinate a1, int dimB, Coordinate b0, Coordinate b1, Coordinate int0, Coordinate int1) { + addEdgesAtNode(RelateGeometry.GEOM_A, dimA, a0, a1, int0); + addEdgesAtNode(RelateGeometry.GEOM_A, dimA, a0, a1, int1); + addEdgesAtNode(RelateGeometry.GEOM_B, dimB, b0, b1, int0); + addEdgesAtNode(RelateGeometry.GEOM_B, dimB, b0, b1, int1); } - public void addSelfIntersectionCollinear(boolean isA, Coordinate a0, Coordinate a1, Coordinate b0, Coordinate b1, Coordinate int0, Coordinate int1) { - addEdgesAtNode(isA, a0, a1, int0); - addEdgesAtNode(isA, a0, a1, int1); - addEdgesAtNode(isA, b0, b1, int0); - addEdgesAtNode(isA, b0, b1, int1); + public void addSelfIntersectionCollinear(boolean isA, int dim0, Coordinate a0, Coordinate a1, int dim1, Coordinate b0, Coordinate b1, Coordinate int0, Coordinate int1) { + addEdgesAtNode(isA, dim0, a0, a1, int0); + addEdgesAtNode(isA, dim0, a0, a1, int1); + addEdgesAtNode(isA, dim1, b0, b1, int0); + addEdgesAtNode(isA, dim1, b0, b1, int1); } - private void addEdgesAtNode(boolean isA, Coordinate p0, Coordinate p1, Coordinate intPt) { + private void addEdgesAtNode(boolean isA, int dim, Coordinate p0, Coordinate p1, Coordinate intPt) { if (! intPt.equals2D(p1)) { - addEdge(isA, intPt, p1, RelateEdge.IS_FORWARD); + addEdge(isA, dim, intPt, p1, RelateEdge.IS_FORWARD); } if (! intPt.equals2D(p0)) { - addEdge(isA, intPt, p0, RelateEdge.IS_REVERSE); + addEdge(isA, dim, intPt, p0, RelateEdge.IS_REVERSE); } } @@ -447,7 +448,7 @@ private void evaluateNode(RelateNode node) { } } - public void addEdge(boolean isA, Coordinate nodePt, Coordinate dirPt, boolean isForward) { + public void addEdge(boolean isA, int dim, Coordinate nodePt, Coordinate dirPt, boolean isForward) { //-- vertices may be null if intersection is at end of a line if (nodePt == null || dirPt == null) return; @@ -457,7 +458,7 @@ public void addEdge(boolean isA, Coordinate nodePt, Coordinate dirPt, boolean is //Assert: nodePt != p RelateNode node = getNode(nodePt); - if (dim(isA) == Dimension.A) { + if (dim == Dimension.A) { node.addAreaEdge(dirPt, isA, isForward); } else { 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 b003715861..2edccfbc85 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 @@ -46,4 +46,10 @@ public void testP_GPL() { checkTouches(a, b, true); checkEquals(a, b, false); } + + public void testL_GA() { + String a = "LINESTRING (3 7, 7 3)"; + String b = "GEOMETRYCOLLECTION (POLYGON ((1 9, 7 9, 7 3, 1 3, 1 9)), POLYGON ((9 1, 3 1, 3 7, 9 7, 9 1)))"; + checkRelate(a, b, "1FF0FF212"); + } } diff --git a/modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelatePointLocatorTest.java b/modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelatePointLocatorTest.java new file mode 100644 index 0000000000..ebc2b5f682 --- /dev/null +++ b/modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelatePointLocatorTest.java @@ -0,0 +1,76 @@ +package org.locationtech.jts.operation.relateng; + +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.Location; + +import junit.textui.TestRunner; +import test.jts.GeometryTestCase; + +public class RelatePointLocatorTest extends GeometryTestCase { + + public static void main(String args[]) { + TestRunner.run(RelatePointLocatorTest.class); + } + + public RelatePointLocatorTest(String name) { + super(name); + } + + String gcPLA = "GEOMETRYCOLLECTION (POINT (1 1), POINT (2 1), LINESTRING (3 1, 3 9), LINESTRING (4 1, 5 4, 7 1, 4 1), LINESTRING (12 12, 14 14), POLYGON ((6 5, 6 9, 9 9, 9 5, 6 5)), POLYGON ((10 10, 10 16, 16 16, 16 10, 10 10)), POLYGON ((11 11, 11 17, 17 17, 17 11, 11 11)), POLYGON ((12 12, 12 16, 16 16, 16 12, 12 12)))"; + + public void testPoint() { + //String wkt = "GEOMETRYCOLLECTION (POINT(0 0), POINT(1 1))"; + checkLocation(gcPLA, 1, 1, RelatePointLocator.POINT_INTERIOR); + checkLocation(gcPLA, 0, 1, Location.EXTERIOR); + } + + public void testPointInLine() { + checkLocation(gcPLA, 3, 8, RelatePointLocator.LINE_INTERIOR); + } + + public void testPointInArea() { + checkLocation(gcPLA, 8, 8, RelatePointLocator.AREA_INTERIOR); + } + + public void testLine() { + checkLocation(gcPLA, 3, 3, RelatePointLocator.LINE_INTERIOR); + checkLocation(gcPLA, 3, 1, RelatePointLocator.LINE_BOUNDARY); + } + + public void testLineInArea() { + checkLocation(gcPLA, 11, 11, RelatePointLocator.AREA_INTERIOR); + checkLocation(gcPLA, 14, 14, RelatePointLocator.AREA_INTERIOR); + } + + public void testArea() { + checkLocation(gcPLA, 8, 8, RelatePointLocator.AREA_INTERIOR); + checkLocation(gcPLA, 9, 9, RelatePointLocator.AREA_BOUNDARY); + } + + public void testAreaInArea() { + checkLocation(gcPLA, 11, 11, RelatePointLocator.AREA_INTERIOR); + checkLocation(gcPLA, 12, 12, RelatePointLocator.AREA_INTERIOR); + checkLocation(gcPLA, 10, 10, RelatePointLocator.AREA_BOUNDARY); + checkLocation(gcPLA, 16, 16, RelatePointLocator.AREA_INTERIOR); + } + + public void testLineNode() { + //checkNodeLocation(gcPLA, 12.1, 12.2, Location.INTERIOR); + checkNodeLocation(gcPLA, 3, 1, Location.BOUNDARY); + } + + private void checkLocation(String wkt, double i, double j, int expected) { + Geometry geom = read(wkt); + RelatePointLocator locator = new RelatePointLocator(geom); + int actual = locator.locateWithDim(new Coordinate(i, j)); + assertEquals(expected, actual); + } + + private void checkNodeLocation(String wkt, double i, double j, int expected) { + Geometry geom = read(wkt); + RelatePointLocator locator = new RelatePointLocator(geom); + int actual = locator.locateNode(new Coordinate(i, j)); + assertEquals(expected, actual); + } +} diff --git a/modules/tests/src/test/resources/testxml/misc/TestRelateGC.xml b/modules/tests/src/test/resources/testxml/misc/TestRelateGC.xml index d969d4b693..94092eb5e9 100644 --- a/modules/tests/src/test/resources/testxml/misc/TestRelateGC.xml +++ b/modules/tests/src/test/resources/testxml/misc/TestRelateGC.xml @@ -84,4 +84,26 @@ false + + + L/GC-A - point lies on boundary of GC + + LINESTRING (3 7, 7 3) + + + GEOMETRYCOLLECTION (POLYGON ((1 9, 7 9, 7 3, 1 3, 1 9)), POLYGON ((9 1, 3 1, 3 7, 9 7, 9 1))) + + true + false + true + false + false + false + false + true + false + false + true + +