All Downloads are FREE. Search and download functionalities are using the official Maven repository.

lspace.codec.json.geojsonld.Decoder.scala Maven / Gradle / Ivy

The newest version!
package lspace.codec.json.geojsonld

import lspace.Label
import lspace.codec.{`@container`, ActiveContext, ActiveProperty, ExpandedMap}
import lspace.codec.exception.FromJsonException
import lspace.codec.json
import lspace.codec.json.geojson.GeoJsonDecoder
import lspace.datatype.DataType.default._
import lspace.datatype.{CollectionType, ListType}
import lspace.provider.detached.DetachedGraph
import lspace.structure.{ClassType, Edge, Property, Resource}
import lspace.types.geo.{Geometry, MultiGeometry}
import monix.eval.Task

class Decoder[Json](decoder: json.jsonld.JsonLDDecoder[Json], geoJsonDecoder: GeoJsonDecoder[Json])
    extends lspace.codec.Decoder {
  import decoder._
  import decoder.baseDecoder._

  def withJsonProperties(resource: lspace.Node, otherJson: ExpandedMap[Json])(
      implicit activeContext: ActiveContext): Task[lspace.Node] = {
    Task
      .gatherUnordered(otherJson.obj.map {
        case (key, value) =>
          activeContext.definitions
            .get(key)
            .map(_ -> value)
            .map(Task.now)
            .getOrElse(
              //              toProperty(key).map(
              Task
                .now(Property.properties.getOrCreate(key))
                .map(property =>
                  activeContext.definitions
                    .get(key)
                    .map(_ -> value)
                    .getOrElse(ActiveProperty(property = property)() -> value)))
      })
      .map(_.map {
        //    Task
        //      .gatherUnordered(otherJson.obj.map {
        case (activeProperty, json) =>
          val property     = activeProperty.property
          val expectedType = activeProperty.`@type`.headOption
          val addEdgeF =
            if (activeProperty.`@reverse`) (value: Resource[_]) => resource.addIn(property, value)
            else (value: Resource[_]) => resource.addOut(property, value)
          val addEdgeTypedF =
            if (activeProperty.`@reverse`)(ct: ClassType[Any], value: Any) => resource.addIn(property, ct, value)
            else (ct: ClassType[Any], value: Any) => resource.addOut(property, ct, value)
          json.obj
            .map {
              obj =>
                activeProperty.`@container` match {
                  case Nil =>
                    toResource(obj.expand, expectedType)
                      .onErrorHandleWith { case t => autodiscoverValue(json) }
                      .flatMap(addEdgeF(_))
                      .map(List(_))
                  case List(`@container`.`@index`) =>
                    Task.gatherUnordered(obj.toList.map { //TODO: expand keys
                      case (index, json) =>
                        toResource(json, expectedType)
                          .onErrorHandleWith { case t => autodiscoverValue(json) }
                          .flatMap(addEdgeF(_))
                          .flatMap(_.addOut(Label.P.`@index`, index))
                    })
                  case List(`@container`.`@language`) =>
                    Task.gatherUnordered(obj.toList.map {
                      case (language, json) =>
                        toResource(json, expectedType)
                          .onErrorHandleWith { case t => autodiscoverValue(json) }
                          .flatMap(addEdgeF(_))
                          .flatMap(_.addOut(Label.P.`@language`, language))
                    })
                  case containers =>
                    Task.raiseError(
                      FromJsonException(s"not yet supported => @container: [${containers.map(_.iri).mkString(",")}]"))
                }
            }
            .orElse(json.list.map {
              array =>
                val edgesTask: Task[List[Edge[Any, Any]]] =
                  activeProperty.`@container` match {
                    case Nil =>
                      expectedType
                        .map {
                          case collectionType: CollectionType[_] =>
                            toCollection(array, collectionType)
                              .flatMap(addEdgeTypedF(collectionType, _))
                              .map(List(_))
                          case et => //no container but expected type, try ListType(List(et))
//                            toCollection(array, ListType(et))
//                              .flatMap(nodes => Task.gatherUnordered(nodes.map(node => addEdgeTypedF(et, node))))
                            toResource(json, expectedType).flatMap(addEdgeF(_)).map(List(_))
                        }
                        .getOrElse {
                          Task.gatherUnordered(array.map(toResource(_, expectedType)
                            .onErrorHandleWith { case t => autodiscoverValue(json) }
                            .flatMap(addEdgeF(_))))
                        }
                    case List(`@container`.`@list`) | List(`@container`.`@set`) =>
                      //this processes an array as a list of edges
                      Task.gatherUnordered(array.map(toResource(_, expectedType)
                        .onErrorHandleWith { case t => autodiscoverValue(json) }
                        .flatMap(addEdgeF(_))))
                  }
                edgesTask
            })
            .getOrElse {
              toResource(json, expectedType)
                .onErrorHandleWith { case e: Throwable => autodiscoverValue(json) }
                .flatMap(addEdgeF(_))
                .map(List(_))
            }
      })
      .flatMap { l =>
        Task.gatherUnordered(l).map { l =>
          resource
        }
      }
  }

  def autodiscoverValue(json: Json): Task[lspace.Value[Any]] = {
    json.int
      .map(graph.values.upsert(_, `@int`))
      .orElse(json.double.map(graph.values.upsert(_, `@double`)))
      .orElse(json.long.map(graph.values.upsert(_, `@long`)))
      .orElse(json.datetime.map(graph.values.upsert(_, `@datetime`)))
      .orElse(json.localdatetime.map(graph.values.upsert(_, `@localdatetime`)))
      .orElse(json.date.map(graph.values.upsert(_, `@date`)))
      .orElse(json.time.map(graph.values.upsert(_, `@time`)))
      .orElse(json.boolean.map(graph.values.upsert(_, `@boolean`)))
      .orElse(decodeGeometryOption(json))
      .orElse(json.string.map(graph.values.upsert(_, `@string`)))
      .getOrElse(Task.raiseError(new Exception("autodiscover not possible")))
  }

  def decodeFeature(map: Map[String, Json])(implicit context: ActiveContext): Task[lspace.Node] = {
    for {
      geometry <- map
        .get("geometry")
        .map(
          Task
            .now(_)
            .flatMap(_.obj.map(decodeGeometry).getOrElse(Task.raiseError(new Exception("geometry not an object")))))
        .getOrElse(Task.raiseError(new Exception("no geometry")))
      properties <- map
        .get("properties")
        .map(
          _.obj
            .map(Task.now)
            .getOrElse(Task.raiseError(new Exception("invalid properties"))))
        .getOrElse(Task.now(Map[String, Json]()))
      node <- DetachedGraph.nodes.create()
      //      geo  <- DetachedGraph.values.upsert(geometry, lspace.ClassType.detect(geometry))
      _ <- node --- "http://schema.org/geo" --> geometry
      _ <- withJsonProperties(node, properties.expand)
    } yield node
  }
  def decodeFeatureOption(map: Map[String, Json])(implicit context: ActiveContext): Option[Task[lspace.Node]] = {
    if (map.get("type").exists(_.string.contains("Feature"))) Some(decodeFeature(map)) else None
  }
  def decode(json: String)(implicit context: ActiveContext): Task[List[lspace.Node]] = {
    decoder.parse(json).flatMap { json =>
      json.obj
        .map { map =>
          map.get("type").flatMap(_.string) match {
            case Some(iri) =>
              iri match {
                case "FeatureCollection" =>
                  for {
                    features <- map
                      .get("features")
                      .map(_.list
                        .map(Task.now)
                        .getOrElse(Task.raiseError(new Exception("features must be an array"))))
                      .getOrElse(Task.raiseError(new Exception("FeatureCollection without 'features' is weird")))
                    nodes <- Task.gather(
                      features
                        .map(
                          _.obj
                            .map(Task
                              .now(_)
                              .flatMap(
                                decodeFeatureOption(_).getOrElse(Task.raiseError(new Exception("invalid feature")))))
                            .getOrElse(Task.raiseError(new Exception("feature must be an object")))): List[
                        Task[lspace.Node]])
                  } yield nodes
                case "Feature" =>
                  decodeFeature(map).map(List(_))
                case "GeometryCollection" =>
                  for {
                    node <- DetachedGraph.nodes.create()
                    v = geoJsonDecoder.decodeGeometryCollection(map)
                    value <- DetachedGraph.values.upsert(v, lspace.DataType.detect(v))
                    _     <- node --- "http://schema.org/location" --> value
                  } yield List(node)
                case _ =>
                  for {
                    node  <- DetachedGraph.nodes.create()
                    geo   <- decodeGeometryCoordinates(iri, geoJsonDecoder.decodeCoordinates(map))
                    value <- DetachedGraph.values.upsert(geo)
                    _     <- node --- "http://schema.org/location" --> value
                  } yield List(node)
              }
            case None => Task.raiseError(new Exception("invalid geojson"))
          }
        }
        .getOrElse(Task.raiseError(new Exception("invalid geojson")))
    }
  }

  def decodeGeometryOption(json: Json): Option[Task[lspace.Value[Geometry]]] =
    json.obj
      .map(decodeGeometryOption(_).getOrElse(throw new Exception("geometry without type")))
  def decodeGeometry(map: Map[String, Json]): Task[lspace.Value[Geometry]] =
    decodeGeometryOption(map).getOrElse(Task.raiseError(new Exception("geometry without type")))
  def decodeGeometryOption(map: Map[String, Json]): Option[Task[lspace.Value[Geometry]]] = {
    map.get("type").flatMap(_.string).map {
      case "GeometryCollection" => decodeGeometryCollection(map)
      case iri                  => decodeGeometryCoordinates(iri, geoJsonDecoder.decodeCoordinates(map))
    }
  }

  def decodeGeometryCoordinates(iri: String, coordinates: List[Json]): Task[lspace.Value[Geometry]] = {
    iri match {
      case "Point"           => graph.values.upsert(geoJsonDecoder.decodePoint(coordinates), `@geopoint`)
      case "MultiPoint"      => graph.values.upsert(geoJsonDecoder.decodeMultiPoint(coordinates), `@geomultipoint`)
      case "LineString"      => graph.values.upsert(geoJsonDecoder.decodeLine(coordinates), `@geoline`)
      case "MultiLineString" => graph.values.upsert(geoJsonDecoder.decodeMultiLine(coordinates), `@geomultiline`)
      case "Polygon"         => graph.values.upsert(geoJsonDecoder.decodePolygon(coordinates), `@geopolygon`)
      case "MultiPolygon"    => graph.values.upsert(geoJsonDecoder.decodeMultiPolygon(coordinates), `@geomultipolygon`)
      case _                 => Task.raiseError { FromJsonException(s"not a valid geojson Geometry $iri") }
    }
  }

  def decodeGeometryCollection(map: Map[String, Json]): Task[lspace.Value[MultiGeometry]] =
    map.get("geometries") match {
      case Some(json) =>
        Task {
          MultiGeometry(
            json.list
              .map(_.map(_.obj.getOrElse(throw new Exception("invalid MultiGeometry Geometry")))
                .map(geoJsonDecoder.decodeGeometry))
              .getOrElse(throw new Exception("invalid MultiGeometry, array expected for geometries"))
              .toVector)
        }.flatMap(graph.values.upsert(_, `@geomultigeo`))
      case None => Task.raiseError(FromJsonException("not a valid geojson MultiGeometry, geometries missing"))
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy