mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-16 09:51:59 +03:00
c08e6d108b
GitOrigin-RevId: 51ca927b55d3d717da07447f67fd4d3c068a8357
194 lines
5.8 KiB
Haskell
194 lines
5.8 KiB
Haskell
module Hasura.SQL.GeoJSON
|
|
( Position(..)
|
|
, Point(..)
|
|
, MultiPoint(..)
|
|
, LineString(..)
|
|
, LinearRing(..)
|
|
, MultiLineString(..)
|
|
, Polygon(..)
|
|
, MultiPolygon(..)
|
|
, Geometry(..)
|
|
, GeometryCollection(..)
|
|
, GeometryWithCRS(..)
|
|
) where
|
|
|
|
import qualified Data.Aeson as J
|
|
import qualified Data.Aeson.Casing as J
|
|
import qualified Data.Aeson.TH as J
|
|
import qualified Data.Aeson.Types as J
|
|
import qualified Data.Vector as V
|
|
|
|
import Control.Monad
|
|
import Hasura.Prelude
|
|
|
|
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"
|
|
-- here we are ignoring anything past 3 elements
|
|
else return $ Position
|
|
(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
|
|
|
|
newtype Point
|
|
= Point { unPoint :: Position }
|
|
deriving (Show, Eq, J.ToJSON, J.FromJSON)
|
|
|
|
newtype MultiPoint
|
|
= MultiPoint { unMultiPoint :: [Position] }
|
|
deriving (Show, Eq, J.ToJSON, J.FromJSON)
|
|
|
|
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"
|
|
-- here we are ignoring anything past 3 elements
|
|
else
|
|
let fstPos = arr `V.unsafeIndex` 0
|
|
sndPos = arr `V.unsafeIndex` 1
|
|
rest = V.toList $ V.drop 2 arr
|
|
in return $ LineString fstPos sndPos rest
|
|
|
|
newtype MultiLineString
|
|
= MultiLineString { unMultiLineString :: [LineString] }
|
|
deriving (Show, Eq, J.ToJSON, J.FromJSON)
|
|
|
|
newtype GeometryCollection
|
|
= GeometryCollection { unGeometryCollection :: [GeometryWithCRS] }
|
|
deriving (Show, Eq, J.ToJSON, J.FromJSON)
|
|
|
|
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"
|
|
-- here we are ignoring anything past 3 elements
|
|
else 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"
|
|
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)
|
|
|
|
data Geometry
|
|
= GPoint !Point
|
|
| GMultiPoint !MultiPoint
|
|
| GLineString !LineString
|
|
| GMultiLineString !MultiLineString
|
|
| GPolygon !Polygon
|
|
| GMultiPolygon !MultiPolygon
|
|
| GGeometryCollection !GeometryCollection
|
|
deriving (Show, Eq)
|
|
|
|
data GeometryWithCRS
|
|
= GeometryWithCRS
|
|
{ _gwcGeom :: !Geometry
|
|
, _gwcCrs :: !(Maybe CRS)
|
|
} deriving (Show, Eq)
|
|
|
|
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
|
|
GGeometryCollection o ->
|
|
J.object [ "type" J..= ("GeometryCollection"::Text)
|
|
, "geometries" J..= o
|
|
]
|
|
|
|
instance J.FromJSON GeometryWithCRS where
|
|
parseJSON = J.withObject "Geometry" $ \o -> do
|
|
ty <- o J..: "type"
|
|
geom <- case ty of
|
|
"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"
|
|
"GeometryCollection" -> GGeometryCollection <$> o J..: "geometries"
|
|
_ -> fail $ "unexpected geometry type: " <> ty
|
|
crsM <- o J..:? "crs"
|
|
return $ GeometryWithCRS geom crsM
|
|
|
|
data CRSNameProps
|
|
= CRSNameProps
|
|
{ _cnpName :: !Text
|
|
} 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)
|