ongo.reactivemongo-bson-geo_3.1.1.0-RC4.source-code.Geometry.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of reactivemongo-bson-geo_3 Show documentation
Show all versions of reactivemongo-bson-geo_3 Show documentation
GeoJSON support for the BSON API
package reactivemongo.api.bson
import scala.util.{ Failure, Success, Try }
import GeoPosition.safeWriter.{ safeWrite => writePos }
/**
* GeoJSON [[https://docs.mongodb.com/manual/reference/geojson/#overview geometry]] object
*/
sealed trait GeoGeometry {
/** The type of coordinates (depends on the type of geometry). */
private[bson] type C
def `type`: String
/**
* The coordinates
*/
protected def coordinates: C
}
/**
* Geometry utilities
*/
object GeoGeometry {
implicit val handler: BSONDocumentHandler[GeoGeometry] = {
implicit val cfg: MacroConfiguration = MacroConfiguration(
discriminator = "type",
typeNaming = TypeNaming(TypeNaming.SimpleName.andThen {
(_: String).stripPrefix("Geo")
})
)
Macros.handler[GeoGeometry]
}
// ---
private[bson] def reader[G <: GeoGeometry](
readCoordinates: BSONValue => Try[G]
): BSONDocumentReader[G] =
BSONDocumentReader.from[G] { doc =>
doc.get("coordinates") match {
case Some(coords) =>
readCoordinates(coords)
case _ =>
Failure(exceptions.BSONValueNotFoundException("coordinates", doc))
}
}
private[bson] def writer[G <: GeoGeometry](
writeCoordinates: G => BSONValue
): BSONDocumentWriter[G] with SafeBSONDocumentWriter[G] =
BSONDocumentWriter.safe[G] { geometry =>
BSONDocument(
"type" -> geometry.`type`,
"coordinates" -> writeCoordinates(geometry)
)
}
}
/**
* GeoJSON [[https://docs.mongodb.com/manual/reference/geojson/#point Point]]
*/
final class GeoPoint private[api] (
val coordinates: GeoPosition)
extends GeoGeometry {
type C = GeoPosition
@inline def `type` = GeoPoint.`type`
override def equals(that: Any): Boolean = that match {
case other: GeoPoint =>
this.coordinates == other.coordinates
case _ =>
false
}
@inline override def hashCode: Int = coordinates.hashCode
override def toString = s"GeoPoint(${coordinates.toString})"
}
/** [[GeoPoint]] factories & utilities */
object GeoPoint {
val `type` = "Point"
/** Creates a new point. */
def apply(coordinates: GeoPosition): GeoPoint = new GeoPoint(coordinates)
/**
* Convenient factory (See [[GeoPosition]];
* equivalent to `GeoPoint(GeoPosition(_1, _2))`).
*/
@inline def apply(_1: Double, _2: Double): GeoPoint =
GeoPoint(GeoPosition(_1, _2, None))
/**
* Convenient factory (See [[GeoPosition]];
* equivalent to `GeoPoint(GeoPosition(_1, _2, elevation))`).
*/
@inline def apply(
_1: Double,
_2: Double,
elevation: Option[Double]
): GeoPoint = GeoPoint(GeoPosition(_1, _2, elevation))
/**
* Extracts the point coordinates.
*
* {{{
* import reactivemongo.api.bson.GeoPoint
*
* def elevation(pt: GeoPoint): Option[Double] = pt match {
* case GeoPoint(a, b, elevation @ Some(_)) =>
* println("_1: " + a + ", _2: " + b)
* elevation
*
* case _ =>
* None
* }
* }}}
*/
def unapply(point: GeoPoint): Option[(Double, Double, Option[Double])] =
Option(point).flatMap(p => GeoPosition.unapply(p.coordinates))
implicit val reader: BSONDocumentReader[GeoPoint] =
GeoGeometry.reader[GeoPoint] {
case GeoPosition.BSON(pos) =>
Success(GeoPoint(pos))
case bson =>
Failure(
exceptions.TypeDoesNotMatchException(
"",
bson.getClass.getSimpleName
)
)
}
implicit val writer: BSONDocumentWriter[GeoPoint] =
GeoGeometry.writer[GeoPoint] { point => writePos(point.coordinates) }
}
/**
* GeoJSON [[https://tools.ietf.org/html/rfc7946#section-3.1.4 LineString]]
*/
final class GeoLineString private[api] (
val _1: GeoPosition,
val _2: GeoPosition,
val more: Seq[GeoPosition])
extends GeoGeometry {
type C = Tuple3[GeoPosition, GeoPosition, Seq[GeoPosition]]
@inline def `type` = GeoLineString.`type`
val coordinates = Tuple3(_1, _2, more)
/**
* Appends more positions
*
* {{{
* import reactivemongo.api.bson.{ GeoLineString, GeoPosition }
*
* def equivalentTo(lineString: GeoLineString, positions: GeoPosition*) =
* GeoLineString(
* _1 = lineString._1,
* _2 = lineString._2,
* more = lineString.more ++ positions)
* }}}
*/
@inline def ++(positions: GeoPosition*): GeoLineString =
new GeoLineString(_1, _2, more ++ positions)
override def equals(that: Any): Boolean = that match {
case other: GeoLineString =>
this.coordinates == other.coordinates
case _ =>
false
}
override def hashCode: Int = coordinates.hashCode
override def toString = s"GeoLineString${coordinates.toString}"
}
/** See [[GeoLineString]] */
object GeoLineString {
val `type` = "LineString"
/** Creates a new line string. */
def apply(
_1: GeoPosition,
_2: GeoPosition,
more: Seq[GeoPosition]
): GeoLineString = new GeoLineString(_1, _2, more)
/**
* Convenient factory
* (equivalent to `GeoLineString(_1, _2, Seq.empty)`).
*/
@inline def apply(_1: GeoPosition, _2: GeoPosition): GeoLineString =
GeoLineString(_1, _2, Seq.empty)
/** Extracts the line string coordinates. */
def unapply(
lineString: GeoLineString
): Option[(GeoPosition, GeoPosition, Seq[GeoPosition])] =
Option(lineString).map(_.coordinates)
private[bson] val readCoordinates: BSONValue => Try[GeoLineString] = {
case BSONArray(Seq(GeoPosition.BSON(a), GeoPosition.BSON(b), more @ _*)) =>
GeoPosition.readSeq(more).map { morePos => GeoLineString(a, b, morePos) }
case bson =>
Failure(
exceptions.TypeDoesNotMatchException(
"[ , , ... ]",
bson.getClass.getSimpleName
)
)
}
implicit val reader: BSONDocumentReader[GeoLineString] =
GeoGeometry.reader[GeoLineString](readCoordinates)
private[bson] val writeCoordinates: GeoLineString => BSONArray = { lineStr =>
writePos(lineStr._1) +: writePos(lineStr._2) +: BSONArray(
lineStr.more.map(writePos)
)
}
implicit def writer: BSONDocumentWriter[GeoLineString] =
GeoGeometry.writer[GeoLineString](writeCoordinates)
}
/**
* GeoJSON [[https://docs.mongodb.com/manual/reference/geojson/#polygon Polygon]]
*/
final class GeoPolygon private[bson] (
val exterior: GeoLinearRing,
val interior: Seq[GeoLinearRing])
extends GeoGeometry {
type C = Tuple2[GeoLinearRing, Seq[GeoLinearRing]]
@inline def `type`: String = GeoPolygon.`type`
private[bson] lazy val tupled = Tuple2(exterior, interior)
@inline def coordinates = tupled
/**
* {{{
* import reactivemongo.api.bson.{ GeoLinearRing, GeoPolygon }
*
* def equivalentTo(poly: GeoPolygon, interior: Seq[GeoLinearRing]) =
* GeoPolygon(poly.exterior, (poly.interior ++ interior): _*)
* }}}
*/
def ++(interior: GeoLinearRing*): GeoPolygon =
new GeoPolygon(exterior, this.interior ++ interior)
override def equals(that: Any): Boolean = that match {
case other: GeoPolygon => tupled == other.tupled
case _ => false
}
override lazy val hashCode: Int = tupled.hashCode
override def toString: String =
s"""GeoPolygon(${exterior}${interior.mkString(", ", ", ", "")})"""
}
/** See [[GeoPolygon]] */
object GeoPolygon {
val `type`: String = "Polygon"
/**
* Creates a [[https://docs.mongodb.com/manual/reference/geojson/#polygons-with-a-single-ring single ring]] polygon.
*
* @param exterior the single (exterior) ring (cannot self-intersect)
*/
def apply(exterior: GeoLinearRing): GeoPolygon =
new GeoPolygon(exterior, Seq.empty)
/**
* Creates a [[https://docs.mongodb.com/manual/reference/geojson/#polygons-with-multiple-rings multiple rings]] polygon.
*
* Any interior ring must be entirely contained by the outer ring.
* Interior rings cannot intersect or overlap each other.
* Interior rings cannot share an edge.
*
* @param exterior the exterior (mandatory) ring (cannot self-intersect)
* @param interior some optional interior rings
*/
def apply(exterior: GeoLinearRing, interior: GeoLinearRing*): GeoPolygon =
new GeoPolygon(exterior, interior)
/**
* Extracts the ring properties.
*/
def unapply(
polygon: GeoPolygon
): Option[(GeoLinearRing, Seq[GeoLinearRing])] =
Option(polygon).map(_.tupled)
private[bson] val readCoordinates: BSONValue => Try[GeoPolygon] = {
case BSONArray(Seq(exterior @ BSONArray(_), interior @ _*)) =>
for {
ex <- GeoLinearRing.reader.readTry(exterior)
in <- GeoLinearRing.readSeq(interior)
} yield new GeoPolygon(ex, in)
case bson =>
Failure(
exceptions.TypeDoesNotMatchException(
"[ , ... ]",
bson.getClass.getSimpleName
)
)
}
implicit val reader: BSONDocumentReader[GeoPolygon] =
GeoGeometry.reader[GeoPolygon](readCoordinates)
private[bson] val writeCoordinates: GeoPolygon => BSONArray = { polygon =>
import GeoLinearRing.safeWriter.safeWrite
BSONArray(safeWrite(polygon.exterior) +: polygon.interior.map(safeWrite))
}
implicit val writer: BSONDocumentWriter[GeoPolygon] =
GeoGeometry.writer[GeoPolygon](writeCoordinates)
}
/** GeoJSON [[https://docs.mongodb.com/manual/reference/geojson/#multipoint MultiPoint]] (collection of [[GeoPosition]]) */
final class GeoMultiPoint private[api] (
val coordinates: Seq[GeoPosition])
extends GeoGeometry {
type C = Seq[GeoPosition]
val `type`: String = GeoMultiPoint.`type`
override def equals(that: Any): Boolean = that match {
case other: GeoMultiPoint =>
this.coordinates == other.coordinates
case _ =>
false
}
@inline override def hashCode: Int = coordinates.hashCode
override def toString: String =
s"""GeoMultiPoint[${coordinates.mkString("[", ", ", "]")}]"""
}
/** See [[GeoMultiPoint]] */
object GeoMultiPoint {
val `type`: String = "MultiPoint"
/** Creates a new multi-point. */
def apply(coordinates: Seq[GeoPosition]): GeoMultiPoint =
new GeoMultiPoint(coordinates)
/**
* Extracts the coordinates.
*/
def unapply(multiPoint: GeoMultiPoint): Option[Seq[GeoPosition]] =
Option(multiPoint).map(_.coordinates)
implicit val reader: BSONDocumentReader[GeoMultiPoint] =
GeoGeometry.reader[GeoMultiPoint] {
case BSONArray(values) =>
GeoPosition.readSeq(values).map(GeoMultiPoint(_))
case bson =>
Failure(
exceptions
.TypeDoesNotMatchException("BSONArray", bson.getClass.getSimpleName)
)
}
implicit val writer: BSONDocumentWriter[GeoMultiPoint] =
GeoGeometry.writer[GeoMultiPoint] { multi =>
BSONArray(multi.coordinates.map(GeoPosition.safeWriter.safeWrite))
}
}
/** GeoJSON [[https://docs.mongodb.com/manual/reference/geojson/#multilinestring MultiLineString]] (collection of [[GeoLineString]]) */
final class GeoMultiLineString private[api] (
val coordinates: Seq[GeoLineString])
extends GeoGeometry {
type C = Seq[GeoLineString]
@inline def `type`: String = GeoMultiLineString.`type`
override def equals(that: Any): Boolean = that match {
case other: GeoMultiLineString =>
this.coordinates == other.coordinates
case _ =>
false
}
@inline override def hashCode: Int = coordinates.hashCode
override def toString: String =
s"""GeoMultiLineString[${coordinates.mkString("[", ", ", "]")}]"""
}
/** See [[GeoMultiLineString]] */
object GeoMultiLineString {
val `type`: String = "MultiLineString"
/** Creates a new multi-linestring. */
def apply(coordinates: Seq[GeoLineString]): GeoMultiLineString =
new GeoMultiLineString(coordinates)
/**
* Extracts the coordinates.
*/
def unapply(multiLineString: GeoMultiLineString): Option[Seq[GeoLineString]] =
Option(multiLineString).map(_.coordinates)
implicit val reader: BSONDocumentReader[GeoMultiLineString] = {
@annotation.tailrec
def go(
values: Seq[BSONValue],
out: List[GeoLineString]
): Try[Seq[GeoLineString]] =
values.headOption match {
case Some(v) =>
GeoLineString.readCoordinates(v) match {
case Success(lineString) =>
go(values.tail, lineString :: out)
case Failure(cause) =>
Failure(cause)
}
case _ =>
Success(out.reverse)
}
GeoGeometry.reader[GeoMultiLineString] {
case BSONArray(values) =>
go(values, List.empty).map(GeoMultiLineString(_))
case bson =>
Failure(
exceptions.TypeDoesNotMatchException(
"[ , ... ]",
bson.getClass.getSimpleName
)
)
}
}
implicit val writer: BSONDocumentWriter[GeoMultiLineString] =
GeoGeometry.writer[GeoMultiLineString] { multiLineString =>
BSONArray(multiLineString.coordinates.map(GeoLineString.writeCoordinates))
}
}
/** GeoJSON [[https://docs.mongodb.com/manual/reference/geojson/#multipolygon MultiPolygon]] (collection of [[GeoPolygon]]) */
final class GeoMultiPolygon private[api] (
val coordinates: Seq[GeoPolygon])
extends GeoGeometry {
type C = Seq[GeoPolygon]
@inline def `type`: String = GeoMultiPolygon.`type`
override def equals(that: Any): Boolean = that match {
case other: GeoMultiPolygon =>
this.coordinates == other.coordinates
case _ =>
false
}
@inline override def hashCode: Int = coordinates.hashCode
override def toString: String =
s"""GeoMultiPolygon[${coordinates.mkString("[", ", ", "]")}]"""
}
/** See [[GeoMultiPolygon]] */
object GeoMultiPolygon {
val `type`: String = "MultiPolygon"
/** Creates a new multi-polygon. */
def apply(coordinates: Seq[GeoPolygon]): GeoMultiPolygon =
new GeoMultiPolygon(coordinates)
/**
* Extracts the coordinates.
*/
def unapply(multiPolygon: GeoMultiPolygon): Option[Seq[GeoPolygon]] =
Option(multiPolygon).map(_.coordinates)
implicit val reader: BSONDocumentReader[GeoMultiPolygon] = {
@annotation.tailrec
def go(
values: Seq[BSONValue],
out: List[GeoPolygon]
): Try[Seq[GeoPolygon]] = values.headOption match {
case Some(bson) =>
GeoPolygon.readCoordinates(bson) match {
case Success(polygon) =>
go(values.tail, polygon :: out)
case Failure(cause) =>
Failure(cause)
}
case _ =>
Success(out.reverse)
}
GeoGeometry.reader[GeoMultiPolygon] {
case BSONArray(values) =>
go(values, List.empty).map(GeoMultiPolygon(_))
case bson =>
Failure(
exceptions.TypeDoesNotMatchException(
"[ , ... ]",
bson.getClass.getSimpleName
)
)
}
}
implicit val writer: BSONDocumentWriter[GeoMultiPolygon] =
GeoGeometry.writer[GeoMultiPolygon] { multiPolygon =>
BSONArray(multiPolygon.coordinates.map(GeoPolygon.writeCoordinates))
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy