{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Database.Postgis.JSON (
ToJSON (..),
FromJSON (..)
) where
import Control.Applicative ((<|>))
import Data.Aeson
import Data.Aeson.Types (Parser)
import Database.Postgis.Geometry
import qualified Data.Vector as V
import qualified Data.Text as T
import Development.Placeholders
import Data.Maybe
import Data.Monoid ((<>))
import Data.Vector ((!), (!?))
import qualified Data.HashMap.Lazy as HM
import Data.Text.Read (decimal)
instance ToJSON Point where
toJSON (Point x y m z) = toJSON $ catMaybes [Just x, Just y, m, z]
instance FromJSON Point where
parseJSON = withArray "Point" $ \v' -> do
v <- sequence $ fmap parseJSON v'
return $ Point (v ! 0) (v ! 1) (v !? 2) (v !? 3)
instance FromJSON LineString where
parseJSON = withObject "LineString" $ \o -> do
("LineString" :: T.Text) <- o .: "type"
cs <- o .: "coordinates"
vs <- sequence $ fmap parseJSON cs
return $ LineString vs
instance ToJSON LineString where
toJSON (LineString points) = object ["type" .= ("LineString" :: T.Text), "coordinates" .= V.map toJSON points]
instance ToJSON Polygon where
toJSON (Polygon rings) = object ["type" .= ("Polygon" :: T.Text), "coordinates" .= V.map toJSON rings]
instance FromJSON Polygon where
parseJSON = withObject "Polygon" $ \o -> do
("Polygon" :: T.Text) <- o .: "type"
ls <- o .: "coordinates"
cs <- sequence $ fmap parseJSON ls
return $ Polygon cs
instance ToJSON MultiPoint where
toJSON (MultiPoint pg) = object ["type" .= ("MultiPoint" :: T.Text), "coordinates" .= V.map toJSON pg]
instance FromJSON MultiPoint where
parseJSON = withObject "MultiPoint" $ \o -> do
("MultiPoint" :: T.Text) <- o .: "type"
ls <- o .: "coordinates"
cs <- sequence $ fmap parseJSON ls
return $ MultiPoint cs
instance ToJSON MultiLineString where
toJSON (MultiLineString ls) = object ["type" .= ("MultiLineString" :: T.Text), "coordinates" .= V.map toJSON ls]
instance FromJSON MultiLineString where
parseJSON = withObject "MultiLineString" $ \o -> do
("MultiLineString" :: T.Text) <- o .: "type"
ls <- o .: "coordinates"
cs <- sequence $ fmap parseJSON ls
return $ MultiLineString cs
instance ToJSON MultiPolygon where
toJSON (MultiPolygon ps) = object ["type" .= ("MultiPolygon" :: T.Text), "coordinates" .= V.map toJSON ps]
instance FromJSON MultiPolygon where
parseJSON = withObject "MultiPolygon" $ \o -> do
("MultiPolygon" :: T.Text) <- o .: "type"
ls <- o .: "coordinates"
cs <- sequence $ fmap parseJSON ls
return $ MultiPolygon cs
addKeyToValue :: Value -> T.Text -> Value -> Maybe Value
addKeyToValue (Object hm) k v = Just . Object $ HM.insert k v hm
addKeyToValue _ _ _ = Nothing
go :: ToJSON a => SRID -> a -> Value
go (Just s) x =
let v = toJSON x
in maybe v id $ addKeyToValue v "crs" $ sridToJson s
go Nothing x = toJSON x
instance ToJSON Geometry where
toJSON (GeoPoint s x) = go s x
toJSON (GeoLineString s x) = go s x
toJSON (GeoPolygon s x) = go s x
toJSON (GeoMultiPoint s x) = go s x
toJSON (GeoMultiLineString s x) = go s x
toJSON (GeoMultiPolygon s x) = go s x
sridToJson srid =
object ["type" .= ("name" :: T.Text), "properties" .= object ["name" .= ("ESPG:" <> (show srid) :: String)] ]
parseCRS :: Value -> Parser (Maybe Int)
parseCRS = withObject "crs" $ \o -> do
("name" :: T.Text) <- o .: "type"
prop <- o .: "prop"
espg <- prop .: "name"
let (x:y:xs) = T.split ((==) ':') espg
case decimal y of
Left e -> return Nothing
Right (v,_) -> return $ Just v
instance FromJSON Geometry where
parseJSON o
= GeoPoint <$> parseCRS o <*> parseJSON o
<|> GeoLineString <$> parseCRS o <*> parseJSON o
<|> GeoPolygon <$> parseCRS o <*> parseJSON o
<|> GeoMultiPoint <$> parseCRS o <*> parseJSON o
<|> GeoMultiLineString <$> parseCRS o <*> parseJSON o
<|> GeoMultiPolygon <$> parseCRS o <*> parseJSON o