Skip to content

Commit

Permalink
SRID support.
Browse files Browse the repository at this point in the history
  • Loading branch information
cranst0n committed Sep 2, 2021
1 parent 241f62d commit cb10039
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 109 deletions.
34 changes: 22 additions & 12 deletions modules/postgis/src/main/scala/ewkb/codecs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -107,31 +107,41 @@ trait EWKBPrimitives {
srid :: provide(ewkb.coordinate) :: listOfN(gint32, coordinate)
}.as[LineString]

private case class PolygonRepr(srid: Option[SRID], dim: Dimension, rings: List[LinearRing])
def polygon(implicit byteOrdering: ByteOrdering, ewkb: EWKBType): Scodec[Polygon] = {
listOfN(gint32, linearRing).xmap[Polygon](
rings => rings match {
case shell :: holes => Polygon(None, ewkb.coordinate, Some(shell), holes)
case Nil => Polygon(None, ewkb.coordinate, None, Nil)
},
polygon => polygon.shell.toList ::: polygon.holes
(srid :: provide(ewkb.coordinate) :: listOfN(gint32, linearRing)).as[PolygonRepr].xmap[Polygon](
repr => Polygon(repr.srid, repr.dim, repr.rings.headOption, repr.rings.drop(1)),
p => PolygonRepr(p.srid, p.dimension, p.shell.toList ::: p.holes)
)
}.as[Polygon]
}

def linearRing(implicit byteOrdering: ByteOrdering, ewkb: EWKBType): Scodec[LinearRing] = {
listOfN(guint32.xmap(_.toInt, _.toLong), coordinate)
.xmap[NonEmptyList[Coordinate]](NonEmptyList.fromListUnsafe, _.toList)
listOfN(gint32, coordinate).xmap[NonEmptyList[Coordinate]](NonEmptyList.fromListUnsafe, _.toList)
}.as[LinearRing]

def multiPoint(implicit byteOrdering: ByteOrdering, ewkb: EWKBType): Scodec[MultiPoint] = {
srid :: provide(ewkb.coordinate) :: listOfN(gint32, geometry.downcast[Point])
srid :: provide(ewkb.coordinate) :: listOfN(gint32, geometry.downcast[Point]).xmap[List[Point]](
pts => pts.map(_.copy(srid = None)),
identity
)
}.as[MultiPoint]

def multiLineString(implicit byteOrdering: ByteOrdering, ewkb: EWKBType): Scodec[MultiLineString] = {
srid :: provide(ewkb.coordinate) :: listOfN(gint32, geometry.downcast[LineString])
srid.flatPrepend { srid =>
provide(ewkb.coordinate) :: listOfN(gint32, geometry.downcast[LineString]).xmap[List[LineString]](
lss => lss.map(_.copy(srid = srid)),
identity
)
}
}.as[MultiLineString]

def multiPolygon(implicit byteOrdering: ByteOrdering, ewkb: EWKBType): Scodec[MultiPolygon] = {
srid :: provide(ewkb.coordinate) :: listOfN(gint32, geometry.downcast[Polygon])
srid.flatPrepend { srid =>
provide(ewkb.coordinate) :: listOfN(gint32, geometry.downcast[Polygon]).xmap[List[Polygon]](
ps => ps.map(_.copy(srid = srid)),
identity
)
}
}.as[MultiPolygon]

def geometryCollection(implicit byteOrdering: ByteOrdering, ewkb: EWKBType): Scodec[GeometryCollection] = {
Expand Down
176 changes: 98 additions & 78 deletions modules/postgis/src/main/scala/ewkt/parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,23 @@ object EWKT {
private[this] val whitespaces0: P[Unit] = whitespace.rep.void
private[this] val comma: P[Unit] = P.char(',').surroundedBy(whitespaces0.?)

private[this] implicit class BetweenOps[A](parser: P[A]) {
implicit val betweenParens: P[A] =
parser.between(P.char('('), P.char(')'))
implicit val betweenParensOpt: P[A] =
parser.between(P.char('(').?, P.char(')').?)
}

private[this] val srid: P[SRID] =
Numbers.nonNegativeIntString.map(s => SRID(Integer.parseInt(s))).between(
P.ignoreCase("SRID="),
P.char(';')
)

private[this] def keyword(keyword: String): P[Dimension] =
P.ignoreCase(keyword).surroundedBy(whitespaces0.?) *>
dimension

// This is only a hint, if you leave off ZM, but provide the 4 dimensions in the coordinate, it's still ZM
// Both 'POINT( 1 2 3 4 )' and 'POINT ZM( 1 2 3 4 )' have the same dimension
private[this] val dimension: P0[Dimension] = (
Expand All @@ -33,16 +50,12 @@ object EWKT {
}
}

private[this] implicit class BetweenOps[A](parser: P[A]) {
implicit val betweenParens: P[A] =
parser.between(P.char('('), P.char(')'))
implicit val betweenParensOpt: P[A] =
parser.between(P.char('(').?, P.char(')').?)
}
private[this] def empty[A](value: A): P[A] =
P.ignoreCase("EMPTY").surroundedBy(whitespaces0.?).as(value)

private[this] val double: P[Double] =
Numbers.jsonNumber.map(s => BigDecimal(s).toDouble)

private[this] def coordinate(implicit dimensionHint: Dimension): P[Coordinate] =
double.surroundedBy(whitespaces0.?).rep(2, 4).flatMap { elements =>
(elements, dimensionHint) match {
Expand All @@ -69,40 +82,33 @@ object EWKT {
private[this] def coordinates(implicit dimensionHint: Dimension): P[NonEmptyList[Coordinate]] =
coordinate.repSep(comma).betweenParens

private[this] def keyword(keyword: String): P[Dimension] =
P.ignoreCase(keyword).surroundedBy(whitespaces0.?) *>
dimension

private[this] def empty[A](value: A): P[A] =
P.ignoreCase("EMPTY").surroundedBy(whitespaces0.?).as(value)

/////////////////////////////////////////////////////////////////////////////

private[this] def nonEmptyPoint(implicit dimensionHint: Dimension): P[Point] =
coordinate.betweenParens.map(c => Point(c))
private[this] def nonEmptyPoint(implicit srid: Option[SRID], dimensionHint: Dimension): P[Point] =
coordinate.betweenParens.map(c => Point(srid, c))

private[this] def nonEmptyLineString(implicit dimensionHint: Dimension): P[LineString] =
private[this] def nonEmptyLineString(implicit srid: Option[SRID], dimensionHint: Dimension): P[LineString] =
coordinate.repSep(comma).betweenParens.map(points =>
LineString(None, dimensionHint, points.toList))
LineString(srid, dimensionHint, points.toList))

private[this] def nonEmptyPolygon(implicit dimensionHint: Dimension): P[Polygon] =
private[this] def nonEmptyPolygon(implicit srid: Option[SRID], dimensionHint: Dimension): P[Polygon] =
coordinates.map(LinearRing.apply).repSep(comma).betweenParens.map(rings =>
Polygon(None, dimensionHint, Some(rings.head), rings.tail))
Polygon(srid, dimensionHint, Some(rings.head), rings.tail))

private[this] def nonEmptyMultiPoint(implicit dimensionHint: Dimension): P[MultiPoint] =
private[this] def nonEmptyMultiPoint(implicit srid: Option[SRID], dimensionHint: Dimension): P[MultiPoint] =
coordinate.betweenParensOpt.repSep(comma)
.map(nel => nel.map(c => Point(c))).betweenParensOpt
.map(points => MultiPoint(None, dimensionHint, points.toList))
.map(points => MultiPoint(srid, dimensionHint, points.toList))

private[this] def nonEmptyMultiLineString(implicit dimensionHint: Dimension): P[MultiLineString] =
private[this] def nonEmptyMultiLineString(implicit srid: Option[SRID], dimensionHint: Dimension): P[MultiLineString] =
nonEmptyLineString.repSep(comma).betweenParens.map(lineStrings =>
MultiLineString(None, dimensionHint, lineStrings.toList))
MultiLineString(srid, dimensionHint, lineStrings.toList))

private[this] def nonEmptyMultiPolygon(implicit dimensionHint: Dimension): P[MultiPolygon] =
private[this] def nonEmptyMultiPolygon(implicit srid: Option[SRID], dimensionHint: Dimension): P[MultiPolygon] =
nonEmptyPolygon.repSep(comma).betweenParens.map(polygons =>
MultiPolygon(None, dimensionHint, polygons.toList))
MultiPolygon(srid, dimensionHint, polygons.toList))

private[this] def nonEmptyGeometryCollection(implicit dimensionHint: Dimension): P[GeometryCollection] =
private[this] def nonEmptyGeometryCollection(implicit srid: Option[SRID], dimensionHint: Dimension): P[GeometryCollection] =
P.oneOf(
point ::
lineString ::
Expand All @@ -113,83 +119,97 @@ object EWKT {
geometryCollection ::
Nil
).repSep(comma).betweenParens.map(geometries =>
GeometryCollection(None, dimensionHint, geometries.toList)
GeometryCollection(srid, dimensionHint, geometries.toList)
)

/////////////////////////////////////////////////////////////////////////////

val point: P[Point] =
keyword("POINT").flatMap { implicit dimensionHint =>
P.oneOf(
coordinateEmpty.map(c => Point(c)) ::
nonEmptyPoint ::
Nil
)
def point: P[Point] =
P.flatMap01(srid.?) { implicit srid =>
keyword("POINT").flatMap { implicit dimensionHint =>
P.oneOf(
coordinateEmpty.map(c => Point(srid, c)) ::
nonEmptyPoint ::
Nil
)
}
}

val lineString: P[LineString] =
keyword("LINESTRING").flatMap { implicit dimensionHint =>
P.oneOf(
empty(LineString.empty(dimensionHint)) ::
nonEmptyLineString ::
Nil
)
P.flatMap01(srid.?) { implicit srid =>
keyword("LINESTRING").flatMap { implicit dimensionHint =>
P.oneOf(
empty(LineString(srid, dimensionHint, Nil)) ::
nonEmptyLineString ::
Nil
)
}
}

val polygon: P[Polygon] =
keyword("POLYGON").flatMap { implicit dimensionHint =>
P.oneOf(
empty(Polygon.empty(dimensionHint)) ::
nonEmptyPolygon ::
Nil
)
P.flatMap01(srid.?) { implicit srid =>
keyword("POLYGON").flatMap { implicit dimensionHint =>
P.oneOf(
empty(Polygon(srid, dimensionHint, None, Nil)) ::
nonEmptyPolygon ::
Nil
)
}
}

val multiPoint: P[MultiPoint] =
keyword("MULTIPOINT").flatMap { implicit dimensionHint =>
P.oneOf(
empty(MultiPoint.empty(dimensionHint)) ::
nonEmptyMultiPoint ::
Nil
)
P.flatMap01(srid.?) { implicit srid =>
keyword("MULTIPOINT").flatMap { implicit dimensionHint =>
P.oneOf(
empty(MultiPoint(srid, dimensionHint, Nil)) ::
nonEmptyMultiPoint ::
Nil
)
}
}

val multiLineString: P[MultiLineString] =
keyword("MULTILINESTRING").flatMap { implicit dimensionHint =>
P.oneOf(
empty(MultiLineString.empty(dimensionHint)) ::
nonEmptyMultiLineString ::
Nil
)
P.flatMap01(srid.?) { implicit srid =>
keyword("MULTILINESTRING").flatMap { implicit dimensionHint =>
P.oneOf(
empty(MultiLineString(srid, dimensionHint, Nil)) ::
nonEmptyMultiLineString ::
Nil
)
}
}

val multiPolygon: P[MultiPolygon] =
keyword("MULTIPOLYGON").flatMap { implicit dimensionHint =>
P.oneOf(
empty(MultiPolygon.empty(dimensionHint)) ::
nonEmptyMultiPolygon ::
Nil
)
P.flatMap01(srid.?) { implicit srid =>
keyword("MULTIPOLYGON").flatMap { implicit dimensionHint =>
P.oneOf(
empty(MultiPolygon(srid, dimensionHint, Nil)) ::
nonEmptyMultiPolygon ::
Nil
)
}
}

val geometryCollection: P[GeometryCollection] =
keyword("GEOMETRYCOLLECTION").flatMap { implicit dimensionHint =>
P.oneOf(
empty(GeometryCollection.empty(dimensionHint)) ::
nonEmptyGeometryCollection ::
Nil
)
P.flatMap01(srid.?) { implicit srid =>
keyword("GEOMETRYCOLLECTION").flatMap { implicit dimensionHint =>
P.oneOf(
empty(GeometryCollection(srid, dimensionHint, Nil)) ::
nonEmptyGeometryCollection ::
Nil
)
}
}

val geometry: P[Geometry] =
P.oneOf(
point ::
lineString ::
polygon ::
multiPoint ::
multiLineString ::
multiPolygon ::
geometryCollection ::
point.backtrack ::
lineString.backtrack ::
polygon.backtrack ::
multiPoint.backtrack ::
multiLineString.backtrack ::
multiPolygon.backtrack ::
geometryCollection.backtrack ::
Nil
)
}
25 changes: 7 additions & 18 deletions modules/postgis/src/main/scala/geometry.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ object Coordinate {
def xyzm(x: Double, y: Double, z: Double, m: Double): Coordinate = Coordinate(x, y, z.some, m.some)
}

final case class Point private (srid: Option[SRID], coordinate: Coordinate) extends Geometry {
final case class Point (srid: Option[SRID], coordinate: Coordinate) extends Geometry {
override def dimension: Dimension = coordinate.dimension
}

Expand All @@ -89,13 +89,11 @@ object Point {
def xyzm(srid: SRID, x: Double, y: Double, z: Double, m: Double): Point = Point(srid.some, Coordinate.xyzm(x, y, z, m))
}

final case class LineString(srid: Option[SRID], private val dimensionHint: Dimension, coordinates: List[Coordinate]) extends Geometry {
final case class LineString(srid: Option[SRID], dimensionHint: Dimension, coordinates: List[Coordinate]) extends Geometry {
override def dimension: Dimension = coordinates.headOption.fold(dimensionHint)(_.dimension)
}

object LineString {
def empty(dimension: Dimension): LineString = LineString(None, dimension, Nil)

def apply(coordinates: Coordinate*): LineString
= apply(coordinates.toList)
def apply(coordinates: List[Coordinate]): LineString =
Expand All @@ -114,13 +112,11 @@ object LinearRing {
LinearRing(NonEmptyList(head, tail.toList))
}

final case class Polygon(srid: Option[SRID], private val dimensionHint: Dimension, shell: Option[LinearRing], holes: List[LinearRing]) extends Geometry {
final case class Polygon(srid: Option[SRID], dimensionHint: Dimension, shell: Option[LinearRing], holes: List[LinearRing]) extends Geometry {
override def dimension: Dimension = shell.map(_.coordinates.head.dimension).getOrElse(dimensionHint)
}

object Polygon {
def empty(dimension: Dimension): Polygon = Polygon(None, dimension, None, Nil)

def apply(shell: LinearRing): Polygon =
Polygon(None, shell.coordinates.head.dimension, shell.some, Nil)
def apply(shell: LinearRing, holes: LinearRing*): Polygon =
Expand All @@ -132,54 +128,47 @@ object Polygon {
Polygon(srid.some, shell.coordinates.head.dimension, shell.some, holes.toList)
}

final case class MultiPoint(srid: Option[SRID], private val dimensionHint: Dimension, points: List[Point]) extends Geometry {
final case class MultiPoint(srid: Option[SRID], dimensionHint: Dimension, points: List[Point]) extends Geometry {
override def dimension: Dimension = points.headOption.fold(dimensionHint)(_.dimension)
}

object MultiPoint {
def empty(dimension: Dimension): MultiPoint = MultiPoint(None, dimension, Nil)

def apply(points: Point*): MultiPoint =
MultiPoint(None, points.headOption.fold[Dimension](Dimension.TwoD)(_.dimension), points.toList)

def apply(srid: SRID, points: Point*): MultiPoint =
MultiPoint(srid.some, points.headOption.fold[Dimension](Dimension.TwoD)(_.dimension), points.toList)
}

final case class MultiLineString(srid: Option[SRID], private val dimensionHint: Dimension, lineStrings: List[LineString]) extends Geometry {
final case class MultiLineString(srid: Option[SRID], dimensionHint: Dimension, lineStrings: List[LineString]) extends Geometry {
override def dimension: Dimension = lineStrings.headOption.fold(dimensionHint)(_.dimension)
}

object MultiLineString {
def empty(dimension: Dimension): MultiLineString = MultiLineString(None, dimension, Nil)

def apply(lineStrings: LineString*): MultiLineString =
MultiLineString(None, lineStrings.headOption.fold[Dimension](Dimension.TwoD)(_.dimension), lineStrings.toList)

def apply(srid: SRID, lineStrings: LineString*): MultiLineString =
MultiLineString(srid.some, lineStrings.headOption.fold[Dimension](Dimension.TwoD)(_.dimension), lineStrings.toList)
}

final case class MultiPolygon(srid: Option[SRID], private val dimensionHint: Dimension, polygons: List[Polygon]) extends Geometry {
final case class MultiPolygon(srid: Option[SRID], dimensionHint: Dimension, polygons: List[Polygon]) extends Geometry {
override def dimension: Dimension = polygons.headOption.fold(dimensionHint)(_.dimension)
}

object MultiPolygon {
def empty(dimension: Dimension): MultiPolygon = MultiPolygon(None, dimension, Nil)

def apply(polygons: Polygon*): MultiPolygon =
MultiPolygon(None, polygons.headOption.fold[Dimension](Dimension.TwoD)(_.dimension), polygons.toList)

def apply(srid: SRID, polygons: Polygon*): MultiPolygon =
MultiPolygon(srid.some, polygons.headOption.fold[Dimension](Dimension.TwoD)(_.dimension), polygons.toList)
}

final case class GeometryCollection(srid: Option[SRID], private val dimensionHint: Dimension, geometries: List[Geometry]) extends Geometry {
final case class GeometryCollection(srid: Option[SRID], dimensionHint: Dimension, geometries: List[Geometry]) extends Geometry {
override def dimension: Dimension = geometries.headOption.fold(dimensionHint)(_.dimension)
}

object GeometryCollection {
def empty(dimension: Dimension): GeometryCollection = GeometryCollection(None, dimension, Nil)

def apply(geometries: Geometry*): GeometryCollection =
GeometryCollection(None, geometries.headOption.fold[Dimension](Dimension.TwoD)(_.dimension), geometries.toList)
Expand Down
Loading

0 comments on commit cb10039

Please sign in to comment.