Contains vs Within ambiguity for equal shapes
When two shapes are equal, the shape.relate(otherShape) method can ambiguously return either Contains or Within. If the caller wants to know specifically if the relationship is Contains (or Within) it can test to see if they are equal if relate() returns Within (or Contains).
Once we introduce aggregate shapes (e.g. ShapeCollection), the aforementioned equality test isn't good enough because the ShapeCollection.relate() doesn't know if the caller prefers Within or Contains. And that's the problem.
One approach taken in SpatialOpRecursivePrefixTreeTest in Lucene spatial is a ShapePair class that has a boolean bias flag to prefer Contains or Within first. It looks something like this:
private SpatialRelation relateApprox(Shape other) {
if (biasContainsThenWithin) {
if (shape1.relate(other) == CONTAINS || shape1.equals(other)
|| shape2.relate(other) == CONTAINS || shape2.equals(other)) return CONTAINS;
if (shape1.relate(other) == WITHIN && shape2.relate(other) == WITHIN) return WITHIN;
} else {
if ((shape1.relate(other) == WITHIN || shape1.equals(other))
&& (shape2.relate(other) == WITHIN || shape2.equals(other))) return WITHIN;
if (shape1.relate(other) == CONTAINS || shape2.relate(other) == CONTAINS) return CONTAINS;
}
if (shape1.relate(other).intersects() || shape2.relate(other).intersects())
return INTERSECTS;//might actually be 'CONTAINS' if these 2 are adjacent
return DISJOINT;
}
Another possible approach (not mutually exclusive) is to establish a protocol in which the more "complex" shape (defaulting to the current shape, 'this', when both shapes are the same type) will always favor 'Contains'. Then if the caller prefers a bias of Within, it can just flip the who relates to who call. This isn't a complete approach that addresses all theoretical use cases, but known cases in Lucene spatial for example could work with this.
Another possible approach is to add a shape.hasRelation(relation, Shape other):boolean method. This would also give the potential for optimization because the implementation can focus on a yes-no answer instead of returning one of multiple possible enum values. However if the caller needs to know if it's Within OR Intersects OR ... (i.e. multiple conditions) then this optimization rationale evaporates.