Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Magellan index #95

Merged
merged 8 commits into from
Feb 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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