Skip to content

Commit

Permalink
Fix relate bug (#207)
Browse files Browse the repository at this point in the history
* Corrected BoundingBox.relate Within case

use [new] Polygon:line contains test instead of intersects
optimized the code a little to avoid extra intersection test

* corrections

* Use strict intersections, add tests

* Upgrade coverage plugin

* Reverting the coverage plugin changes
  • Loading branch information
halfabrane authored and harsha2010 committed Mar 16, 2018
1 parent e1d6809 commit aa9021e
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 47 deletions.
7 changes: 5 additions & 2 deletions src/main/scala/magellan/BoundingBox.scala
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ case class BoundingBox(xmin: Double, ymin: Double, xmax: Double, ymax: Double) {
Point(xmin, ymax)
)

// include both edges and diagonals
// diagonals catch corner cases of bounding box in annulus
val lines = Array(
Line(Point(xmin, ymin), Point(xmax, ymin)),
Line(Point(xmin, ymin), Point(xmin, ymax)),
Expand All @@ -112,8 +114,9 @@ case class BoundingBox(xmin: Double, ymin: Double, xmax: Double, ymax: Double) {
Line(Point(xmin, ymax), Point(xmax, ymin))
)

val lineIntersections = (lines filter (shape intersects _)).size
val vertexContained = (vertices filter (shape contains _)).size
// look for strict intersections between the edges of the bounding box and the shape
val lineIntersections = lines count (shape intersects (_, true))
val vertexContained = vertices count (shape contains _)

if (contains(shape.boundingBox)) {
Contains
Expand Down
17 changes: 11 additions & 6 deletions src/main/scala/magellan/PolyLine.scala
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,12 @@ class PolyLine extends Shape {


/**
* A polygon intersects a line iff it is a proper intersection,
* or if either vertex of the line touches the polygon.
* A polyline intersects a line iff it is a proper intersection
*
* @param line
* @return
*/
private [magellan] def intersects(line: Line): Boolean = {
private [magellan] def intersects(line: Line, strict: Boolean): Boolean = {
curves exists (_.intersects(line))
}

Expand All @@ -131,9 +130,15 @@ class PolyLine extends Shape {

def getRing(index: Int): Int = indices(index)

def intersects(polygon:Polygon):Boolean = {
// a polyline intersects a polygon iff any line intersects a polygon
curves exists (_.iterator().exists(polygon intersects))
/**
* A polygon intersects a polyline iff any line intersects a polygon
*
* @param polygon
* @param strict is this a strict intersection?
* @return
*/
def intersects(polygon:Polygon, strict: Boolean):Boolean = {
curves exists (_.iterator().exists(line => polygon intersects (line, strict)))
}

def canEqual(other: Any): Boolean = other.isInstanceOf[PolyLine]
Expand Down
53 changes: 27 additions & 26 deletions src/main/scala/magellan/Polygon.scala
Original file line number Diff line number Diff line change
Expand Up @@ -121,52 +121,47 @@ class Polygon extends Shape {
}

/**
* A polygon intersects a line iff it is a proper intersection,
* or if either vertex of the line touches the polygon.
* A polygon intersects a line iff it is a proper intersection(strict),
* or if the interior of the polygon contains any part of the line.
*
* @param line
* @param strict
* @return
*/
private [magellan] def intersects(line: Line): Boolean = {
var intersects = false
if(this.contains(line.getStart()) || this.contains(line.getEnd())){
intersects = true
}
else{
intersects = loops.exists(_.intersects(line))
}
intersects
private [magellan] def intersects(line: Line, strict: Boolean): Boolean = {
val interior = this.contains(line.getStart()) || this.contains(line.getEnd())
val strictIntersects = loops.exists(_.intersects(line))
strictIntersects || (!strict && interior)
}

/**
* A polygon intersects a polyline iff it is a proper intersection,
* A polygon intersects a polyline iff it is a proper intersection (strict),
* or if either vertex of the polyline touches the polygon.
*
* @param polyline
* @param strict
* @return
*/
private [magellan] def intersects(polyline: PolyLine): Boolean = {
polyline.intersects(this)
private [magellan] def intersects(polyline: PolyLine, strict: Boolean): Boolean = {
polyline.intersects(this, strict)
}


/**
* A polygon intersects another polygon iff at least one edge of the
* other polygon intersects this polygon.
*
* @param polygon
* @return
*/
private [magellan] def intersects(polygon: Polygon): Boolean = {
var intersects = false
if(polygon.getVertexes().exists(other => this.contains(other))
|| this.getVertexes().exists(vertex => polygon.contains(vertex))){
intersects = true
}
else{
intersects = polygon.loops.exists(otherLoop => this.loops.exists(_.intersects(otherLoop)))
}
intersects
private [magellan] def intersects(polygon: Polygon, strict: Boolean): Boolean = {
val touches =
polygon.getVertexes().exists(other => this.contains(other)) ||
this.getVertexes().exists(vertex => polygon.contains(vertex))

val strictIntersects = polygon.loops
.exists(otherLoop => this.loops.exists(_.intersects(otherLoop)))

strictIntersects || (!strict && touches)
}

private [magellan] def contains(box: BoundingBox): Boolean = {
Expand All @@ -180,6 +175,12 @@ class Polygon extends Shape {
!(lines exists (!contains(_)))
}

/**
* Checks if the polygon intersects the bounding box in a strict sense.
*
* @param box
* @return
*/
private [magellan] def intersects(box: BoundingBox): Boolean = {
val BoundingBox(xmin, ymin, xmax, ymax) = box
val lines = Array(
Expand Down Expand Up @@ -353,4 +354,4 @@ class PolygonDeserializer extends JsonDeserializer[Polygon] {
polygon
}

}
}
41 changes: 28 additions & 13 deletions src/main/scala/magellan/Shape.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,38 +56,53 @@ trait Shape extends DataType with Serializable {
/**
* Tests whether this shape intersects the argument shape.
* <p>
* The <code>intersects</code> predicate has the following equivalent definitions:
* A strict intersection is one where
* <ul>
* <li>The two geometries have at least one point in common
* <li><code>! other.disjoint(this) = true</code>
* <br>(<code>intersects</code> is the inverse of <code>disjoint</code>)
* <li><code>! (other.contains(this) || this.contains(other)) </code>
* </ul>
*
* @param other the <code>Shape</code> with which to compare this <code>Shape</code>
* @param strict is this intersection strict?
* @return <code>true</code> if the two <code>Shape</code>s intersect
*
* @see Shape#disjoint
*/
def intersects(other: Shape): Boolean = {
def intersects(other: Shape, strict: Boolean): Boolean = {
if (!boundingBox.disjoint(other.boundingBox)) {
(this, other) match {
case (p: Point, q: Point) => p.equals(q)
case (p: Point, q: Polygon) => q.touches(p)
case (p: Polygon, q: Point) => p.touches(q)
case (p: Polygon, q: Line) => p.intersects(q)
case (p: Polygon, q: PolyLine) => p.intersects(q)
case (p: Polygon, q: Polygon) => p.intersects(q)
case (p: PolyLine, q: Line) => p.intersects(q)
case (p: PolyLine, q: Polygon) => p.intersects(q)
case (p: Line, q: Polygon) => q.intersects(p)
case (p: Line, q: PolyLine) => q.intersects(p)
case (p: Polygon, q: Line) => p.intersects(q, strict)
case (p: Polygon, q: PolyLine) => p.intersects(q, strict)
case (p: Polygon, q: Polygon) => p.intersects(q, strict)
case (p: PolyLine, q: Line) => p.intersects(q, strict)
case (p: PolyLine, q: Polygon) => p.intersects(q, strict)
case (p: Line, q: Polygon) => q.intersects(p, strict)
case (p: Line, q: PolyLine) => q.intersects(p, strict)
case _ => ???
}
} else {
false
}
}

/**
* Computes the non strict intersection between two shapes.
* <p>
* The <code>intersects</code> predicate has the following equivalent definitions:
* <ul>
* <li>The two geometries have at least one point in common
* <li><code>! other.disjoint(this) = true</code>
* <br>(<code>intersects</code> is the inverse of <code>disjoint</code>)
* </ul> *
*
* @param other
* @return
*/
def intersects(other: Shape): Boolean = this.intersects(other, false)

/**
* Tests whether this shape contains the
* argument shape.
Expand Down Expand Up @@ -122,7 +137,7 @@ trait Shape extends DataType with Serializable {
case (p: Point, q: PolyLine) => false

case (p: Polygon, q: Point) => p.contains(q)
case (p: Polygon, q: Line) => p.contains(q)
case (p: Polygon, q: Line) => ???

case (p: Line, q: Point) => p.contains(q)
case (p: Line, q: Line) => p.contains(q)
Expand Down Expand Up @@ -194,7 +209,7 @@ object NullShape extends Shape {

override def isEmpty() = true

override def intersects(shape: Shape): Boolean = false
override def intersects(shape: Shape, strict: Boolean = false): Boolean = false

override def contains(shape: Shape): Boolean = false

Expand Down
89 changes: 89 additions & 0 deletions src/test/scala/magellan/BoundingBoxSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,93 @@ class BoundingBoxSuite extends FunSuite {
assert(!x.withinCircle(Point(0.5, 0.75), 0.5))
assert(!x.withinCircle(Point(0.5, 0.5), 0.2))
}

test("Relate") {
/**
* +---------+ 1,1
* + +----+ +
* + + + +
* + +----+ +
* +---------+
*
*/
val box = BoundingBox(0.0, 0.0, 0.5, 0.5)

val outerPolygon = Polygon(
Array(0),
Array(Point(1.0, 1.0), Point(1.0, -1.0),
Point(-1.0, -1.0), Point(-1.0, 1.0), Point(1.0, 1.0)))

assert(box.relate(outerPolygon) === Relate.Within)

/**
* +---------+ 1,1
* + +
* + +
* + +
* +-----+---+
* +----+
* + +
* +----+
*/

val disjointPolygon = Polygon(
Array(0),
Array(Point(1.1, -1.0), Point(2.0, -1.0),
Point(2.0, -2.0), Point(1.1, -2.0), Point(1.1, -1.0)))

assert(box.relate(disjointPolygon) === Relate.Disjoint)

/**
* +---------+ 1,1
* + +
* + +
* + +
* +-----+---+----+
* + +
* +----+
*/


val touchesPolygon = Polygon(
Array(0),
Array(Point(1.0, -1.0), Point(2.0, -1.0),
Point(2.0, -2.0), Point(1.0, -2.0), Point(1.0, -1.0)))

/**
* +---------+ 1,1
* + +
* + +----+
* + + +
* +-----+---+ +
* +----+
*/

val touchesPolygon2 = Polygon(
Array(0),
Array(Point(1.0, 0.0), Point(2.0, 0.0),
Point(2.0, -2.0), Point(1.0, -2.0), Point(1.0, 0.0)))

assert(box.relate(touchesPolygon2) == Relate.Disjoint)

// the interiors of the boxes do not intersect
assert(box.relate(touchesPolygon) === Relate.Disjoint)

/**
* +---------+ 1,1
* + 0,0 + 2,0
* + +---+----+
* + + + +
* +-----+---+ +
* +--------+
*/

val intersectsPolygon = Polygon(
Array(0),
Array(Point(0.0, 0.0), Point(2.0, 0.0),
Point(2.0, -2.0), Point(0.0, -2.0), Point(0.0, 0.0)))

assert(box.relate(intersectsPolygon) == Relate.Intersects)

}
}
27 changes: 27 additions & 0 deletions src/test/scala/magellan/PolygonSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -401,4 +401,31 @@ class PolygonSuite extends FunSuite {
assert(bufferedPolygon.getNumRings() === 1)
assert(bufferedPolygon.contains(Point(1.3, 1.3)))
}

test("strict intersection") {
// polygon - line
val outerRing = Array(Point(1.0, 1.0), Point(1.0, -1.0),
Point(-1.0, -1.0), Point(-1.0, 1.0), Point(1.0, 1.0))
val outerPolygon = Polygon(Array(0), outerRing)

val innerRing = Array(Point(0.5, 0.5), Point(0.5, -0.5),
Point(-0.5, -0.5), Point(-0.5, 0.5), Point(0.5, 0.5))
val innerPolygon = Polygon(Array(0), innerRing)

assert(outerPolygon.intersects(Line(Point(0.0, 0.0), Point(2.0, 2.0)), strict = true))
assert(outerPolygon.intersects(Line(Point(0.0, 0.0), Point(1.0, 1.0)), strict = true))
assert(outerPolygon.intersects(Line(Point(1.0, 1.0), Point(1.0, -1.0)), strict = true))

// interior of polygon contains line (disallowed in strict intersection)
assert(!outerPolygon.intersects(Line(Point(0.0, 0.0), Point(0.5, 0.5)), strict = true))

// polygon - polygon
assert(!outerPolygon.intersects(innerPolygon, strict = true))
assert(outerPolygon.intersects(innerPolygon))

// polygon - polyline
val innerPolyline = PolyLine(Array(0), innerRing.dropRight(1))
assert(!outerPolygon.intersects(innerPolyline,strict = true))
assert(outerPolygon.intersects(innerPolyline))
}
}

0 comments on commit aa9021e

Please sign in to comment.