Skip to content

Commit

Permalink
Merge pull request #95 from halfabrane/MAGELLAN-INDEX
Browse files Browse the repository at this point in the history
Magellan index
  • Loading branch information
harsha2010 authored Feb 14, 2017
2 parents b8577a3 + 4c0251c commit 0ba8933
Show file tree
Hide file tree
Showing 28 changed files with 879 additions and 45 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name := "magellan"

version := "1.0.4"
version := "1.0.5-SNAPSHOT"

organization := "harsha2010"

Expand Down
92 changes: 92 additions & 0 deletions src/main/scala/magellan/BoundingBox.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* Copyright 2015 Ram Sriharsha
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package magellan

import com.fasterxml.jackson.annotation.JsonProperty

/**
* A bounding box is an axis parallel rectangle. It is completely specified by
* the bottom left and top right points.
*
* @param xmin the minimum x coordinate of the rectangle.
* @param ymin the minimum y coordinate of the rectangle.
* @param xmax the maximum x coordinate of the rectangle.
* @param ymax the maximum y coordinate of the rectangle.
*/
case class BoundingBox(xmin: Double, ymin: Double, xmax: Double, ymax: Double) {

private def this() {this(0, 0, 0, 0)}

@JsonProperty
def getXmin(): Double = xmin

@JsonProperty
def getYmin(): Double = ymin

@JsonProperty
def getXmax(): Double = xmax

@JsonProperty
def getYmax(): Double = ymax

private [magellan] def intersects(other: BoundingBox): Boolean = {
val BoundingBox(otherxmin, otherymin, otherxmax, otherymax) = other
!(otherxmin >= xmax || otherymin >= ymax || otherymax <= ymin || otherxmax <= xmin)
}

private [magellan] def contains(other: BoundingBox): Boolean = {
val BoundingBox(otherxmin, otherymin, otherxmax, otherymax) = other
(xmin <= otherxmin && ymin <= otherymin && xmax >= otherxmax && ymax >= otherymax)
}

private [magellan] def contains(point: Point): Boolean = {
val (x, y) = (point.getX(), point.getY())
(xmin <= x && ymin <= y && xmax >= x && ymax >= y)
}

private [magellan] def intersects(point: Point): Boolean = {
val lines = Array(
Line(Point(xmin, ymin), Point(xmax, ymin)),
Line(Point(xmin, ymin), Point(xmin, ymax)),
Line(Point(xmax, ymin), Point(xmax, ymax)),
Line(Point(xmin, ymax), Point(xmax, ymax)))

lines exists (_ contains point)
}

private [magellan] def disjoint(shape: Shape): Boolean = {
// a bounding box is disjoint from a shape if it does not intersect the shape
// nor is contained in nor contains the shape.

val vertices = Array(Point(xmin, ymin),
Point(xmax, ymin),
Point(xmax, ymax),
Point(xmin, ymax)
)

val lines = Array(
Line(Point(xmin, ymin), Point(xmax, ymin)),
Line(Point(xmin, ymin), Point(xmin, ymax)),
Line(Point(xmax, ymin), Point(xmax, ymax)),
Line(Point(xmin, ymax), Point(xmax, ymax)))

!contains(shape.boundingBox) &&
!(vertices exists (shape contains _)) &&
!(lines exists(shape intersects _))
}

}
4 changes: 2 additions & 2 deletions src/main/scala/magellan/Line.scala
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class Line extends Shape {
*/
override def transform(fn: (Point) => Point): Shape = ???

override def boundingBox: ((Double, Double), (Double, Double)) = {
override def boundingBox: BoundingBox = {
val (xmin, xmax) = if (start.getX() < end.getX()) {
(start.getX(), end.getX())
} else {
Expand All @@ -136,7 +136,7 @@ class Line extends Shape {
} else {
(end.getY(), start.getY())
}
((xmin, ymin), (xmax, ymax))
BoundingBox(xmin, ymin, xmax, ymax)
}

def canEqual(other: Any): Boolean = other.isInstanceOf[Line]
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/magellan/Point.scala
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class Point extends Shape {
("y" -> y)

@JsonProperty
override def boundingBox: ((Double, Double), (Double, Double)) = ((x, y), (x, y))
override def boundingBox = BoundingBox(x, y, x, y)

@JsonIgnore
override def isEmpty(): Boolean = true
Expand Down
10 changes: 6 additions & 4 deletions src/main/scala/magellan/PolyLine.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ class PolyLine(
val indices: Array[Int],
val xcoordinates: Array[Double],
val ycoordinates: Array[Double],
override val boundingBox: Tuple2[Tuple2[Double, Double], Tuple2[Double, Double]]) extends Shape {
override val boundingBox: BoundingBox) extends Shape {

private def this() {this(Array(0), Array(), Array(), BoundingBox(0,0,0,0))}

override def getType(): Int = 3

Expand Down Expand Up @@ -69,8 +71,8 @@ class PolyLine(
}

def exceedsBounds(point:Point):Boolean = {
val ((pt_xmin, pt_ymin), (pt_xmax, pt_ymax)) = point.boundingBox
val ((xmin, ymin), (xmax, ymax)) = boundingBox
val BoundingBox(pt_xmin, pt_ymin, pt_xmax, pt_ymax) = point.boundingBox
val BoundingBox(xmin, ymin, xmax, ymax) = boundingBox

pt_xmin < xmin && pt_ymin < ymin ||
pt_xmax > xmax && pt_ymax > ymax
Expand Down Expand Up @@ -170,7 +172,7 @@ object PolyLine {
indices,
points.map(_.getX()),
points.map(_.getY()),
((xmin, ymin), (xmax, ymax))
BoundingBox(xmin, ymin, xmax, ymax)
)
}
}
59 changes: 56 additions & 3 deletions src/main/scala/magellan/Polygon.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ class Polygon(
val indices: Array[Int],
val xcoordinates: Array[Double],
val ycoordinates: Array[Double],
override val boundingBox: Tuple2[Tuple2[Double, Double], Tuple2[Double, Double]]) extends Shape {
override val boundingBox: BoundingBox) extends Shape {

private def this() {this(Array(0), Array(), Array(), BoundingBox(0,0,0,0))}

@inline private def intersects(point: Point, line: Line): Boolean = {
val (start, end) = (line.getStart(), line.getEnd())
Expand Down Expand Up @@ -251,6 +253,57 @@ class Polygon(
}
}

private [magellan] def contains(box: BoundingBox): Boolean = {
val BoundingBox(xmin, ymin, xmax, ymax) = box
val lines = Array(
Line(Point(xmin, ymin), Point(xmax, ymin)),
Line(Point(xmin, ymin), Point(xmin, ymax)),
Line(Point(xmax, ymin), Point(xmax, ymax)),
Line(Point(xmin, ymax), Point(xmax, ymax)))

!(lines exists (!contains(_)))
}

private [magellan] def intersects(box: BoundingBox): Boolean = {
val BoundingBox(xmin, ymin, xmax, ymax) = box
val lines = Array(
Line(Point(xmin, ymin), Point(xmax, ymin)),
Line(Point(xmin, ymin), Point(xmin, ymax)),
Line(Point(xmax, ymin), Point(xmax, ymax)),
Line(Point(xmin, ymax), Point(xmax, ymax)))

lines exists (intersects(_))
}

private [magellan] def intersects(point: Point): Boolean = {
// Check if any edge intersects this line
var i = 0
val length = xcoordinates.length
var found = false
var start:Point = null
var end:Point = new Point()
val edge = new Line()

while (i < length && !found) {
if (start == null) {
start = new Point()
start.setX(xcoordinates(i))
start.setY(ycoordinates(i))
} else {
start = end
end = new Point()
end.setX(xcoordinates(i))
end.setY(ycoordinates(i))
edge.setStart(start)
edge.setEnd(end)
found = edge.contains(point)
}
i += 1
}
found
}


@JsonProperty
override def getType(): Int = 5

Expand Down Expand Up @@ -312,7 +365,7 @@ object Polygon {
indices,
points.map(_.getX()),
points.map(_.getY()),
((xmin, ymin), (xmax, ymax))
BoundingBox(xmin, ymin, xmax, ymax)
)
}
}
}
34 changes: 18 additions & 16 deletions src/main/scala/magellan/Shape.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,11 @@ import org.apache.spark.sql.types._
*/
trait Shape extends DataType with Serializable {

type BoundingBox = Tuple2[Tuple2[Double, Double], Tuple2[Double, Double]]

override def defaultSize: Int = 4096

override def asNullable: DataType = this

@JsonProperty
@JsonIgnore
def getType(): Int

/**
Expand All @@ -50,6 +48,7 @@ trait Shape extends DataType with Serializable {
*
* @return <code>true</code> if this <code>Shape</code> is valid
*/
@JsonIgnore
def isValid(): Boolean = true

/**
Expand Down Expand Up @@ -93,10 +92,9 @@ trait Shape extends DataType with Serializable {
* @see Shape#disjoint
*/
def intersects(other: Shape, bitMask: Int): Boolean = {
val ((xmin, ymin), (xmax, ymax)) = this.boundingBox
val ((otherxmin, otherymin), (otherxmax, otherymax)) = other.boundingBox
if ((xmin <= otherxmin && xmax >= otherxmin && ymin <= otherymin && ymax >= otherymin) ||
(otherxmin <= xmin && otherxmax >= xmin && otherymin <= ymin && otherymax >= ymin)) {
if (boundingBox.intersects(other.boundingBox) ||
boundingBox.contains(other.boundingBox) ||
other.boundingBox.contains(boundingBox)) {
(this, other) match {
case (p: Point, q: Point) => p.equals(q)
case (p: Point, q: Polygon) => q.intersects(Line(p, p))
Expand Down Expand Up @@ -130,7 +128,12 @@ trait Shape extends DataType with Serializable {
* Returns <code>false</code> if both <code>Shape</code>s are points
*/
def touches(other: Shape): Boolean = {
???
(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 _ => ???
}
}

/**
Expand All @@ -155,11 +158,10 @@ trait Shape extends DataType with Serializable {
* @return true if this shape contains the other.
*/
def contains(other: Shape): Boolean = {
// check if the bounding box encompasses other's bounding box.
// check if the bounding box intersects other's bounding box.
// if not, no need to check further
val ((xmin, ymin), (xmax, ymax)) = this.boundingBox
val ((otherxmin, otherymin), (otherxmax, otherymax)) = other.boundingBox
if (xmin <= otherxmin && ymin <= otherymin && xmax >= otherxmax && ymax >= otherymax) {

if (boundingBox.contains(other.boundingBox)) {
(this, other) match {
case (p: Point, q: Point) => p.equals(q)
case (p: Point, q: Polygon) => false
Expand All @@ -176,7 +178,7 @@ trait Shape extends DataType with Serializable {
}

@JsonProperty
def boundingBox: Tuple2[Tuple2[Double, Double], Tuple2[Double, Double]]
def boundingBox: BoundingBox

/**
* Tests whether this shape is within the
Expand Down Expand Up @@ -296,9 +298,9 @@ object NullShape extends Shape {

override def transform(fn: (Point) => Point): Shape = this

override def boundingBox: ((Double, Double), (Double, Double)) = (
(Int.MinValue, Int.MinValue),
(Int.MaxValue, Int.MaxValue)
override def boundingBox = BoundingBox(
Int.MinValue, Int.MinValue,
Int.MaxValue, Int.MaxValue
)
}

Expand Down
4 changes: 4 additions & 0 deletions src/main/scala/magellan/dsl/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ package object dsl {

def transform(fn: Point => Point) = Transformer(expr, fn)

def geohash(other: Expression, precision: Int) = GeohashIndexer(other, precision)

}

trait ExpressionConversions {
Expand All @@ -69,6 +71,8 @@ package object dsl {

def transform(fn: Point => Point): Column = Column(Transformer(c.expr, fn))

def geohash(precision: Int): Column = Column(GeohashIndexer(c.expr, precision))

}

implicit def point(x: Expression, y: Expression) = PointConverter(x, y)
Expand Down
37 changes: 37 additions & 0 deletions src/main/scala/magellan/index/Index.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Copyright 2015 Ram Sriharsha
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package magellan.index

import magellan.BoundingBox

/**
* An abstraction for a spatial curve based 2D Index.
* A spatial curve represents a two dimensional grid of a given precision.
*/
trait Index extends Serializable {

def precision(): Int

def code(): String

def bits(): Long

def boundingBox(): BoundingBox

def toBase32(): String

}
Loading

0 comments on commit 0ba8933

Please sign in to comment.