graphql-engine/server/src-lib/Hasura/SQL/GeoJSON.hs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

210 lines
5.9 KiB
Haskell
Raw Normal View History

{-# LANGUAGE TemplateHaskell #-}
2018-06-27 16:11:32 +03:00
module Hasura.SQL.GeoJSON
( Position (..),
Point (..),
2018-06-27 16:11:32 +03:00
MultiPoint (..),
LineString (..),
LinearRing (..),
2018-06-27 16:11:32 +03:00
MultiLineString (..),
Polygon (..),
MultiPolygon (..),
Geometry (..),
2018-06-27 16:11:32 +03:00
GeometryCollection (..),
GeometryWithCRS (..),
2018-06-27 16:11:32 +03:00
)
where
2018-06-27 16:11:32 +03:00
import Control.Monad
import Data.Aeson qualified as J
import Data.Aeson.Casing qualified as J
import Data.Aeson.TH qualified as J
import Data.Aeson.Types qualified as J
import Data.Vector qualified as V
2018-06-27 16:11:32 +03:00
import Hasura.Prelude
-- Positions
2018-06-27 16:11:32 +03:00
data Position
= Position !Double !Double !(Maybe Double)
deriving (Show, Eq)
withParsedArray ::
(J.FromJSON a) =>
String ->
(V.Vector a -> J.Parser b) ->
J.Value ->
J.Parser b
withParsedArray s fn =
J.withArray s (mapM J.parseJSON >=> fn)
instance J.FromJSON Position where
parseJSON = withParsedArray "Position" $ \arr ->
if V.length arr < 2
then fail "A Position needs at least 2 elements"
else -- here we are ignoring anything past 3 elements
return
$ Position
2018-06-27 16:11:32 +03:00
(arr `V.unsafeIndex` 0)
(arr `V.unsafeIndex` 1)
(arr V.!? 2)
instance J.ToJSON Position where
toJSON (Position a b c) =
J.toJSON $ a : b : maybeToList c
-- Point, Multipoint
2018-06-27 16:11:32 +03:00
newtype Point = Point {unPoint :: Position}
deriving (Show, Eq, J.ToJSON, J.FromJSON)
newtype MultiPoint = MultiPoint {unMultiPoint :: [Position]}
deriving (Show, Eq, J.ToJSON, J.FromJSON)
-- LineString, MultiLineString
2018-06-27 16:11:32 +03:00
data LineString = LineString
{ _lsFirst :: !Position,
_lsSecond :: !Position,
_lsRest :: ![Position]
}
deriving (Show, Eq)
instance J.ToJSON LineString where
toJSON (LineString a b rest) =
J.toJSON $ a : b : rest
instance J.FromJSON LineString where
parseJSON = withParsedArray "LineString" $ \arr ->
if V.length arr < 2
then fail "A LineString needs at least 2 Positions"
else -- here we are ignoring anything past 3 elements
2018-06-27 16:11:32 +03:00
let fstPos = arr `V.unsafeIndex` 0
sndPos = arr `V.unsafeIndex` 1
rest = V.toList $ V.drop 2 arr
in return $ LineString fstPos sndPos rest
2018-06-27 16:11:32 +03:00
newtype MultiLineString = MultiLineString {unMultiLineString :: [LineString]}
deriving (Show, Eq, J.ToJSON, J.FromJSON)
-- Polygon, MultiPolygon
2018-06-27 16:11:32 +03:00
data LinearRing = LinearRing
{ _pFirst :: !Position,
_pSecond :: !Position,
_pThird :: !Position,
_pRest :: ![Position]
}
deriving (Show, Eq)
instance J.FromJSON LinearRing where
parseJSON = withParsedArray "LinearRing" $ \arr ->
if V.length arr < 4
then fail "A LinearRing needs at least 4 Positions"
else -- here we are ignoring anything past 3 elements
do
let fstPos = arr `V.unsafeIndex` 0
sndPos = arr `V.unsafeIndex` 1
thrPos = arr `V.unsafeIndex` 2
rest = V.drop 3 arr
let lastPos = V.last rest
unless (fstPos == lastPos)
$ fail "the first and last locations have to be equal for a LinearRing"
2018-06-27 16:11:32 +03:00
return $ LinearRing fstPos sndPos thrPos $ V.toList $ V.init rest
instance J.ToJSON LinearRing where
toJSON (LinearRing a b c rest) =
J.toJSON $ (V.fromList [a, b, c] <> V.fromList rest) `V.snoc` a
newtype Polygon = Polygon {unPolygon :: [LinearRing]}
deriving (Show, Eq, J.ToJSON, J.FromJSON)
newtype MultiPolygon = MultiPolygon {unMultiPolygon :: [Polygon]}
deriving (Show, Eq, J.ToJSON, J.FromJSON)
-- GeometryCollection
data CRSNameProps = CRSNameProps
{ _cnpName :: !Text
}
2018-06-27 16:11:32 +03:00
deriving (Show, Eq)
data CRSLinkProps = CRSLinkProps
{ _clpHref :: !Text,
_clpType :: !(Maybe Text)
}
deriving (Show, Eq)
data CRS
= CRSName !CRSNameProps
| CRSLink !CRSLinkProps
deriving (Show, Eq)
$(J.deriveJSON (J.aesonPrefix J.camelCase) ''CRSNameProps)
$(J.deriveJSON (J.aesonPrefix J.camelCase) ''CRSLinkProps)
$( J.deriveJSON
J.defaultOptions
{ J.constructorTagModifier = J.camelCase . drop 3,
J.sumEncoding = J.TaggedObject "type" "properties"
}
''CRS
)
data GeometryWithCRS = GeometryWithCRS
{ _gwcGeom :: !Geometry,
_gwcCrs :: !(Maybe CRS)
}
deriving (Show, Eq)
2018-06-27 16:11:32 +03:00
encToCoords :: (J.ToJSON a) => Text -> a -> Maybe CRS -> J.Value
encToCoords ty a Nothing =
J.object ["type" J..= ty, "coordinates" J..= a]
encToCoords ty a (Just crs) =
J.object ["type" J..= ty, "coordinates" J..= a, "crs" J..= crs]
instance J.ToJSON GeometryWithCRS where
toJSON (GeometryWithCRS geom crsM) = case geom of
GPoint o -> encToCoords "Point" o crsM
GMultiPoint o -> encToCoords "MultiPoint" o crsM
GLineString o -> encToCoords "LineString" o crsM
GMultiLineString o -> encToCoords "MultiLineString" o crsM
GPolygon o -> encToCoords "Polygon" o crsM
GMultiPolygon o -> encToCoords "MultiPolygon" o crsM
2018-06-27 16:11:32 +03:00
GGeometryCollection o ->
J.object
[ "type" J..= ("GeometryCollection" :: Text),
2018-06-27 16:11:32 +03:00
"geometries" J..= o
]
instance J.FromJSON GeometryWithCRS where
2018-06-27 16:11:32 +03:00
parseJSON = J.withObject "Geometry" $ \o -> do
ty <- o J..: "type"
geom <- case ty of
2018-06-27 16:11:32 +03:00
"Point" -> GPoint <$> o J..: "coordinates"
"MultiPoint" -> GMultiPoint <$> o J..: "coordinates"
"LineString" -> GLineString <$> o J..: "coordinates"
"MultiLineString" -> GMultiLineString <$> o J..: "coordinates"
"Polygon" -> GPolygon <$> o J..: "coordinates"
"MultiPolygon" -> GMultiPolygon <$> o J..: "coordinates"
2018-06-27 16:11:32 +03:00
"GeometryCollection" -> GGeometryCollection <$> o J..: "geometries"
_ -> fail $ "unexpected geometry type: " <> ty
crsM <- o J..:? "crs"
return $ GeometryWithCRS geom crsM
newtype GeometryCollection = GeometryCollection {unGeometryCollection :: [GeometryWithCRS]}
deriving (Show, Eq, J.ToJSON, J.FromJSON)
-- Geometry
data Geometry
= GPoint !Point
| GMultiPoint !MultiPoint
| GLineString !LineString
| GMultiLineString !MultiLineString
| GPolygon !Polygon
| GMultiPolygon !MultiPolygon
| GGeometryCollection !GeometryCollection
deriving (Show, Eq)