-- | Ewkb is deeply integrated into Wkb, which is why we kept this around.
-- For postgis we mostly want Ewkb.
--
-- This module Allows parsing of ByteString into a Geospatial Object,
-- and provides foundational elements for constructing an Ewkb.
--
-- Refer to the WKB Wikipedia page <https://en.wikipedia.org/wiki/Well-known_text#Well-known_binary>
module Database.Esqueleto.Postgis.Wkb
  ( parseByteString
  , parseHexByteString
  , toByteString
  -- * Core
  -- | the greasy gears inside for experienced users or the brave!

  -- ** Geometry
  , module Geometry
  -- ** Endian
  , module Endian
  -- ** Point
  , module Point
  ) where

import qualified Data.Binary.Get              as BinaryGet
import qualified Data.ByteString.Builder      as ByteStringBuilder
import qualified Data.ByteString.Lazy         as LazyByteString
import qualified Data.Geospatial              as Geospatial

import Database.Esqueleto.Postgis.Wkb.Endian     as Endian
import Database.Esqueleto.Postgis.Wkb.Geometry   as Geometry
import Database.Esqueleto.Postgis.Wkb.Point      as Point
import qualified Database.Esqueleto.Postgis.Wkb.Geospatial as WkbGeospatial
import Data.ByteString.Lazy.Base16(decodeBase16)
import Data.Base16.Types(Base16)

-- |
-- Representation of WKB as Binary
parseByteString :: LazyByteString.ByteString -> Either String Geospatial.GeospatialGeometry
parseByteString :: ByteString -> Either [Char] GeospatialGeometry
parseByteString ByteString
byteString =
  case Get GeospatialGeometry
-> ByteString
-> Either
     (ByteString, ByteOffset, [Char])
     (ByteString, ByteOffset, GeospatialGeometry)
forall a.
Get a
-> ByteString
-> Either
     (ByteString, ByteOffset, [Char]) (ByteString, ByteOffset, a)
BinaryGet.runGetOrFail
        ((EndianType -> Get WkbGeometryType) -> Get GeospatialGeometry
WkbGeospatial.getGeospatialGeometry EndianType -> Get WkbGeometryType
Geometry.getWkbGeom)
        ByteString
byteString of
    Left (ByteString
_, ByteOffset
_, [Char]
err)                 -> [Char] -> Either [Char] GeospatialGeometry
forall a b. a -> Either a b
Left ([Char] -> Either [Char] GeospatialGeometry)
-> [Char] -> Either [Char] GeospatialGeometry
forall a b. (a -> b) -> a -> b
$ [Char]
"Could not parse wkb: " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
err
    Right (ByteString
_, ByteOffset
_, GeospatialGeometry
geoSpatialGeometry) -> GeospatialGeometry -> Either [Char] GeospatialGeometry
forall a b. b -> Either a b
Right GeospatialGeometry
geoSpatialGeometry

-- |
-- Representation of WKB as a String in Base16/Hex form i.e. "0101000000000000000000f03f0000000000000040" is POINT 1.0 2.0
parseHexByteString :: Base16 LazyByteString.ByteString -> Either String Geospatial.GeospatialGeometry
parseHexByteString :: Base16 ByteString -> Either [Char] GeospatialGeometry
parseHexByteString = ByteString -> Either [Char] GeospatialGeometry
parseByteString (ByteString -> Either [Char] GeospatialGeometry)
-> (Base16 ByteString -> ByteString)
-> Base16 ByteString
-> Either [Char] GeospatialGeometry
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Base16 ByteString -> ByteString
decodeBase16

-- |
-- Produce the binary representation of WKB given its EndianType (Little or Big - Intel is Little).  Use EWKB when you know the SRID.
toByteString :: Endian.EndianType -> Geospatial.GeospatialGeometry -> LazyByteString.ByteString
toByteString :: EndianType -> GeospatialGeometry -> ByteString
toByteString EndianType
endianType =
  Builder -> ByteString
ByteStringBuilder.toLazyByteString (Builder -> ByteString)
-> (GeospatialGeometry -> Builder)
-> GeospatialGeometry
-> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. BuilderWkbGeometryType
-> EndianType -> GeospatialGeometry -> Builder
WkbGeospatial.builderGeospatialGeometry BuilderWkbGeometryType
Geometry.builderWkbGeom EndianType
endianType