diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/SimplePredicate.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/BasicPredicate.java similarity index 84% rename from modules/core/src/main/java/org/locationtech/jts/operation/relateng/SimplePredicate.java rename to modules/core/src/main/java/org/locationtech/jts/operation/relateng/BasicPredicate.java index 15f91bb7b8..edb63ef10a 100644 --- a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/SimplePredicate.java +++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/BasicPredicate.java @@ -13,7 +13,7 @@ import org.locationtech.jts.geom.Location; -public abstract class SimplePredicate implements TopologyPredicate { +public abstract class BasicPredicate implements TopologyPredicate { private int value = TopologyPredicateValue.UNKNOWN; @@ -21,6 +21,16 @@ public boolean isSelfNodingRequired() { return false; } + @Override + public boolean isKnown() { + return TopologyPredicateValue.isKnown(value); + } + + @Override + public boolean value() { + return TopologyPredicateValue.toBoolean(value); + } + /** * Updates the predicate value to the given state * if it is currently unknown. @@ -28,24 +38,16 @@ public boolean isSelfNodingRequired() { * @param val the predicate value to update */ protected void updateValue(boolean val) { - //-- don't change already-known value - if (isKnown()) - return; - value = TopologyPredicateValue.toValue(val); - } - - @Override - public int valuePartial(int dimA, int dimB) { - return value; - } - - @Override - public boolean value(int dimA, int dimB) { - return TopologyPredicateValue.toBoolean(value); + updateValue(TopologyPredicateValue.toValue(val)); } - public boolean isKnown() { - return TopologyPredicateValue.isKnown(value); + protected void updateValue(int val) { + //-- don't change already-known value + if (isKnown()) + return; + if (TopologyPredicateValue.isKnown(val)) { + value = val; + } } /** diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/IMPredicate.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/IMPredicate.java index ed92491dc3..9ba4496f9c 100644 --- a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/IMPredicate.java +++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/IMPredicate.java @@ -15,20 +15,30 @@ import org.locationtech.jts.geom.IntersectionMatrix; import org.locationtech.jts.geom.Location; -public abstract class IMPredicate implements TopologyPredicate { +public abstract class IMPredicate extends BasicPredicate { static final int DIM_UNKNOWN = Dimension.DONTCARE; + protected int dimA; + protected int dimB; protected IntersectionMatrix intMatrix; public IMPredicate() { - //TODO: add initializer for IntersectionMatrix to a single value? - //intMatrix = new IntersectionMatrix("*********"); intMatrix = new IntersectionMatrix(); //-- E/E is always dim = 2 intMatrix.set(Location.EXTERIOR, Location.EXTERIOR, Dimension.A); } + @Override + public void init(int dimA, int dimB) { + this.dimA = dimA; + this.dimB = dimB; + } + + public boolean isSelfNodingRequired() { + return true; + } + /** * Gets the current state of the IM matrix (which may only be partially complete). * @@ -44,9 +54,14 @@ public void updateDim(int locA, int locB, int dimension) { if (dimension <= intMatrix.get(locA, locB)) return; - intMatrix.set(locA, locB, dimension); + intMatrix.set(locA, locB, dimension); + updateValue( valuePartial()); } + protected int valuePartial() { + return TopologyPredicateValue.UNKNOWN; + } + protected boolean intersectsExteriorOf(boolean isA) { if (isA) { return isIntersects(Location.EXTERIOR, Location.INTERIOR) @@ -81,7 +96,9 @@ public int getDim(int locA, int locB) { /** * Finalizes the matrix by setting UNKNOWN values to appropriate values. */ + @Override public void finish() { + updateValue(valueIM()); //TODO: is this needed? /* for (int ia = 0; ia < 3; ia++) { @@ -93,6 +110,8 @@ public void finish() { */ } + protected abstract boolean valueIM(); + public String toString() { return name() + ": " + intMatrix; } 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 114a833918..5e3720e183 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 @@ -35,10 +35,10 @@ * The algorithm used provides the following: *
    *
  1. Efficient short-circuited evaluation of all predicates - *
  2. Optimized evaluation of repeated predicates against a single geometry - * (via cached spatial indexes) + *
  3. Optimized evaluation of repeated predicates against a single geometry + * via cached spatial indexes (AKA prepared mode) *
  4. Robust computation (since only point-local topology is required) - *
  5. FUTURE Support for {@link BoundaryNodeRule} + *
  6. Support for {@link BoundaryNodeRule} *
  7. FUTURE Support for all GeometryCollection inputs, using union semantics *
  8. FUTURE Support for a distance tolerance to compute approximate predicates *
@@ -111,15 +111,14 @@ public boolean evaluate(TopologyPredicate predicate, Geometry inputB) { int dimA = geomA.getDimension(); int dimB = geomB.getDimension(); - //-- check if predicate is determined by dimension - int dimValue = predicate.valueDimensions(dimA, dimB); - if (TopologyPredicateValue.isKnown(dimValue)) - return TopologyPredicateValue.toBoolean(dimValue); + //-- check if predicate is determined by dimension or envelope + predicate.init(dimA, dimB); + if (predicate.isKnown()) + return predicate.value(); - //-- check if predicate is determined by envelopes - int envValue = predicate.valueEnvelopes(geomA.getEnvelope(), inputB.getEnvelopeInternal()); - if (TopologyPredicateValue.isKnown(envValue)) - return TopologyPredicateValue.toBoolean(envValue); + predicate.init(geomA.getEnvelope(), inputB.getEnvelopeInternal()); + if (predicate.isKnown()) + return predicate.value(); TopologyBuilder topoBuilder = new TopologyBuilder(predicate, geomA, geomB); diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelatePredicate.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelatePredicate.java index 44e0f4941f..8b32f098c5 100644 --- a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelatePredicate.java +++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelatePredicate.java @@ -25,19 +25,16 @@ public RelatePredicate(String mask) { public String name() { return "relate"; } @Override - public int valuePartial(int dimA, int dimB) { - return TopologyPredicateValue.UNKNOWN; - } - - @Override - public boolean value(int dimA, int dimB) { + public boolean valueIM() { if (mask == null) return false; boolean val = intMatrix.matches(mask); + /* if (! val) { - System.out.println( intMatrix + " does not equal mask " + mask); + System.out.println("DEBUG: " + intMatrix + " does not equal mask " + mask); } + */ return val; } 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 e01dbaaed3..c4e7f32a30 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 @@ -30,7 +30,6 @@ class TopologyBuilder { private RelateGeometry geomB; private int dimA; private int dimB; - private int predicateValue; private Map nodeMap = new HashMap(); public TopologyBuilder(TopologyPredicate predicate, RelateGeometry geomA, RelateGeometry geomB) { @@ -41,7 +40,6 @@ public TopologyBuilder(TopologyPredicate predicate, RelateGeometry geomA, Relate this.dimB = geomB.getDimension(); initExteriorDims(); - predicateValue = predicate.valueDimensions(dimA, dimB); } /** @@ -136,7 +134,6 @@ public boolean isSelfNodingRequired() { */ private boolean updateDim(int locA, int locB, int dimension) { predicate.updateDim(locA, locB, dimension); - predicateValue = predicate.valuePartial(dimA, dimB); return true; } @@ -149,19 +146,18 @@ private boolean updateDim(boolean isAB, int loc1, int loc2, int dimension) { } public boolean isResultKnown() { - return TopologyPredicateValue.isKnown(predicateValue); + return predicate.isKnown(); } public boolean getResult() { - return TopologyPredicateValue.toBoolean(predicateValue); + return predicate.value(); } /** - * Finalizes the matrix by setting UNKNOWN values to Dimension.FALSE (empty). + * Finalize the evaluation. */ public void finish() { predicate.finish(); - predicateValue = TopologyPredicateValue.toValue(predicate.value(dimA, dimB)); } private RelateNode getNode(Coordinate intPt) { diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyPredicate.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyPredicate.java index 46420f2913..4f8f704525 100644 --- a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyPredicate.java +++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyPredicate.java @@ -15,8 +15,7 @@ /** * The API for strategy classes implementing - * short-circuited spatial predicates based - * on the DE-9IM topology model, + * short-circuited spatial predicates based on the DE-9IM topology model, * evaluated by {@link RelateNG}. * * @author Martin Davis @@ -43,6 +42,30 @@ default boolean isSelfNodingRequired() { return true; } + /** + * Initializes the predicate for a specific geometric case. + * This may allow the predicate result to become known + * if it can be inferred from the dimensions. + * + * @param dimA the dimension of geometry A + * @param dimB the dimension of geometry B + */ + default void init(int dimA, int dimB) { + + } + + /** + * Initializes the predicate for a specific geometric case. + * This may allow the predicate result to become known + * if it can be inferred from the envelopes. + * + * @param envA the envelope of geometry A + * @param envB the envelope of geometry B + */ + default void init(Envelope envA, Envelope envB) { + + } + /** * Updates the entry in the DE-9IM intersection matrix * for given {@link Location}s in the input geometries. @@ -61,25 +84,24 @@ default boolean isSelfNodingRequired() { void updateDim(int locA, int locB, int dimension); /** - * Gets the predicate value if it can be determined - * solely from the geometry dimensions. + * Tests if the predicate value is known. * - * @param dimA - * @param dimB - * @return + * @return true if the result is known */ - default int valueDimensions(int dimA, int dimB) { - return TopologyPredicateValue.UNKNOWN; - } - - default int valueEnvelopes(Envelope envA, Envelope envB) { - return TopologyPredicateValue.UNKNOWN; - } - - int valuePartial(int dimA, int dimB); + boolean isKnown(); + /** + * Indicates that the value of the predicate can be finalized + * based on its current state. + */ void finish(); - boolean value(int dimA, int dimB); + /** + * Gets the current value of the predicate result. + * The value is only valid if {@link #isKnown()} is true. + * + * @return the predicate result value + */ + boolean value(); } diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyPredicateFactory.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyPredicateFactory.java index 958bdc8888..9533a1bcf8 100644 --- a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyPredicateFactory.java +++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyPredicateFactory.java @@ -18,9 +18,14 @@ public class TopologyPredicateFactory { public static TopologyPredicate intersects() { - return new SimplePredicate() { + return new BasicPredicate() { public String name() { return "intersects"; } + + @Override + public void init(Envelope envA, Envelope envB) { + updateValue(requireIntersects(envA, envB)); + } @Override public void updateDim(int locA, int locB, int dimension) { @@ -28,11 +33,6 @@ public void updateDim(int locA, int locB, int dimension) { updateValue(true); } } - - @Override - public int valueEnvelopes(Envelope envA, Envelope envB) { - return requireIntersects(envA, envB); - } @Override public void finish() { @@ -43,22 +43,22 @@ public void finish() { } public static TopologyPredicate disjoint() { - return new SimplePredicate() { + return new BasicPredicate() { public String name() { return "disjoint"; } - @Override + @Override + public void init(Envelope envA, Envelope envB) { + updateValue(valueIf(true, envA.disjoint(envB))); + } + + @Override public void updateDim(int locA, int locB, int dimension) { if (isIntersection(locA, locB)) { updateValue(false); } } - @Override - public int valueEnvelopes(Envelope envA, Envelope envB) { - return valueIf(true, envA.disjoint(envB)); - } - @Override public void finish() { //-- no intersecting locations have been found @@ -74,102 +74,105 @@ public static TopologyPredicate contains() { public String name() { return "contains"; } @Override - public int valueDimensions(int dimA, int dimB) { - return require( isDimsCompatibleWithCovers(dimA, dimB) ); + public void init(int dimA, int dimB) { + super.init(dimA, dimB); + updateValue( require( isDimsCompatibleWithCovers(dimA, dimB) )); } - + @Override - public int valueEnvelopes(Envelope envA, Envelope envB) { - return requireCovers(envA, envB); + public void init(Envelope envA, Envelope envB) { + updateValue(requireCovers(envA, envB)); } @Override - public int valuePartial(int dimA, int dimB) { + public int valuePartial() { return valueIf( false, intersectsExteriorOf(RelateGeometry.GEOM_A)); } @Override - public boolean value(int dimA, int dimB) { + public boolean valueIM() { return intMatrix.isContains(); } }; } - public static TopologyPredicate within() { - return new IMPredicate() { - - public String name() { return "within"; } - - @Override - public int valueDimensions(int dimA, int dimB) { - return require( isDimsCompatibleWithCovers(dimB, dimA) ); - } - - @Override - public int valueEnvelopes(Envelope envA, Envelope envB) { - return requireCovers(envB, envA); - } - - @Override - public int valuePartial(int dimA, int dimB) { - return valueIf( false, intersectsExteriorOf(RelateGeometry.GEOM_B)); - } - - @Override - public boolean value(int dimA, int dimB) { - return intMatrix.isWithin(); - } - }; - } - public static TopologyPredicate covers() { return new IMPredicate() { public String name() { return "covers"; } @Override - public int valueDimensions(int dimA, int dimB) { - return require(isDimsCompatibleWithCovers(dimA, dimB)); + public void init(int dimA, int dimB) { + super.init(dimA, dimB); + updateValue( require( isDimsCompatibleWithCovers(dimA, dimB) )); } - + @Override - public int valueEnvelopes(Envelope envA, Envelope envB) { - return requireCovers(envA, envB); + public void init(Envelope envA, Envelope envB) { + updateValue(requireCovers(envA, envB)); } @Override - public int valuePartial(int dimA, int dimB) { + public int valuePartial() { return valueIf( false, intersectsExteriorOf(RelateGeometry.GEOM_A)); } @Override - public boolean value(int dimA, int dimB) { + public boolean valueIM() { return intMatrix.isCovers(); } }; } + + public static TopologyPredicate within() { + return new IMPredicate() { + + public String name() { return "within"; } + + @Override + public void init(int dimA, int dimB) { + super.init(dimA, dimB); + updateValue( require( isDimsCompatibleWithCovers(dimB, dimA) )); + } + + @Override + public void init(Envelope envA, Envelope envB) { + updateValue(requireCovers(envB, envA)); + } + + @Override + public int valuePartial() { + return valueIf( false, intersectsExteriorOf(RelateGeometry.GEOM_B)); + } + + public boolean valueIM() { + return intMatrix.isWithin(); + } + }; + } public static TopologyPredicate coveredBy() { return new IMPredicate() { public String name() { return "coveredBy"; } @Override - public int valueDimensions(int dimA, int dimB) { - return require(isDimsCompatibleWithCovers(dimB, dimA)); + public void init(int dimA, int dimB) { + super.init(dimA, dimB); + updateValue( require( isDimsCompatibleWithCovers(dimB, dimA) )); } - + @Override - public int valueEnvelopes(Envelope envA, Envelope envB) { - return requireCovers(envB, envA); + public void init(Envelope envA, Envelope envB) { + updateValue(requireCovers(envB, envA)); } @Override - public int valuePartial(int dimA, int dimB) { + public int valuePartial() { return valueIf( false, intersectsExteriorOf(RelateGeometry.GEOM_B)); } @Override - public boolean value(int dimA, int dimB) { + public boolean valueIM() { return intMatrix.isCoveredBy(); } }; @@ -180,15 +183,15 @@ public static TopologyPredicate crosses() { public String name() { return "crosses"; } @Override - public int valueDimensions(int dimA, int dimB) { - //-- value is FALSE for P/P and A/A situations + public void init(int dimA, int dimB) { + super.init(dimA, dimB); boolean isBothPointsOrAreas = (dimA == Dimension.P && dimB == Dimension.P) || (dimA == Dimension.A && dimB == Dimension.A); - return require(! isBothPointsOrAreas); + updateValue( require(! isBothPointsOrAreas)); } @Override - public int valuePartial(int dimA, int dimB) { + public int valuePartial() { if (dimA == Dimension.L && dimB == Dimension.L) { //-- L/L interaction can only be dim = 0 if (getDim(Location.INTERIOR, Location.INTERIOR) > Dimension.P) @@ -210,7 +213,7 @@ && isIntersects(Location.EXTERIOR, Location.INTERIOR)) { } @Override - public boolean value(int dimA, int dimB) { + public boolean valueIM() { return intMatrix.isCrosses(dimA, dimB); } }; @@ -221,17 +224,18 @@ public static TopologyPredicate equalsTopo() { public String name() { return "equals"; } @Override - public int valueDimensions(int dimA, int dimB) { - return require(dimA == dimB); + public void init(int dimA, int dimB) { + super.init(dimA, dimB); + updateValue( require(dimA == dimB)); } - + @Override - public int valueEnvelopes(Envelope envA, Envelope envB) { - return requireEquals(envA, envB); + public void init(Envelope envA, Envelope envB) { + updateValue( requireEquals(envA, envB)); } @Override - public int valuePartial(int dimA, int dimB) { + public int valuePartial() { boolean isEitherExteriorIntersects = isIntersects(Location.INTERIOR, Location.EXTERIOR) || isIntersects(Location.BOUNDARY, Location.EXTERIOR) @@ -242,7 +246,7 @@ public int valuePartial(int dimA, int dimB) { } @Override - public boolean value(int dimA, int dimB) { + public boolean valueIM() { return intMatrix.isEquals(dimA, dimB); } }; @@ -253,12 +257,13 @@ public static TopologyPredicate overlaps() { public String name() { return "overlaps"; } @Override - public int valueDimensions(int dimA, int dimB) { - return require( dimA == dimB ); + public void init(int dimA, int dimB) { + super.init(dimA, dimB); + updateValue( require(dimA == dimB)); } @Override - public int valuePartial(int dimA, int dimB) { + public int valuePartial() { if (dimA == Dimension.A || dimA == Dimension.P) { if (isIntersects(Location.INTERIOR, Location.INTERIOR) && isIntersects(Location.INTERIOR, Location.EXTERIOR) @@ -275,7 +280,7 @@ && isIntersects(Location.EXTERIOR, Location.INTERIOR)) } @Override - public boolean value(int dimA, int dimB) { + public boolean valueIM() { return intMatrix.isOverlaps(dimA, dimB); } }; @@ -286,21 +291,22 @@ public static TopologyPredicate touches() { public String name() { return "touches"; } @Override - public int valueDimensions(int dimA, int dimB) { + public void init(int dimA, int dimB) { + super.init(dimA, dimB); //-- Points have only interiors, so cannot touch boolean isBothPoints = dimA == 0 && dimB == 0; - return require(! isBothPoints); + updateValue( require(! isBothPoints)); } @Override - public int valuePartial(int dimA, int dimB) { + public int valuePartial() { //-- for touches interiors cannot intersect boolean isInteriorsIntersects = isIntersects(Location.INTERIOR, Location.INTERIOR); return valueIf(false, isInteriorsIntersects); } @Override - public boolean value(int dimA, int dimB) { + public boolean valueIM() { return intMatrix.isTouches(dimA, dimB); } }; diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyPredicateTracer.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyPredicateTracer.java index 7101ba7708..52d82085d4 100644 --- a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyPredicateTracer.java +++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyPredicateTracer.java @@ -24,31 +24,32 @@ public class TopologyPredicateTracer implements TopologyPredicate { public String name() { return pred.name(); } + @Override + public void init(int dimA, int dimB) { + pred.init(dimA, dimB); + checkValue("dimensions"); + } + + @Override + public void init(Envelope envA, Envelope envB) { + pred.init(envA, envB); + checkValue("envelopes"); + } + @Override public void updateDim(int locA, int locB, int dimension) { System.out.println("A:" + Location.toLocationSymbol(locA) + "/B:" + Location.toLocationSymbol(locB) + " -> " + dimension); pred.updateDim(locA, locB, dimension); + checkValue("IM entry"); } - @Override - public int valueDimensions(int dimA, int dimB) { - return pred.valueDimensions(dimA, dimB); - } - - @Override - public int valueEnvelopes(Envelope envA, Envelope envB) { - return pred.valueEnvelopes(envA, envB); - } - - @Override - public int valuePartial(int dimA, int dimB) { - int val = pred.valuePartial(dimA, dimB); - if (TopologyPredicateValue.isKnown(val)) { - System.out.println(name() + " = " + TopologyPredicateValue.toBoolean(val)); + private void checkValue(String source) { + if (pred.isKnown()) { + System.out.println(name() + " = " + pred.value() + + " based on " + source); } - return val; } @Override @@ -57,11 +58,17 @@ public void finish() { } @Override - public boolean value(int dimA, int dimB) { - return pred.value(dimA, dimB); + public boolean isKnown() { + return pred.isKnown(); + } + + @Override + public boolean value() { + return pred.value(); } public String toString() { return pred.toString(); } + }