如何在ReactiveMongo模型中表示GeoJSON点?

时间:2013-12-14 15:59:17

标签: mongodb scala playframework playframework-2.2 reactivemongo

为了在MongoDB中进行地理空间查询,带有位置(带有2d2dsphere地理空间索引)的文档应如下所示:

{
    _id: …,
    loc: {
        type: "Point",
        coordinates: [ <longitude>, <latitude> ]
    }
}

我是Scala,ReactiveMongo和Play Framework的新手,但在我看来,使用这样一个位置的一个明显方法是通过一个案例类:

case class Point(lon: Double, lat: Double)

网站API处理的JSON表示应该类似于:

{
    _id: …
    loc: [ <longitude>, <latitude> ]
}

现在,我无法弄清楚如何告诉我的ReactiveMongo模型在这些格式之间进行序列化/反序列化。

我的控制器看起来像这样:

package controllers

import play.api._
import play.api.mvc._
import play.api.libs.json._
import scala.concurrent.Future

// Reactive Mongo imports
import reactivemongo.api._
import scala.concurrent.ExecutionContext.Implicits.global

// Reactive Mongo plugin
import play.modules.reactivemongo.MongoController
import play.modules.reactivemongo.json.collection.JSONCollection

object Application extends Controller with MongoController {
    def collection: JSONCollection = db.collection[JSONCollection]("test")

    import play.api.data.Form
    import models._
    import models.JsonFormats._

    def createCC = Action.async {
        val user = User("John", "Smith", Point(-0.0015, 51.0015))
        val futureResult = collection.insert(user)
        futureResult.map(_ => Ok("Done!"))
    }
}

我尝试使用PointWriter和PointReader。这是我的models.scala:

package models

import reactivemongo.bson._
import play.modules.reactivemongo.json.BSONFormats._

case class User(
    // _id: Option[BSONObjectID],
    firstName: String,
    lastName: String,
    loc: Point)

case class Point(lon: Double, lat: Double)

object Point {
    implicit object PointWriter extends BSONDocumentWriter[Point] {
        def write(point: Point): BSONDocument = BSONDocument(
            "type" -> "Point",
            "coordinates" -> Seq(point.lat, point.lon)
        )
    }

    implicit object PointReader extends BSONReader[BSONDocument, Point] {
        def read(doc: BSONDocument): Point = Point(88, 88)
    }
}

object JsonFormats {
    import play.api.libs.json.Json
    import play.api.data._
    import play.api.data.Forms._

    import play.api.libs.json._
    import play.api.libs.functional.syntax._

    implicit val pointFormat = Json.format[Point]
}

当我调用控制器动作createCC时,我希望在新创建的文档中有一个格式正确的Point对象,但实际得到的是:

{
    "_id": ObjectId("52ac76dd1454bbf6d96ad1f1"),
    "loc": {
        "lon": -0.0015,
        "lat": 51.0015 
    }
}

所以我尝试使用PointWriterPointReader告诉ReactiveMongo如何将这样的Point对象写入数据库根本没有任何效果。

有人能帮我理解我必须做什么吗?

(我来自PHP背景,并试图让我的头围绕Scala ......)

更新 感谢tmbo的回答,我想出了这位作家:

val pointWrites = Writes[Point]( p =>
    Json.obj(
        "type" -> JsString("Point"),
        "coordinates" -> Json.arr(JsNumber(p.lon), JsNumber(p.lat))
    )
)

1 个答案:

答案 0 :(得分:5)

您遇到的问题与JSONCollectionBSONCollection之间的混淆有关。

BSONCollection reactivemongo 使用的默认集合。此实现需要实现BSONDocumentWriterBSONReader的案例类来获取(反)序列化。

另一方面,

JSONCollection播放反应模块使用的默认集合实现。由于您在JSONCollection中将集合定义为db.collection[JSONCollection]("test"),因此需要提供隐式json格式。

您提供的json格式是

implicit val pointFormat = Json.format[Point]

这会将对象序列化为以下格式

{
    "lon": -0.0015,
    "lat": 51.0015 
}

如果您想将Point序列化为数组,则需要替换上面隐含的pointFormat

import play.api.libs.json._
import play.api.libs.json.Reads._

case class Point(lng: Double, lat: Double)

object Point {

  val pointWrites = Writes[Point]( p => Json.toJson(List(p.lng, p.lat)))

  val pointReads = minLength[List[Double]](2).map(l => Point(l(0), l(1)))

  implicit val pointFormat = Format(pointReads, pointWrites)
}

您实际上不需要BSONReaderBSONDocumentWriter

修改 这是一个读取,它还验证文档的type属性:

val pointReads =
  (__ \ 'type).read[String](constraints.verifying[String](_ == "Point")) andKeep
    (__ \ 'coordinates).read[Point](minLength[List[Double]](2).map(l => Point(l(0), l(1))))