packages feed

esqueleto-postgis 1.1.0 → 1.2.0

raw patch · 26 files changed

+1535/−12 lines, 26 filesdep +base16dep +binarydep +bytestringdep −wkt-geomdep ~geojsonPVP ok

version bump matches the API change (PVP)

Dependencies added: base16, binary, bytestring, hspec, hw-hspec-hedgehog, tasty-hspec

Dependencies removed: wkt-geom

Dependency ranges changed: geojson

API changes (from Hackage documentation)

+ Database.Esqueleto.Postgis: PointXY :: !Double -> !Double -> PointXY
+ Database.Esqueleto.Postgis: PointXYZ :: !Double -> !Double -> !Double -> PointXYZ
+ Database.Esqueleto.Postgis: PointXYZM :: !Double -> !Double -> !Double -> !Double -> PointXYZM
+ Database.Esqueleto.Postgis: [_xyX] :: PointXY -> !Double
+ Database.Esqueleto.Postgis: [_xyY] :: PointXY -> !Double
+ Database.Esqueleto.Postgis: [_xyzX] :: PointXYZ -> !Double
+ Database.Esqueleto.Postgis: [_xyzY] :: PointXYZ -> !Double
+ Database.Esqueleto.Postgis: [_xyzZ] :: PointXYZ -> !Double
+ Database.Esqueleto.Postgis: [_xyzmM] :: PointXYZM -> !Double
+ Database.Esqueleto.Postgis: [_xyzmX] :: PointXYZM -> !Double
+ Database.Esqueleto.Postgis: [_xyzmY] :: PointXYZM -> !Double
+ Database.Esqueleto.Postgis: [_xyzmZ] :: PointXYZM -> !Double
+ Database.Esqueleto.Postgis: data PointXY
+ Database.Esqueleto.Postgis: data PointXYZ
+ Database.Esqueleto.Postgis: data PointXYZM
+ Database.Esqueleto.Postgis.Ewkb: parseByteString :: ByteString -> Either String GeospatialGeometry
+ Database.Esqueleto.Postgis.Ewkb: parseHexByteString :: Hex -> Either String GeospatialGeometry
+ Database.Esqueleto.Postgis.Ewkb: toByteString :: EndianType -> SridType -> GeospatialGeometry -> ByteString
+ Database.Esqueleto.Postgis.Ewkb.Geometry: EwkbGeom :: WkbGeometryType -> SridType -> EwkbGeometryType
+ Database.Esqueleto.Postgis.Ewkb.Geometry: NoSrid :: SridType
+ Database.Esqueleto.Postgis.Ewkb.Geometry: Srid :: Word32 -> SridType
+ Database.Esqueleto.Postgis.Ewkb.Geometry: builderEwkbGeom :: EndianType -> EwkbGeometryType -> Builder
+ Database.Esqueleto.Postgis.Ewkb.Geometry: builderWkbGeom :: EndianType -> WkbGeometryType -> Builder
+ Database.Esqueleto.Postgis.Ewkb.Geometry: data EwkbGeometryType
+ Database.Esqueleto.Postgis.Ewkb.Geometry: data SridType
+ Database.Esqueleto.Postgis.Ewkb.Geometry: getEwkbGeom :: EndianType -> Get EwkbGeometryType
+ Database.Esqueleto.Postgis.Ewkb.Geometry: getWkbGeom :: EndianType -> Get WkbGeometryType
+ Database.Esqueleto.Postgis.Ewkb.Geometry: instance GHC.Classes.Eq Database.Esqueleto.Postgis.Ewkb.Geometry.EwkbGeometryType
+ Database.Esqueleto.Postgis.Ewkb.Geometry: instance GHC.Classes.Eq Database.Esqueleto.Postgis.Ewkb.Geometry.SridType
+ Database.Esqueleto.Postgis.Ewkb.Geometry: instance GHC.Show.Show Database.Esqueleto.Postgis.Ewkb.Geometry.EwkbGeometryType
+ Database.Esqueleto.Postgis.Ewkb.Geometry: instance GHC.Show.Show Database.Esqueleto.Postgis.Ewkb.Geometry.SridType
+ Database.Esqueleto.Postgis.Hex: Hex :: ByteString -> Hex
+ Database.Esqueleto.Postgis.Hex: newtype Hex
+ Database.Esqueleto.Postgis.Hex: safeConvert :: (ByteString -> Either String GeospatialGeometry) -> Hex -> Either String GeospatialGeometry
+ Database.Esqueleto.Postgis.Wkb: parseByteString :: ByteString -> Either String GeospatialGeometry
+ Database.Esqueleto.Postgis.Wkb: parseHexByteString :: Hex -> Either String GeospatialGeometry
+ Database.Esqueleto.Postgis.Wkb: toByteString :: EndianType -> GeospatialGeometry -> ByteString
+ Database.Esqueleto.Postgis.Wkb.Endian: BigEndian :: EndianType
+ Database.Esqueleto.Postgis.Wkb.Endian: LittleEndian :: EndianType
+ Database.Esqueleto.Postgis.Wkb.Endian: builderDouble :: EndianType -> Double -> Builder
+ Database.Esqueleto.Postgis.Wkb.Endian: builderEndianType :: EndianType -> Builder
+ Database.Esqueleto.Postgis.Wkb.Endian: builderFourBytes :: EndianType -> Word32 -> Builder
+ Database.Esqueleto.Postgis.Wkb.Endian: data EndianType
+ Database.Esqueleto.Postgis.Wkb.Endian: getDouble :: EndianType -> Get Double
+ Database.Esqueleto.Postgis.Wkb.Endian: getEndianType :: Get EndianType
+ Database.Esqueleto.Postgis.Wkb.Endian: getFourBytes :: EndianType -> Get Word32
+ Database.Esqueleto.Postgis.Wkb.Endian: instance GHC.Classes.Eq Database.Esqueleto.Postgis.Wkb.Endian.EndianType
+ Database.Esqueleto.Postgis.Wkb.Endian: instance GHC.Show.Show Database.Esqueleto.Postgis.Wkb.Endian.EndianType
+ Database.Esqueleto.Postgis.Wkb.Geometry: Geometry :: GeometryType
+ Database.Esqueleto.Postgis.Wkb.Geometry: GeometryCollection :: GeometryType
+ Database.Esqueleto.Postgis.Wkb.Geometry: LineString :: GeometryType
+ Database.Esqueleto.Postgis.Wkb.Geometry: M :: CoordinateType
+ Database.Esqueleto.Postgis.Wkb.Geometry: MultiLineString :: GeometryType
+ Database.Esqueleto.Postgis.Wkb.Geometry: MultiPoint :: GeometryType
+ Database.Esqueleto.Postgis.Wkb.Geometry: MultiPolygon :: GeometryType
+ Database.Esqueleto.Postgis.Wkb.Geometry: Point :: GeometryType
+ Database.Esqueleto.Postgis.Wkb.Geometry: Polygon :: GeometryType
+ Database.Esqueleto.Postgis.Wkb.Geometry: TwoD :: CoordinateType
+ Database.Esqueleto.Postgis.Wkb.Geometry: WkbGeom :: GeometryType -> CoordinateType -> WkbGeometryType
+ Database.Esqueleto.Postgis.Wkb.Geometry: Z :: CoordinateType
+ Database.Esqueleto.Postgis.Wkb.Geometry: ZM :: CoordinateType
+ Database.Esqueleto.Postgis.Wkb.Geometry: builderWkbGeom :: EndianType -> WkbGeometryType -> Builder
+ Database.Esqueleto.Postgis.Wkb.Geometry: coordTypeOfLinearRings :: Seq (LinearRing GeoPositionWithoutCRS) -> CoordinateType
+ Database.Esqueleto.Postgis.Wkb.Geometry: coordTypeOfSequence :: Seq GeoPositionWithoutCRS -> CoordinateType
+ Database.Esqueleto.Postgis.Wkb.Geometry: data CoordinateType
+ Database.Esqueleto.Postgis.Wkb.Geometry: data GeometryType
+ Database.Esqueleto.Postgis.Wkb.Geometry: data WkbGeometryType
+ Database.Esqueleto.Postgis.Wkb.Geometry: geoPositionWithoutCRSToCoordinateType :: GeoPositionWithoutCRS -> Maybe CoordinateType
+ Database.Esqueleto.Postgis.Wkb.Geometry: getWkbGeom :: EndianType -> Get WkbGeometryType
+ Database.Esqueleto.Postgis.Wkb.Geometry: instance GHC.Classes.Eq Database.Esqueleto.Postgis.Wkb.Geometry.CoordinateType
+ Database.Esqueleto.Postgis.Wkb.Geometry: instance GHC.Classes.Eq Database.Esqueleto.Postgis.Wkb.Geometry.GeometryType
+ Database.Esqueleto.Postgis.Wkb.Geometry: instance GHC.Classes.Eq Database.Esqueleto.Postgis.Wkb.Geometry.WkbGeometryType
+ Database.Esqueleto.Postgis.Wkb.Geometry: instance GHC.Show.Show Database.Esqueleto.Postgis.Wkb.Geometry.CoordinateType
+ Database.Esqueleto.Postgis.Wkb.Geometry: instance GHC.Show.Show Database.Esqueleto.Postgis.Wkb.Geometry.GeometryType
+ Database.Esqueleto.Postgis.Wkb.Geometry: instance GHC.Show.Show Database.Esqueleto.Postgis.Wkb.Geometry.WkbGeometryType
+ Database.Esqueleto.Postgis.Wkb.Geometry: type BuilderWkbGeometryType = EndianType -> WkbGeometryType -> Builder
+ Database.Esqueleto.Postgis.Wkb.GeometryCollection: builderGeometryCollection :: BuilderGeospatialFeature -> BuilderWkbGeometryType -> EndianType -> Seq GeospatialGeometry -> Builder
+ Database.Esqueleto.Postgis.Wkb.GeometryCollection: getEnclosedFeature :: (EndianType -> Get WkbGeometryType) -> GeometryType -> (EndianType -> CoordinateType -> Get feature) -> Get feature
+ Database.Esqueleto.Postgis.Wkb.GeometryCollection: getGeometryCollection :: Get GeospatialGeometry -> EndianType -> CoordinateType -> Get GeospatialGeometry
+ Database.Esqueleto.Postgis.Wkb.Geospatial: builderGeospatialGeometry :: BuilderWkbGeometryType -> EndianType -> GeospatialGeometry -> Builder
+ Database.Esqueleto.Postgis.Wkb.Geospatial: getGeospatialGeometry :: (EndianType -> Get WkbGeometryType) -> Get GeospatialGeometry
+ Database.Esqueleto.Postgis.Wkb.Line: builderLine :: BuilderWkbGeometryType -> EndianType -> GeoLine -> Builder
+ Database.Esqueleto.Postgis.Wkb.Line: builderMultiLine :: BuilderWkbGeometryType -> EndianType -> GeoMultiLine -> Builder
+ Database.Esqueleto.Postgis.Wkb.Line: getLine :: EndianType -> CoordinateType -> Get GeospatialGeometry
+ Database.Esqueleto.Postgis.Wkb.Line: getMultiLine :: (EndianType -> Get WkbGeometryType) -> EndianType -> CoordinateType -> Get GeospatialGeometry
+ Database.Esqueleto.Postgis.Wkb.Point: builderCoordPoint :: EndianType -> GeoPositionWithoutCRS -> Builder
+ Database.Esqueleto.Postgis.Wkb.Point: builderCoordPoints :: EndianType -> Seq GeoPositionWithoutCRS -> Builder
+ Database.Esqueleto.Postgis.Wkb.Point: builderMultiPoint :: BuilderWkbGeometryType -> EndianType -> GeoMultiPoint -> Builder
+ Database.Esqueleto.Postgis.Wkb.Point: builderPoint :: BuilderWkbGeometryType -> EndianType -> GeoPoint -> Builder
+ Database.Esqueleto.Postgis.Wkb.Point: getCoordPoint :: EndianType -> CoordinateType -> Get GeoPositionWithoutCRS
+ Database.Esqueleto.Postgis.Wkb.Point: getCoordPoints :: EndianType -> CoordinateType -> Word32 -> Get (Seq GeoPositionWithoutCRS)
+ Database.Esqueleto.Postgis.Wkb.Point: getGeoPoint :: EndianType -> CoordinateType -> Get GeoPoint
+ Database.Esqueleto.Postgis.Wkb.Point: getMultiPoint :: (EndianType -> Get WkbGeometryType) -> EndianType -> CoordinateType -> Get GeospatialGeometry
+ Database.Esqueleto.Postgis.Wkb.Point: getPoint :: EndianType -> CoordinateType -> Get GeospatialGeometry
+ Database.Esqueleto.Postgis.Wkb.Polygon: builderMultiPolygon :: BuilderWkbGeometryType -> EndianType -> GeoMultiPolygon -> Builder
+ Database.Esqueleto.Postgis.Wkb.Polygon: builderPolygon :: BuilderWkbGeometryType -> EndianType -> GeoPolygon -> Builder
+ Database.Esqueleto.Postgis.Wkb.Polygon: getMultiPolygon :: (EndianType -> Get WkbGeometryType) -> EndianType -> CoordinateType -> Get GeospatialGeometry
+ Database.Esqueleto.Postgis.Wkb.Polygon: getPolygon :: EndianType -> CoordinateType -> Get GeospatialGeometry

Files

Changelog.md view
@@ -1,5 +1,19 @@ # Change log for esqueleto-postgis project +## Version 1.2.0 ++ re-export point, less annoying to use.++ adopt wkt-geom package and put it in as a submodule,+  original library doesn't appear to be maintained.+  this way we're a step closer to stackage.+  + got rid of the internal convention, +    it goes against pvp. If we want some specialized+    usage package I should split off a core package instead.+    For now I don't care I don't think I'm not changing those +    functions anyway.+  + also ported over the test suite++ swap out bytestring-base16 for base16, which appears maintained.++ ## Version 1.1.0  + Add st_union + add getPoitns to escape the postgis geometry more easily.
esqueleto-postgis.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           esqueleto-postgis-version:        1.1.0+version:        1.2.0 homepage:       https://github.com/jappeace/esqueleto-postgis#readme bug-reports:    https://github.com/jappeace/esqueleto-postgis/issues author:         Jappie Klooster@@ -64,8 +64,10 @@       esqueleto >= 3.5.10 && < 3.7,       text >= 1.2.5 && < 2.2,       persistent >= 2.13.3 && < 2.19,-      geojson >= 4.1.1 && < 4.2,-      wkt-geom >= 0.0.12 && < 0.1,+      geojson >= 4.1.0 && < 4.2,+      bytestring < 2,+      base16 >= 1.0 && < 1.1,+      binary < 1,     default-language: Haskell2010@@ -74,6 +76,18 @@   import: common-options   exposed-modules:       Database.Esqueleto.Postgis+      Database.Esqueleto.Postgis.Ewkb+      Database.Esqueleto.Postgis.Hex+      Database.Esqueleto.Postgis.Ewkb.Geometry+      Database.Esqueleto.Postgis.Wkb.Endian+      Database.Esqueleto.Postgis.Wkb.Geometry+      Database.Esqueleto.Postgis.Wkb.GeometryCollection+      Database.Esqueleto.Postgis.Wkb.Geospatial+      Database.Esqueleto.Postgis.Wkb.Line+      Database.Esqueleto.Postgis.Wkb.Point+      Database.Esqueleto.Postgis.Wkb.Polygon+      Database.Esqueleto.Postgis.Wkb+   hs-source-dirs:       src @@ -82,6 +96,19 @@   type: exitcode-stdio-1.0   main-is: Test.hs   ghc-options: -Wno-unused-packages+  other-modules:+        Data.SpecHelper+        Ewkb.GeometrySpec+        Ewkb.LineSpec+        Ewkb.PointSpec+        Wkb.EndianSpec+        Wkb.GeometryCollectionSpec+        Wkb.GeometrySpec+        Wkb.HexParsingSpec+        Wkb.LineSpec+        Wkb.PointSpec+        Wkb.PolygonSpec+   hs-source-dirs:       test   build-depends:@@ -93,3 +120,6 @@       resourcet,       monad-logger,       hedgehog,+      hspec,+      tasty-hspec,+      hw-hspec-hedgehog < 0.2 &&   >= 0.1.0.4
src/Database/Esqueleto/Postgis.hs view
@@ -19,15 +19,20 @@     st_point,     st_point_xyz,     st_point_xyzm,++    -- * re-exports+    PointXY(..),+    PointXYZ(..),+    PointXYZM(..),   ) where  import Data.Bifunctor (first)-import Data.Ewkb (parseHexByteString)+import Database.Esqueleto.Postgis.Ewkb (parseHexByteString) import Data.Foldable (Foldable (toList), fold) import Data.Geospatial (GeoPoint (..), GeoPositionWithoutCRS (..), GeospatialGeometry, PointXY (..), PointXYZ (..), PointXYZM (..)) import Data.Geospatial qualified as Geospatial-import Data.Hex (Hex (..))+import Database.Esqueleto.Postgis.Hex (Hex (..)) import Data.LineString (LineString, fromLineString, lineStringHead) import Data.LinearRing (LinearRing, fromLinearRing, makeLinearRing, ringHead, toSeq) import Data.List qualified as List
+ src/Database/Esqueleto/Postgis/Ewkb.hs view
@@ -0,0 +1,52 @@+-- | I copied these because the original libraryy doesn't+--   appear to be maintained.+--   I'm only interested in this parseHexBytestring function.+--+--   friendly copied from github.com/zellige/wkt-geom.git+--   Copyright 2017-2018 wkt-geom Project+--   Apache license+--+--+-- Refer to the eWKB Postgis Documentation <https://postgis.net/docs/using_postgis_dbmanagement.html#EWKB_EWKT>+--+-- Allows parsing of ByteString into a Geospatial Object.+--+-------------------------------------------------------------------+module Database.Esqueleto.Postgis.Ewkb+  ( parseByteString+  , parseHexByteString+  , toByteString+  ) 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 qualified Database.Esqueleto.Postgis.Hex  as Hex++import qualified Database.Esqueleto.Postgis.Ewkb.Geometry  as EwkbGeometry+import qualified Database.Esqueleto.Postgis.Wkb.Endian     as Endian+import qualified Database.Esqueleto.Postgis.Wkb.Geospatial as WkbGeospatial++-- |+-- Representation of EWKB as Binary+parseByteString :: LazyByteString.ByteString -> Either String Geospatial.GeospatialGeometry+parseByteString byteString =+  case BinaryGet.runGetOrFail+        (WkbGeospatial.getGeospatialGeometry EwkbGeometry.getWkbGeom)+        byteString of+    Left (_, _, err)                 -> Left $ "Could not parse ewkb: " ++ err+    Right (_, _, geoSpatialGeometry) -> Right geoSpatialGeometry++-- |+-- Representation of EWKB as a String in Base16/Hex form i.e. "0101000000000000000000f03f0000000000000040" is POINT 1.0 2.0+parseHexByteString :: Hex.Hex -> Either String Geospatial.GeospatialGeometry+parseHexByteString = Hex.safeConvert parseByteString++-- |+-- Produce the binary representation of EWKB given its EndianType (Little or Big - Intel is Little) and SRID (4326 for example).+toByteString :: Endian.EndianType -> EwkbGeometry.SridType -> Geospatial.GeospatialGeometry -> LazyByteString.ByteString+toByteString endianType sridType =+  ByteStringBuilder.toLazyByteString . WkbGeospatial.builderGeospatialGeometry+    mkBuilder endianType+  where mkBuilder eType gType = EwkbGeometry.builderEwkbGeom eType (EwkbGeometry.EwkbGeom gType sridType)
+ src/Database/Esqueleto/Postgis/Ewkb/Geometry.hs view
@@ -0,0 +1,148 @@+module Database.Esqueleto.Postgis.Ewkb.Geometry+  ( EwkbGeometryType (..)+  , SridType (..)+  , getEwkbGeom+  , getWkbGeom+  , builderWkbGeom+  , builderEwkbGeom+  ) where++import qualified Control.Monad              as Monad+import qualified Data.Binary.Get            as BinaryGet+import           Data.Bits                  ((.&.), (.|.))+import qualified Data.ByteString.Builder    as ByteStringBuilder+import qualified Data.Word                  as Word++import qualified Database.Esqueleto.Postgis.Wkb.Endian   as Endian+import qualified Database.Esqueleto.Postgis.Wkb.Geometry as Geometry++-- Types++data SridType = Srid Word.Word32 | NoSrid deriving (Show, Eq)++data EwkbGeometryType = EwkbGeom Geometry.WkbGeometryType SridType deriving (Show, Eq)+++-- Binary parsers++getEwkbGeom :: Endian.EndianType -> BinaryGet.Get EwkbGeometryType+getEwkbGeom endianType = do+  rawGeometryType <- Endian.getFourBytes endianType+  ewkbSrid <- getEwkbSrid endianType rawGeometryType+  geomType <- rawtoWkbGeometryType rawGeometryType+  pure $ EwkbGeom geomType ewkbSrid++getWkbGeom :: Endian.EndianType -> BinaryGet.Get Geometry.WkbGeometryType+getWkbGeom endianType = do+  rawGeometryType <- Endian.getFourBytes endianType+  _ <- getEwkbSrid endianType rawGeometryType+  rawtoWkbGeometryType rawGeometryType++getEwkbSrid :: Endian.EndianType -> Word.Word32 -> BinaryGet.Get SridType+getEwkbSrid endianType int =+  if int .&. sridMask /= 0 then do+    srid <- Endian.getFourBytes endianType+    if srid == supportedSrid then+      pure $ Srid srid+    else+      Monad.fail $ "Invalid SRID only " <> show supportedSrid <> " supported: " ++ show srid+  else+    pure NoSrid+++-- Binary builders++builderEwkbGeom :: Endian.EndianType -> EwkbGeometryType -> ByteStringBuilder.Builder+builderEwkbGeom endianType (EwkbGeom wkbGeometryType NoSrid) =+  builderWkbGeom endianType wkbGeometryType+builderEwkbGeom endianType (EwkbGeom wkbGeometryType (Srid srid)) = do+  let int = wkbGeometryTypeToInt wkbGeometryType .|. sridMask+  Endian.builderFourBytes endianType int+    <> Endian.builderFourBytes endianType srid++builderWkbGeom :: Endian.EndianType -> Geometry.WkbGeometryType -> ByteStringBuilder.Builder+builderWkbGeom endianType wkbGeometryType =+  Endian.builderFourBytes endianType $ wkbGeometryTypeToInt wkbGeometryType+++-- Helpers++wkbGeometryTypeToInt :: Geometry.WkbGeometryType -> Word.Word32+wkbGeometryTypeToInt (Geometry.WkbGeom geometryType coordinateType) =+  coordinateTypeToInt coordinateType .|. geometryTypeToInt geometryType++rawtoWkbGeometryType :: Word.Word32 -> BinaryGet.Get Geometry.WkbGeometryType+rawtoWkbGeometryType rawGeometryType = do+  let geomType = intToGeometryType rawGeometryType+      coordType = intToCoordinateType rawGeometryType+  case geomType of+    Just g -> pure $ Geometry.WkbGeom g coordType+    _      -> Monad.fail $ "Invalid EwkbGeometry: " ++ show rawGeometryType+++intToGeometryType :: Word.Word32 -> Maybe Geometry.GeometryType+intToGeometryType int =+  case int .&. geometryMask of+    0 -> Just Geometry.Geometry+    1 -> Just Geometry.Point+    2 -> Just Geometry.LineString+    3 -> Just Geometry.Polygon+    4 -> Just Geometry.MultiPoint+    5 -> Just Geometry.MultiLineString+    6 -> Just Geometry.MultiPolygon+    7 -> Just Geometry.GeometryCollection+    _ -> Nothing++geometryTypeToInt :: Geometry.GeometryType -> Word.Word32+geometryTypeToInt geometryType =+  case geometryType of+    Geometry.Geometry           -> 0+    Geometry.Point              -> 1+    Geometry.LineString         -> 2+    Geometry.Polygon            -> 3+    Geometry.MultiPoint         -> 4+    Geometry.MultiLineString    -> 5+    Geometry.MultiPolygon       -> 6+    Geometry.GeometryCollection -> 7++intToCoordinateType :: Word.Word32 -> Geometry.CoordinateType+intToCoordinateType int =+  case (hasZ int, hasM int) of+    (False, False) -> Geometry.TwoD+    (False, True)  -> Geometry.M+    (True, False)  -> Geometry.Z+    (True, True)   -> Geometry.ZM++coordinateTypeToInt :: Geometry.CoordinateType -> Word.Word32+coordinateTypeToInt coordinateType =+  case coordinateType of+    Geometry.TwoD -> 0+    Geometry.Z    -> zMask+    Geometry.M    -> mMask+    Geometry.ZM   -> zMask .|. mMask++hasZ :: Word.Word32 -> Bool+hasZ int =+  int .&. zMask /= 0++hasM :: Word.Word32 -> Bool+hasM int =+  int .&. 0x40000000 /= 0+++-- Constants++zMask :: Word.Word32+zMask = 0x80000000++mMask :: Word.Word32+mMask = 0x40000000++sridMask :: Word.Word32+sridMask = 0x20000000++geometryMask :: Word.Word32+geometryMask = 0x0fffffff++supportedSrid :: Word.Word32+supportedSrid = 4326
+ src/Database/Esqueleto/Postgis/Hex.hs view
@@ -0,0 +1,23 @@++-- | friendly copied from github.com/zellige/wkt-geom.git+--   Copyright 2017-2018 wkt-geom Project+--   Apache license+module Database.Esqueleto.Postgis.Hex+  ( safeConvert+  , Hex(..)+  ) where++import qualified Data.ByteString        as ByteString+import qualified Data.ByteString.Base16 as ByteStringBase16+import qualified Data.ByteString.Char8  as ByteStringChar8+import qualified Data.ByteString.Lazy   as LazyByteString+import qualified Data.Geospatial        as Geospatial++newtype Hex = Hex ByteString.ByteString++safeConvert :: (LazyByteString.ByteString -> Either String Geospatial.GeospatialGeometry) -> Hex -> Either String Geospatial.GeospatialGeometry+safeConvert f (Hex byteString) =+    case ByteStringBase16.decodeBase16Untyped byteString of+         Left  _       -> Left $ "Invalid hex representation: " <> ByteStringChar8.unpack byteString+         Right decoded -> f    $ LazyByteString.fromStrict decoded+
+ src/Database/Esqueleto/Postgis/Wkb.hs view
@@ -0,0 +1,41 @@+-- Refer to the WKB Wikipedia page <https://en.wikipedia.org/wiki/Well-known_text#Well-known_binary>+--+-- Allows parsing of ByteString into a Geospatial Object.+--+-------------------------------------------------------------------+module Database.Esqueleto.Postgis.Wkb+  ( parseByteString+  , parseHexByteString+  , toByteString+  ) 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 qualified Database.Esqueleto.Postgis.Hex                     as Hex++import qualified Database.Esqueleto.Postgis.Wkb.Endian     as Endian+import qualified Database.Esqueleto.Postgis.Wkb.Geometry   as Geometry+import qualified Database.Esqueleto.Postgis.Wkb.Geospatial as WkbGeospatial++-- |+-- Representation of WKB as Binary+parseByteString :: LazyByteString.ByteString -> Either String Geospatial.GeospatialGeometry+parseByteString byteString =+  case BinaryGet.runGetOrFail+        (WkbGeospatial.getGeospatialGeometry Geometry.getWkbGeom)+        byteString of+    Left (_, _, err)                 -> Left $ "Could not parse wkb: " ++ err+    Right (_, _, geoSpatialGeometry) -> Right geoSpatialGeometry++-- |+-- Representation of WKB as a String in Base16/Hex form i.e. "0101000000000000000000f03f0000000000000040" is POINT 1.0 2.0+parseHexByteString :: Hex.Hex -> Either String Geospatial.GeospatialGeometry+parseHexByteString = Hex.safeConvert parseByteString++-- |+-- 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 =+  ByteStringBuilder.toLazyByteString . WkbGeospatial.builderGeospatialGeometry Geometry.builderWkbGeom endianType
+ src/Database/Esqueleto/Postgis/Wkb/Endian.hs view
@@ -0,0 +1,63 @@+module Database.Esqueleto.Postgis.Wkb.Endian+  ( EndianType (..)+  , getEndianType+  , getFourBytes+  , getDouble+  , builderEndianType+  , builderFourBytes+  , builderDouble+  ) where++import qualified Control.Monad           as Monad+import qualified Data.Binary.Get         as BinaryGet+import qualified Data.ByteString.Builder as ByteStringBuilder+import qualified Data.Word               as Word++data EndianType = LittleEndian | BigEndian deriving (Show, Eq)++getEndianType :: BinaryGet.Get EndianType+getEndianType = do+  byte <- BinaryGet.getWord8+  case byte of+    0 ->+      pure BigEndian+    1 ->+      pure LittleEndian+    _ ->+      Monad.fail "Invalid EndianType"++getFourBytes :: EndianType -> BinaryGet.Get Word.Word32+getFourBytes endianType =+  case endianType of+    LittleEndian ->+      BinaryGet.getWord32le+    BigEndian ->+      BinaryGet.getWord32be++getDouble :: EndianType -> BinaryGet.Get Double+getDouble endianType =+  case endianType of+    LittleEndian ->+      BinaryGet.getDoublele+    BigEndian ->+      BinaryGet.getDoublebe++builderEndianType :: EndianType -> ByteStringBuilder.Builder+builderEndianType BigEndian    = ByteStringBuilder.word8 0+builderEndianType LittleEndian = ByteStringBuilder.word8 1++builderFourBytes :: EndianType -> Word.Word32 -> ByteStringBuilder.Builder+builderFourBytes endianType =+  case endianType of+    LittleEndian ->+      ByteStringBuilder.word32LE+    BigEndian ->+      ByteStringBuilder.word32BE++builderDouble :: EndianType -> Double -> ByteStringBuilder.Builder+builderDouble endianType =+  case endianType of+    LittleEndian ->+      ByteStringBuilder.doubleLE+    BigEndian ->+      ByteStringBuilder.doubleBE
+ src/Database/Esqueleto/Postgis/Wkb/Geometry.hs view
@@ -0,0 +1,123 @@+module Database.Esqueleto.Postgis.Wkb.Geometry+  ( GeometryType (..)+  , CoordinateType (..)+  , WkbGeometryType (..)+  , BuilderWkbGeometryType+  , getWkbGeom+  , builderWkbGeom+  , geoPositionWithoutCRSToCoordinateType+  , coordTypeOfSequence+  , coordTypeOfLinearRings+  ) where++import qualified Control.Monad            as Monad+import qualified Data.Binary.Get          as BinaryGet+import qualified Data.ByteString.Builder  as ByteStringBuilder+import qualified Data.Geospatial          as Geospatial+import qualified Data.LinearRing          as LinearRing+import qualified Data.Maybe               as Maybe+import qualified Data.Sequence            as Sequence+import qualified Data.Word                as Word++import qualified Database.Esqueleto.Postgis.Wkb.Endian as Endian++-- Types++data GeometryType+  = Geometry+  | Point+  | LineString+  | Polygon+  | MultiPoint+  | MultiLineString+  | MultiPolygon+  | GeometryCollection deriving (Show, Eq)++data CoordinateType = TwoD | Z | M | ZM  deriving (Show, Eq)++data WkbGeometryType = WkbGeom GeometryType CoordinateType deriving (Show, Eq)+++-- Binary parsers++getWkbGeom :: Endian.EndianType -> BinaryGet.Get WkbGeometryType+getWkbGeom endianType = do+  fullGeometryType <- Endian.getFourBytes endianType+  let geomType = intToGeometryType $ fullGeometryType `rem` 1000+      coordType = intToCoordinateType $ fullGeometryType `div` 1000+  case (geomType, coordType) of+    (Just g, Just c) -> pure $ WkbGeom g c+    _                ->+      Monad.fail $ "Invalid WkbGeometryType: " ++ show fullGeometryType+++-- Binary builders++type BuilderWkbGeometryType = Endian.EndianType -> WkbGeometryType -> ByteStringBuilder.Builder++builderWkbGeom :: Endian.EndianType -> WkbGeometryType -> ByteStringBuilder.Builder+builderWkbGeom endianType (WkbGeom geometryType coordinateType) = do+  let int = coordinateTypeToInt coordinateType * 1000 + geometryTypeToInt geometryType+  Endian.builderFourBytes endianType int+++-- Helpers++geoPositionWithoutCRSToCoordinateType :: Geospatial.GeoPositionWithoutCRS -> Maybe CoordinateType+geoPositionWithoutCRSToCoordinateType geoPosition =+  case geoPosition of+    Geospatial.GeoEmpty       -> Nothing+    Geospatial.GeoPointXY _   -> Just TwoD+    Geospatial.GeoPointXYZ _  -> Just Z+    Geospatial.GeoPointXYZM _ -> Just ZM++coordTypeOfSequence :: Sequence.Seq Geospatial.GeoPositionWithoutCRS -> CoordinateType+coordTypeOfSequence (first Sequence.:<| _) =+  Maybe.fromMaybe TwoD (geoPositionWithoutCRSToCoordinateType first)+coordTypeOfSequence _ = TwoD++coordTypeOfLinearRings :: Sequence.Seq (LinearRing.LinearRing Geospatial.GeoPositionWithoutCRS) -> CoordinateType+coordTypeOfLinearRings (first Sequence.:<| _) = coordTypeOfSequence $ LinearRing.toSeq first+coordTypeOfLinearRings _ = TwoD++intToGeometryType :: Word.Word32 -> Maybe GeometryType+intToGeometryType int =+  case int of+    0 -> Just Geometry+    1 -> Just Point+    2 -> Just LineString+    3 -> Just Polygon+    4 -> Just MultiPoint+    5 -> Just MultiLineString+    6 -> Just MultiPolygon+    7 -> Just GeometryCollection+    _ -> Nothing++geometryTypeToInt :: GeometryType -> Word.Word32+geometryTypeToInt geometryType =+  case geometryType of+    Geometry           -> 0+    Point              -> 1+    LineString         -> 2+    Polygon            -> 3+    MultiPoint         -> 4+    MultiLineString    -> 5+    MultiPolygon       -> 6+    GeometryCollection -> 7++intToCoordinateType :: Word.Word32 -> Maybe CoordinateType+intToCoordinateType int =+  case int of+    0 -> Just TwoD+    1 -> Just Z+    2 -> Just M+    3 -> Just ZM+    _ -> Nothing++coordinateTypeToInt :: CoordinateType -> Word.Word32+coordinateTypeToInt coordinateType =+  case coordinateType of+    TwoD -> 0+    Z    -> 1+    M    -> 2+    ZM   -> 3
+ src/Database/Esqueleto/Postgis/Wkb/GeometryCollection.hs view
@@ -0,0 +1,56 @@+module Database.Esqueleto.Postgis.Wkb.GeometryCollection+  ( getGeometryCollection+  , getEnclosedFeature+  , builderGeometryCollection+  ) where++import qualified Control.Monad              as Monad+import qualified Data.Binary.Get            as BinaryGet+import qualified Data.ByteString.Builder    as ByteStringBuilder+import qualified Data.Foldable              as Foldable+import qualified Data.Geospatial            as Geospatial+import qualified Data.Sequence              as Sequence++import qualified Database.Esqueleto.Postgis.Wkb.Endian   as Endian+import qualified Database.Esqueleto.Postgis.Wkb.Geometry as Geometry+++-- Binary parsers++getGeometryCollection :: BinaryGet.Get Geospatial.GeospatialGeometry+                          -> Endian.EndianType+                          -> Geometry.CoordinateType+                          -> BinaryGet.Get Geospatial.GeospatialGeometry+getGeometryCollection getGeospatialGeometry endianType _ = do+  numberOfGeometries <- Endian.getFourBytes endianType+  geoSpatialGeometries <- Sequence.replicateM (fromIntegral numberOfGeometries) getGeospatialGeometry+  pure $ Geospatial.Collection geoSpatialGeometries++getEnclosedFeature :: (Endian.EndianType -> BinaryGet.Get Geometry.WkbGeometryType)+                      -> Geometry.GeometryType+                      -> (Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get feature)+                      -> BinaryGet.Get feature+getEnclosedFeature getWkbGeom expectedGeometryType getFeature = do+  endianType <- Endian.getEndianType+  geometryTypeWithCoords <- getWkbGeom endianType+  let (Geometry.WkbGeom geoType coordType) = geometryTypeWithCoords+  if geoType == expectedGeometryType then+    getFeature endianType coordType+  else+    Monad.fail "Wrong geometry type of enclosed feature"+++-- Binary builders++type BuilderGeospatialFeature = Geometry.BuilderWkbGeometryType -> Endian.EndianType ->  Geospatial.GeospatialGeometry -> ByteStringBuilder.Builder++builderGeometryCollection :: BuilderGeospatialFeature+                              -> Geometry.BuilderWkbGeometryType+                              -> Endian.EndianType+                              -> Sequence.Seq Geospatial.GeospatialGeometry+                              -> ByteStringBuilder.Builder+builderGeometryCollection builderGeospatialFeature builderWkbGeom endianType geometryCollection =+  Endian.builderEndianType endianType+    <> builderWkbGeom endianType (Geometry.WkbGeom Geometry.GeometryCollection Geometry.TwoD)+    <> Endian.builderFourBytes endianType (fromIntegral $ length geometryCollection)+    <> Foldable.foldMap (builderGeospatialFeature builderWkbGeom endianType) geometryCollection
+ src/Database/Esqueleto/Postgis/Wkb/Geospatial.hs view
@@ -0,0 +1,68 @@+module Database.Esqueleto.Postgis.Wkb.Geospatial+  ( getGeospatialGeometry+  , builderGeospatialGeometry+  ) where++import qualified Data.Binary.Get                      as BinaryGet+import qualified Data.ByteString.Builder              as ByteStringBuilder+import qualified Data.Geospatial                      as Geospatial+import qualified Data.Monoid                          as Monoid++import qualified Database.Esqueleto.Postgis.Wkb.Endian             as Endian+import qualified Database.Esqueleto.Postgis.Wkb.Geometry           as Geometry+import qualified Database.Esqueleto.Postgis.Wkb.GeometryCollection as GeometryCollection+import qualified Database.Esqueleto.Postgis.Wkb.Line               as Line+import qualified Database.Esqueleto.Postgis.Wkb.Point              as Point+import qualified Database.Esqueleto.Postgis.Wkb.Polygon            as Polygon+++-- Binary parsers++getGeospatialGeometry :: (Endian.EndianType -> BinaryGet.Get Geometry.WkbGeometryType)+                      -> BinaryGet.Get Geospatial.GeospatialGeometry+getGeospatialGeometry getWkbGeom = do+  endianType <- Endian.getEndianType+  geometryTypeWithCoords <- getWkbGeom endianType+  let (Geometry.WkbGeom geomType coordType) = geometryTypeWithCoords+  getFeature geomType getWkbGeom endianType coordType++getFeature :: Geometry.GeometryType+           -> (Endian.EndianType -> BinaryGet.Get Geometry.WkbGeometryType)+           -> Endian.EndianType+           -> Geometry.CoordinateType+           -> BinaryGet.Get Geospatial.GeospatialGeometry+getFeature geomType getWkbGeom =+  case geomType of+    Geometry.Geometry           -> getNoGeometry+    Geometry.Point              -> Point.getPoint+    Geometry.LineString         -> Line.getLine+    Geometry.Polygon            -> Polygon.getPolygon+    Geometry.MultiPoint         -> Point.getMultiPoint getWkbGeom+    Geometry.MultiLineString    -> Line.getMultiLine getWkbGeom+    Geometry.MultiPolygon       -> Polygon.getMultiPolygon getWkbGeom+    Geometry.GeometryCollection -> GeometryCollection.getGeometryCollection (getGeospatialGeometry getWkbGeom)++getNoGeometry :: Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get Geospatial.GeospatialGeometry+getNoGeometry _ _ =+  pure Geospatial.NoGeometry+++-- Binary builders++builderGeospatialGeometry :: Geometry.BuilderWkbGeometryType+                          -> Endian.EndianType+                          -> Geospatial.GeospatialGeometry+                          -> ByteStringBuilder.Builder+builderGeospatialGeometry builderWkbGeom endianType geospatialGeometry =+  case geospatialGeometry of+    Geospatial.NoGeometry                   -> Monoid.mempty+    Geospatial.Point geoPoint               -> build Point.builderPoint geoPoint+    Geospatial.Line geoLine                 -> build Line.builderLine geoLine+    Geospatial.Polygon geoPolygon           -> build Polygon.builderPolygon geoPolygon+    Geospatial.MultiPoint geoMultiPoint     -> build Point.builderMultiPoint geoMultiPoint+    Geospatial.MultiLine geoMultiLine       -> build Line.builderMultiLine geoMultiLine+    Geospatial.MultiPolygon geoMultiPolygon -> build Polygon.builderMultiPolygon geoMultiPolygon+    Geospatial.Collection geoCollection     -> builderGeometryCollection endianType geoCollection+  where builderGeometryCollection =+          GeometryCollection.builderGeometryCollection builderGeospatialGeometry builderWkbGeom+        build builder = builder builderWkbGeom endianType
+ src/Database/Esqueleto/Postgis/Wkb/Line.hs view
@@ -0,0 +1,63 @@+module Database.Esqueleto.Postgis.Wkb.Line+  ( Database.Esqueleto.Postgis.Wkb.Line.getLine+  , getMultiLine+  , builderLine+  , builderMultiLine+  ) where++import qualified Control.Monad                        as Monad+import qualified Data.Binary.Get                      as BinaryGet+import qualified Data.ByteString.Builder              as ByteStringBuilder+import qualified Data.Foldable                        as Foldable+import qualified Data.Geospatial                      as Geospatial+import qualified Data.LineString                      as LineString+import qualified Data.Sequence                        as Sequence+++import qualified Database.Esqueleto.Postgis.Wkb.Endian             as Endian+import qualified Database.Esqueleto.Postgis.Wkb.Geometry           as Geometry+import qualified Database.Esqueleto.Postgis.Wkb.GeometryCollection as GeometryCollection+import qualified Database.Esqueleto.Postgis.Wkb.Point              as Point++-- Binary parsers++getLine :: Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get Geospatial.GeospatialGeometry+getLine endianType coordType = do+  gl <- getGeoLine endianType coordType+  pure $ Geospatial.Line gl++getMultiLine :: (Endian.EndianType -> BinaryGet.Get Geometry.WkbGeometryType) -> Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get Geospatial.GeospatialGeometry+getMultiLine getWkbGeom endianType _ = do+  numberOfLines <- Endian.getFourBytes endianType+  geoLines <- Sequence.replicateM (fromIntegral numberOfLines) (GeometryCollection.getEnclosedFeature getWkbGeom Geometry.LineString getGeoLine)+  pure $ Geospatial.MultiLine $ Geospatial.mergeGeoLines geoLines++getGeoLine :: Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get Geospatial.GeoLine+getGeoLine endianType coordType = do+  numberOfPoints <- Endian.getFourBytes endianType+  if numberOfPoints >= 2 then do+    p1 <- Point.getCoordPoint endianType coordType+    p2 <- Point.getCoordPoint endianType coordType+    pts <- Point.getCoordPoints endianType coordType (numberOfPoints - 2)+    pure $ Geospatial.GeoLine $ LineString.makeLineString p1 p2 pts+  else+    Monad.fail "Must have at least two points for a line"+++-- Binary builders++builderLine :: Geometry.BuilderWkbGeometryType -> Endian.EndianType -> Geospatial.GeoLine -> ByteStringBuilder.Builder+builderLine builderWkbGeom endianType (Geospatial.GeoLine lineString) = do+  let coordPoints = LineString.toSeq lineString+      coordType = Geometry.coordTypeOfSequence coordPoints+  Endian.builderEndianType endianType+    <> builderWkbGeom endianType (Geometry.WkbGeom Geometry.LineString coordType)+    <> Endian.builderFourBytes endianType (fromIntegral $ length coordPoints)+    <> Foldable.foldMap (Point.builderCoordPoint endianType) coordPoints++builderMultiLine :: Geometry.BuilderWkbGeometryType -> Endian.EndianType -> Geospatial.GeoMultiLine -> ByteStringBuilder.Builder+builderMultiLine builderWkbGeom endianType (Geospatial.GeoMultiLine lineStrings) =+  Endian.builderEndianType endianType+    <> builderWkbGeom endianType (Geometry.WkbGeom Geometry.MultiLineString Geometry.TwoD)+    <> Endian.builderFourBytes endianType (fromIntegral $ length lineStrings)+    <> Foldable.foldMap (builderLine builderWkbGeom endianType . Geospatial.GeoLine) lineStrings
+ src/Database/Esqueleto/Postgis/Wkb/Point.hs view
@@ -0,0 +1,99 @@+module Database.Esqueleto.Postgis.Wkb.Point+  ( getPoint+  , getMultiPoint+  , getGeoPoint+  , getCoordPoint+  , getCoordPoints+  , builderPoint+  , builderMultiPoint+  , builderCoordPoint+  , builderCoordPoints+  ) where++import qualified Data.Binary.Get                      as BinaryGet+import qualified Data.ByteString.Builder              as ByteStringBuilder+import qualified Data.Foldable                        as Foldable+import qualified Data.Geospatial                      as Geospatial+import qualified Data.Monoid                          as Monoid+import qualified Data.Sequence                        as Sequence+import qualified Data.Word                            as Word++import qualified Database.Esqueleto.Postgis.Wkb.Endian             as Endian+import qualified Database.Esqueleto.Postgis.Wkb.Geometry           as Geometry+import qualified Database.Esqueleto.Postgis.Wkb.GeometryCollection as GeometryCollection++-- Binary parsers++getPoint :: Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get Geospatial.GeospatialGeometry+getPoint endianType coordType = do+  geoPoint <- getGeoPoint endianType coordType+  pure $ Geospatial.Point geoPoint++getMultiPoint :: (Endian.EndianType -> BinaryGet.Get Geometry.WkbGeometryType) -> Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get Geospatial.GeospatialGeometry+getMultiPoint getWkbGeom endianType _ = do+  numberOfPoints <- Endian.getFourBytes endianType+  geoPoints <- Sequence.replicateM (fromIntegral numberOfPoints) (GeometryCollection.getEnclosedFeature getWkbGeom Geometry.Point getGeoPoint)+  pure $ Geospatial.MultiPoint $ Geospatial.mergeGeoPoints geoPoints++getGeoPoint :: Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get Geospatial.GeoPoint+getGeoPoint endianType coordType = do+  p <- getCoordPoint endianType coordType+  pure $ Geospatial.GeoPoint p++getCoordPoint :: Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get Geospatial.GeoPositionWithoutCRS+getCoordPoint endianType coordType =+  case coordType of+    Geometry.TwoD -> do+      point <- Geospatial.PointXY <$> getDouble <*> getDouble+      return $ Geospatial.GeoPointXY point+    Geometry.Z -> do+      point <- Geospatial.PointXYZ <$> getDouble <*> getDouble <*> getDouble+      return $ Geospatial.GeoPointXYZ point+    Geometry.M -> do+      point <- Geospatial.PointXYZ <$> getDouble <*> getDouble <*> getDouble+      return $ Geospatial.GeoPointXYZ point+    Geometry.ZM -> do+      point <- Geospatial.PointXYZM <$> getDouble <*> getDouble <*> getDouble <*> getDouble+      return $ Geospatial.GeoPointXYZM point+  where getDouble = Endian.getDouble endianType++getCoordPoints :: Endian.EndianType -> Geometry.CoordinateType -> Word.Word32 -> BinaryGet.Get (Sequence.Seq Geospatial.GeoPositionWithoutCRS)+getCoordPoints endianType coordType numberOfPoints =+  Sequence.replicateM (fromIntegral numberOfPoints) (getCoordPoint endianType coordType)+++-- Binary builders++builderPoint :: Geometry.BuilderWkbGeometryType -> Endian.EndianType -> Geospatial.GeoPoint -> ByteStringBuilder.Builder+builderPoint builderWkbGeom endianType (Geospatial.GeoPoint coordPoint) =+  case Geometry.geoPositionWithoutCRSToCoordinateType coordPoint of+    Just coordinateType ->+      Endian.builderEndianType endianType+        <> builderWkbGeom endianType (Geometry.WkbGeom Geometry.Point coordinateType)+        <> builderCoordPoint endianType coordPoint+    Nothing ->+      Monoid.mempty++builderMultiPoint :: Geometry.BuilderWkbGeometryType -> Endian.EndianType -> Geospatial.GeoMultiPoint -> ByteStringBuilder.Builder+builderMultiPoint builderWkbGeom endianType (Geospatial.GeoMultiPoint coordPoints) =+  Endian.builderEndianType endianType+    <> builderWkbGeom endianType (Geometry.WkbGeom Geometry.MultiPoint coordType)+    <> Endian.builderFourBytes endianType (fromIntegral $ length coordPoints)+    <> Foldable.foldMap (builderPoint builderWkbGeom endianType . Geospatial.GeoPoint) coordPoints+  where coordType = Geometry.coordTypeOfSequence coordPoints++builderCoordPoint :: Endian.EndianType -> Geospatial.GeoPositionWithoutCRS -> ByteStringBuilder.Builder+builderCoordPoint endianType coordPoint =+  case coordPoint of+    Geospatial.GeoEmpty -> Monoid.mempty+    Geospatial.GeoPointXY (Geospatial.PointXY x y) ->+      Foldable.foldMap builderDouble [x, y]+    Geospatial.GeoPointXYZ (Geospatial.PointXYZ x y z) ->+      Foldable.foldMap builderDouble [x, y, z]+    Geospatial.GeoPointXYZM (Geospatial.PointXYZM x y z m) ->+      Foldable.foldMap builderDouble [x, y, z, m]+  where builderDouble = Endian.builderDouble endianType++builderCoordPoints :: Endian.EndianType -> Sequence.Seq Geospatial.GeoPositionWithoutCRS -> ByteStringBuilder.Builder+builderCoordPoints endianType =+  Foldable.foldMap (builderCoordPoint endianType)
+ src/Database/Esqueleto/Postgis/Wkb/Polygon.hs view
@@ -0,0 +1,89 @@+module Database.Esqueleto.Postgis.Wkb.Polygon+  ( getPolygon+  , getMultiPolygon+  , builderPolygon+  , builderMultiPolygon+  ) where++import qualified Control.Monad                        as Monad+import qualified Data.Binary.Get                      as BinaryGet+import qualified Data.ByteString.Builder              as ByteStringBuilder+import qualified Data.Foldable                        as Foldable+import qualified Data.Geospatial                      as Geospatial+import qualified Data.LinearRing                      as LinearRing+import qualified Data.Sequence                        as Sequence++import qualified Database.Esqueleto.Postgis.Wkb.Endian             as Endian+import qualified Database.Esqueleto.Postgis.Wkb.Geometry           as Geometry+import qualified Database.Esqueleto.Postgis.Wkb.GeometryCollection as GeometryCollection+import qualified Database.Esqueleto.Postgis.Wkb.Point              as Point+import qualified Data.SeqHelper                       as SeqHelper++-- Binary parsers++getPolygon :: Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get Geospatial.GeospatialGeometry+getPolygon endianType coordType = do+  geoPolygon <- getGeoPolygon endianType coordType+  pure $ Geospatial.Polygon geoPolygon++getMultiPolygon :: (Endian.EndianType -> BinaryGet.Get Geometry.WkbGeometryType) -> Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get Geospatial.GeospatialGeometry+getMultiPolygon getWkbGeom endianType _ = do+  numberOfPolygons <- Endian.getFourBytes endianType+  geoPolygons <- Sequence.replicateM (fromIntegral numberOfPolygons) (GeometryCollection.getEnclosedFeature getWkbGeom Geometry.Polygon getGeoPolygon)+  pure $ Geospatial.MultiPolygon $ Geospatial.mergeGeoPolygons geoPolygons++getGeoPolygon :: Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get Geospatial.GeoPolygon+getGeoPolygon endianType coordType = do+  linearRings <- getLinearRings endianType coordType+  pure $ Geospatial.GeoPolygon linearRings++getLinearRings :: Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get (Sequence.Seq (LinearRing.LinearRing Geospatial.GeoPositionWithoutCRS))+getLinearRings endianType coordType = do+  numberOfRings <- Endian.getFourBytes endianType+  Sequence.replicateM (fromIntegral numberOfRings) (getLinearRing endianType coordType)++getLinearRing :: Endian.EndianType -> Geometry.CoordinateType -> BinaryGet.Get (LinearRing.LinearRing Geospatial.GeoPositionWithoutCRS)+getLinearRing endianType coordType = do+  numberOfPoints <- Endian.getFourBytes endianType+  if numberOfPoints >= 4 then do+    p1 <- Point.getCoordPoint endianType coordType+    p2 <- Point.getCoordPoint endianType coordType+    p3 <- Point.getCoordPoint endianType coordType+    pts@(_ Sequence.:|> lastS) <- Point.getCoordPoints endianType coordType (numberOfPoints - 3)+    if lastS == p1 then+      pure $ LinearRing.makeLinearRing p1 p2 p3 (SeqHelper.sequenceHead pts)+    else+      Monad.fail $+        "First and last points of linear ring are different: first="+         ++ show p1 ++ " last=" ++ show lastS+  else+    Monad.fail $+      "Must have at least four points for a linear ring: "+       ++ show numberOfPoints+++-- Binary builders++builderPolygon :: Geometry.BuilderWkbGeometryType -> Endian.EndianType -> Geospatial.GeoPolygon -> ByteStringBuilder.Builder+builderPolygon builderWkbGeom endianType (Geospatial.GeoPolygon linearRings) = do+  let coordType = Geometry.coordTypeOfLinearRings linearRings+  Endian.builderEndianType endianType+    <> builderWkbGeom endianType (Geometry.WkbGeom Geometry.Polygon coordType)+    <> Endian.builderFourBytes endianType (fromIntegral $ length linearRings)+    <> Foldable.foldMap (builderLinearRing endianType) linearRings++builderMultiPolygon :: Geometry.BuilderWkbGeometryType -> Endian.EndianType -> Geospatial.GeoMultiPolygon -> ByteStringBuilder.Builder+builderMultiPolygon builderWkbGeom endianType (Geospatial.GeoMultiPolygon polygons) =+  Endian.builderEndianType endianType+    <> builderWkbGeom endianType (Geometry.WkbGeom Geometry.MultiPolygon Geometry.TwoD)+    <> Endian.builderFourBytes endianType (fromIntegral $ length polygons)+    <> Foldable.foldMap (builderPolygon builderWkbGeom endianType . Geospatial.GeoPolygon) polygons++builderLinearRing :: Endian.EndianType -> LinearRing.LinearRing Geospatial.GeoPositionWithoutCRS -> ByteStringBuilder.Builder+builderLinearRing endianType linearRing = do+  let coordPoints = LinearRing.toSeq linearRing+      lastCoordPoint = LinearRing.ringHead linearRing+      lengthOfRing = fromIntegral $ length coordPoints + 1+  Endian.builderFourBytes endianType lengthOfRing+    <> Foldable.foldMap (Point.builderCoordPoint endianType) coordPoints+    <> Point.builderCoordPoint endianType lastCoordPoint
+ test/Data/SpecHelper.hs view
@@ -0,0 +1,182 @@+module Data.SpecHelper where++import qualified Data.Geospatial             as Geospatial+import qualified Data.LinearRing             as LinearRing+import qualified Data.LineString             as LineString+import qualified Data.Sequence               as Sequence+import qualified Data.Word                   as Word+import qualified HaskellWorks.Hspec.Hedgehog as HedgehogHspec+import           Hedgehog+import qualified Hedgehog.Gen                as Gen+import qualified Hedgehog.Range              as Range+import           Test.Hspec                  (Spec, it)++import qualified Database.Esqueleto.Postgis.Ewkb                   as Ewkb+import qualified Database.Esqueleto.Postgis.Ewkb.Geometry as EwkbGeometry+import qualified Database.Esqueleto.Postgis.Wkb.Endian    as Endian+import qualified Database.Esqueleto.Postgis.Wkb.Geometry  as Geometry+import qualified Database.Esqueleto.Postgis.Wkb                    as Wkb++-- Helpers++type GeometryGenerator = Gen Geospatial.GeoPositionWithoutCRS -> Gen Geospatial.GeospatialGeometry++testRoundTripWkbGeometryParsing :: String -> GeometryGenerator -> Spec+testRoundTripWkbGeometryParsing name generator =+  mapM_ (testRoundTripGeometryParsing' roundTripWkb name generator) coordPointGenerators++testRoundTripEwkbGeometryParsing :: String -> GeometryGenerator -> Spec+testRoundTripEwkbGeometryParsing name generator =+  mapM_ (testRoundTripGeometryParsing' roundTripEwkb name generator) coordPointGenerators++testRoundTripGeometryParsing' :: (Endian.EndianType -> Geospatial.GeospatialGeometry -> PropertyT IO ()) -> String -> GeometryGenerator -> (Geometry.CoordinateType, Gen Geospatial.GeoPositionWithoutCRS) -> Spec+testRoundTripGeometryParsing' f name generator (coordType, genCoordPoint) =+  it ("round trips wkb " ++ name ++ ": " ++ show coordType) $+    HedgehogHspec.require $ property $ do+      geometry <- forAll $ generator genCoordPoint+      endianType <- forAll genEndianType+      f endianType geometry++roundTripWkb :: MonadTest m => Endian.EndianType -> Geospatial.GeospatialGeometry -> m ()+roundTripWkb endianType geometry =+  let+    p = Wkb.parseByteString . Wkb.toByteString endianType+  in+    p geometry === Right geometry++roundTripEwkb :: MonadTest m => Endian.EndianType -> Geospatial.GeospatialGeometry -> m ()+roundTripEwkb endianType geometry =+  let+    p = Ewkb.parseByteString . Ewkb.toByteString endianType (EwkbGeometry.Srid (4326 :: Word.Word32))+  in+    p geometry === Right geometry++-- Generators++upperBoundOfMultiGeometries :: Int+upperBoundOfMultiGeometries = 10++upperBoundOfPoints :: Int+upperBoundOfPoints = 100++coordPointGenerators :: [(Geometry.CoordinateType, Gen Geospatial.GeoPositionWithoutCRS)]+coordPointGenerators =+  [ (Geometry.TwoD, genCoordPointXY)+  , (Geometry.Z, genCoordPointXYZ)+-- We can't roundtrip or express M in the current geometry (hs-geojson)+--  , (Geometry.M, genCoordPointXYZ)+  , (Geometry.ZM, genCoordPointXYZM)+  ]++genGeometryCollection :: Gen Geospatial.GeoPositionWithoutCRS -> Gen Geospatial.GeospatialGeometry+genGeometryCollection genCoordPoint = do+  geoCollection <- Gen.seq (Range.linear 0 upperBoundOfMultiGeometries) (genGeospatialGeometry genCoordPoint)+  return $ Geospatial.Collection geoCollection++genGeospatialGeometry :: Gen Geospatial.GeoPositionWithoutCRS -> Gen Geospatial.GeospatialGeometry+genGeospatialGeometry genCoordPoint = Gen.recursive Gen.choice+  [ genPoint genCoordPoint+  , genLine genCoordPoint+  , genPolygon genCoordPoint+  , genMultiPoint genCoordPoint+  , genMultiLine genCoordPoint+  , genMultiPolygon genCoordPoint+  ]+  [ genGeometryCollection genCoordPoint ]++genMultiPolygon :: Gen Geospatial.GeoPositionWithoutCRS -> Gen Geospatial.GeospatialGeometry+genMultiPolygon genCoordPoint = do+  geoMultiPolygon <- Geospatial.GeoMultiPolygon <$> Gen.seq (Range.linear 0 upperBoundOfMultiGeometries) (genLinearRings genCoordPoint)+  return $ Geospatial.MultiPolygon geoMultiPolygon++genPolygon :: Gen Geospatial.GeoPositionWithoutCRS -> Gen Geospatial.GeospatialGeometry+genPolygon genCoordPoint = do+  geoPolygon <- Geospatial.GeoPolygon <$> genLinearRings genCoordPoint+  return $ Geospatial.Polygon geoPolygon++genMultiLine :: Gen Geospatial.GeoPositionWithoutCRS -> Gen Geospatial.GeospatialGeometry+genMultiLine genCoordPoint = do+  geoMultiLine <- Geospatial.GeoMultiLine <$> Gen.seq (Range.linear 0 upperBoundOfMultiGeometries) (genLineString genCoordPoint)+  return $ Geospatial.MultiLine geoMultiLine++genLine :: Gen Geospatial.GeoPositionWithoutCRS -> Gen Geospatial.GeospatialGeometry+genLine genCoordPoint = do+  geoLine <- Geospatial.GeoLine <$> genLineString genCoordPoint+  return $ Geospatial.Line geoLine++genMultiPoint :: Gen Geospatial.GeoPositionWithoutCRS -> Gen Geospatial.GeospatialGeometry+genMultiPoint genCoordPoint = do+  geoMultiPoint <- Geospatial.GeoMultiPoint <$> genCoordPoints genCoordPoint+  return $ Geospatial.MultiPoint geoMultiPoint++genPoint :: Gen Geospatial.GeoPositionWithoutCRS -> Gen Geospatial.GeospatialGeometry+genPoint genCoordPoint = do+  geoPoint <- Geospatial.GeoPoint <$> genCoordPoint+  return $ Geospatial.Point geoPoint++genLinearRings :: Gen Geospatial.GeoPositionWithoutCRS -> Gen (Sequence.Seq (LinearRing.LinearRing Geospatial.GeoPositionWithoutCRS))+genLinearRings genCoordPoint =+  Gen.seq (Range.linear 0 upperBoundOfMultiGeometries) (genLinearRing genCoordPoint)++genLinearRing :: Gen Geospatial.GeoPositionWithoutCRS -> Gen (LinearRing.LinearRing Geospatial.GeoPositionWithoutCRS)+genLinearRing genCoordPoint =+  LinearRing.makeLinearRing <$> genCoordPoint <*> genCoordPoint <*> genCoordPoint <*> genCoordPoints genCoordPoint++genLineString :: Gen Geospatial.GeoPositionWithoutCRS -> Gen (LineString.LineString Geospatial.GeoPositionWithoutCRS)+genLineString genCoordPoint =+  LineString.makeLineString <$> genCoordPoint <*> genCoordPoint <*> genCoordPoints genCoordPoint++genCoordPoints :: Gen Geospatial.GeoPositionWithoutCRS -> Gen (Sequence.Seq Geospatial.GeoPositionWithoutCRS)+genCoordPoints =+  Gen.seq (Range.linear 0 upperBoundOfPoints)++genCoordPointXY :: Gen Geospatial.GeoPositionWithoutCRS+genCoordPointXY = do+  point <- Geospatial.PointXY <$> genDouble <*> genDouble+  return $ Geospatial.GeoPointXY point++genCoordPointXYZ :: Gen Geospatial.GeoPositionWithoutCRS+genCoordPointXYZ = do+  point <- Geospatial.PointXYZ <$> genDouble <*> genDouble <*> genDouble+  return $ Geospatial.GeoPointXYZ point++genCoordPointXYZM :: Gen Geospatial.GeoPositionWithoutCRS+genCoordPointXYZM = do+  point <- Geospatial.PointXYZM <$> genDouble <*> genDouble <*> genDouble <*> genDouble+  return $ Geospatial.GeoPointXYZM point++genDouble :: Gen Double+genDouble = Gen.double $ Range.linearFrac (-10e6) 10e6++genEndianType :: Gen Endian.EndianType+genEndianType = Gen.choice+  [ Gen.constant Endian.BigEndian+  , Gen.constant Endian.LittleEndian+  ]+++-- Example++point1 :: Geospatial.GeoPositionWithoutCRS+point1 = Geospatial.GeoPointXY (Geospatial.PointXY 1.0 2.0)++point2 :: Geospatial.GeoPositionWithoutCRS+point2 = Geospatial.GeoPointXY (Geospatial.PointXY 3.0 4.0)++lineString1 :: LineString.LineString Geospatial.GeoPositionWithoutCRS+lineString1 = LineString.makeLineString (Geospatial.GeoPointXY (Geospatial.PointXY 1.0 2.0)) (Geospatial.GeoPointXY (Geospatial.PointXY 3.0 4.0)) Sequence.empty++lineString2 :: LineString.LineString Geospatial.GeoPositionWithoutCRS+lineString2 = LineString.makeLineString (Geospatial.GeoPointXY (Geospatial.PointXY 1.5 2.5)) (Geospatial.GeoPointXY (Geospatial.PointXY 3.5 4.5)) Sequence.empty++lineString3 :: LineString.LineString Geospatial.GeoPositionWithoutCRS+lineString3 = LineString.makeLineString (Geospatial.GeoPointXY (Geospatial.PointXY 15 15)) (Geospatial.GeoPointXY (Geospatial.PointXY 20 20)) (Sequence.singleton (Geospatial.GeoPointXY (Geospatial.PointXY 20 20)))++lineString4 :: LineString.LineString Geospatial.GeoPositionWithoutCRS+lineString4 = LineString.makeLineString (Geospatial.GeoPointXY (Geospatial.PointXY 15 15)) (Geospatial.GeoPointXY (Geospatial.PointXY 20 20)) (Sequence.singleton (Geospatial.GeoPointXY (Geospatial.PointXY 25 25)))++linearRing1 :: LinearRing.LinearRing Geospatial.GeoPositionWithoutCRS+linearRing1 = LinearRing.makeLinearRing (Geospatial.GeoPointXY (Geospatial.PointXY 1.0 2.0)) (Geospatial.GeoPointXY (Geospatial.PointXY 3.0 4.0)) (Geospatial.GeoPointXY (Geospatial.PointXY 5.0 6.0)) Sequence.empty++linearRing2 :: LinearRing.LinearRing Geospatial.GeoPositionWithoutCRS+linearRing2 = LinearRing.makeLinearRing (Geospatial.GeoPointXY (Geospatial.PointXY 1.5 2.5)) (Geospatial.GeoPointXY (Geospatial.PointXY 3.5 4.5)) (Geospatial.GeoPointXY (Geospatial.PointXY 5.5 6.5)) Sequence.empty
+ test/Ewkb/GeometrySpec.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE OverloadedStrings #-}++module Ewkb.GeometrySpec where++import qualified Data.Binary.Get             as BinaryGet+import qualified Data.ByteString.Builder     as ByteStringBuilder+import qualified Data.Word                   as Word+import           Test.Hspec                  (Spec, describe, it, shouldBe)++import qualified Database.Esqueleto.Postgis.Ewkb.Geometry as Ewkb+import qualified Database.Esqueleto.Postgis.Wkb.Endian    as Endian+import qualified Database.Esqueleto.Postgis.Wkb.Geometry  as Wkb++spec :: Spec+spec =+  testValidGetEwkbGeometryType++testValidGetEwkbGeometryType :: Spec+testValidGetEwkbGeometryType =+  describe "get extended wkb geometry type for valid data" $+    mapM_ testGetEwkbGeometryType testValues++testGetEwkbGeometryType :: Ewkb.EwkbGeometryType -> Spec+testGetEwkbGeometryType geometryType =+  it ("Parse " ++ show geometryType) $+    mapM_ test [Endian.BigEndian, Endian.LittleEndian]+    where test endianType = roundTrip endianType geometryType `shouldBe` geometryType++roundTrip :: Endian.EndianType -> Ewkb.EwkbGeometryType -> Ewkb.EwkbGeometryType+roundTrip endianType geometryType =+  BinaryGet.runGet (Ewkb.getEwkbGeom endianType) encodedGeometryType+  where encodedGeometryType = ByteStringBuilder.toLazyByteString $ Ewkb.builderEwkbGeom endianType geometryType++testValues :: [Ewkb.EwkbGeometryType]+testValues =+  [ Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Geometry Wkb.TwoD) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Point Wkb.TwoD) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.LineString Wkb.TwoD) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Polygon Wkb.TwoD) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiPoint Wkb.TwoD) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiLineString Wkb.TwoD) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiPolygon Wkb.TwoD) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.GeometryCollection Wkb.TwoD) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Geometry Wkb.Z) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Point Wkb.Z) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.LineString Wkb.Z) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Polygon Wkb.Z) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiPoint Wkb.Z) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiLineString Wkb.Z) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiPolygon Wkb.Z) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.GeometryCollection Wkb.Z) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Geometry Wkb.M) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Point Wkb.M) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.LineString Wkb.M) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Polygon Wkb.M) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiPoint Wkb.M) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiLineString Wkb.M) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiPolygon Wkb.M) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.GeometryCollection Wkb.M) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Geometry Wkb.ZM) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Point Wkb.ZM) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.LineString Wkb.ZM) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Polygon Wkb.ZM) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiPoint Wkb.ZM) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiLineString Wkb.ZM) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiPolygon Wkb.ZM) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.GeometryCollection Wkb.ZM) Ewkb.NoSrid+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Geometry Wkb.TwoD) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Point Wkb.TwoD) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.LineString Wkb.TwoD) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Polygon Wkb.TwoD) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiPoint Wkb.TwoD) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiLineString Wkb.TwoD) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiPolygon Wkb.TwoD) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.GeometryCollection Wkb.TwoD) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Geometry Wkb.Z) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Point Wkb.Z) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.LineString Wkb.Z) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Polygon Wkb.Z) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiPoint Wkb.Z) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiLineString Wkb.Z) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiPolygon Wkb.Z) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.GeometryCollection Wkb.Z) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Geometry Wkb.M) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Point Wkb.M) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.LineString Wkb.M) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Polygon Wkb.M) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiPoint Wkb.M) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiLineString Wkb.M) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiPolygon Wkb.M) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.GeometryCollection Wkb.M) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Geometry Wkb.ZM) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Point Wkb.ZM) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.LineString Wkb.ZM) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.Polygon Wkb.ZM) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiPoint Wkb.ZM) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiLineString Wkb.ZM) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.MultiPolygon Wkb.ZM) (Ewkb.Srid srid)+  , Ewkb.EwkbGeom (Wkb.WkbGeom Wkb.GeometryCollection Wkb.ZM) (Ewkb.Srid srid)+  ]++srid :: Word.Word32+srid = 4326
+ test/Ewkb/LineSpec.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE OverloadedStrings #-}++module Ewkb.LineSpec where++import           Test.Hspec      (Spec, describe)++import qualified Data.SpecHelper as SpecHelper++spec :: Spec+spec = do+  testEwkbLineParsing+  testEwkbMultiLineParsing+++-- Test Wkb Line Parsing++testEwkbLineParsing :: Spec+testEwkbLineParsing =+  describe "Test ewkb line parsing" $+    SpecHelper.testRoundTripEwkbGeometryParsing "line" SpecHelper.genLine+++-- Test Wkb MultiLine Parsing++testEwkbMultiLineParsing :: Spec+testEwkbMultiLineParsing =+  describe "Test ewkb multiline parsing" $+    SpecHelper.testRoundTripEwkbGeometryParsing "multiLine" SpecHelper.genMultiLine
+ test/Ewkb/PointSpec.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE OverloadedStrings #-}++module Ewkb.PointSpec where++import           Test.Hspec      (Spec, describe)++import qualified Data.SpecHelper as SpecHelper++spec :: Spec+spec = do+  testEwkbPointParsing+  testEwkbMultiPointParsing+++-- Test Wkb Point Parsing++testEwkbPointParsing :: Spec+testEwkbPointParsing =+  describe "Test ewkb point parsing" $+    SpecHelper.testRoundTripEwkbGeometryParsing "point" SpecHelper.genPoint+++-- Test Wkb MultiPoint Parsing++testEwkbMultiPointParsing :: Spec+testEwkbMultiPointParsing =+  describe "Test ewkb multipoint parsing" $+    SpecHelper.testRoundTripEwkbGeometryParsing "multipoint" SpecHelper.genMultiPoint
test/Test.hs view
@@ -21,7 +21,6 @@ import Control.Monad.IO.Class import Control.Monad.Logger (MonadLogger (..), runStderrLoggingT) import Control.Monad.Trans.Resource (MonadThrow, ResourceT, runResourceT)-import Data.Geospatial (PointXY (..), PointXYZ (..), PointXYZM (..)) import Data.LineString (LineString, makeLineString) import Data.LinearRing (LinearRing, makeLinearRing) import Data.List.NonEmpty (NonEmpty)@@ -47,7 +46,21 @@ import qualified Hedgehog.Range as Range import Test.Tasty import Test.Tasty.HUnit+import Test.Tasty.Hspec(testSpec) +import qualified Ewkb.GeometrySpec+import qualified Ewkb.LineSpec+import qualified Ewkb.PointSpec++import qualified Wkb.EndianSpec+import qualified Wkb.GeometryCollectionSpec+import qualified Wkb.GeometrySpec+import qualified Wkb.HexParsingSpec+import qualified Wkb.LineSpec+import qualified Wkb.PointSpec+import qualified Wkb.PolygonSpec++ connString :: ConnectionString connString = "host=localhost port=5432 user=test dbname=test password=test" @@ -87,11 +100,30 @@     $ (initializeDB >> act >>= \ret -> transactionUndo >> return ret)  main :: IO ()-main = defaultMain tests+main = do+  -- normally hspec discover does this, but we don't want to use+  -- convuleted preprocessor,+  -- it sometimes doesn't work and it's hard to understand why.+  -- so I prefer to just import everything by hand.+  -- also, go look at the source of hspec discover, it's kinda shocking.+  hspecTrees <- sequence [+    testSpec "Ewkb.GeometrySpec" Ewkb.GeometrySpec.spec,+    testSpec "Ewkb.LineSpec" Ewkb.LineSpec.spec,+    testSpec "Ewkb.PointSpec" Ewkb.PointSpec.spec, -tests :: TestTree-tests = testGroup "Tests" [unitTests]+    testSpec "Wkb.EndianSpec" Wkb.EndianSpec.spec,+    testSpec "Wkb.GeometryCollectionSpec" Wkb.GeometryCollectionSpec.spec,+    testSpec "Wkb.GeometrySpec" Wkb.GeometrySpec.spec,+    testSpec "Wkb.HexParsingSpec" Wkb.HexParsingSpec.spec,+    testSpec "Wkb.LineSpec" Wkb.LineSpec.spec,+    testSpec "Wkb.PointSpec" Wkb.PointSpec.spec,+    testSpec "Wkb.PolygonSpec" Wkb.PolygonSpec.spec +    ]++  defaultMain $+    testGroup "all tests" $ postgisBindingsTests : hspecTrees+ test' :: Gen (PostgisGeometry PointXY) -> TestTree test' gen =   testCase "roundtrip xy geometry" $ do@@ -119,10 +151,10 @@       selectList @(Unityzm) [] []     (entityVal <$> result) @?= [someUnit] -unitTests :: TestTree-unitTests =+postgisBindingsTests :: TestTree+postgisBindingsTests =   testGroup-    "Unit tests"+    "postgis binding tests"     [ testGroup "roundtrip tests xy" $         (test') <$> (genCollection genPointxy : genGeometry genPointxy),       testGroup "roundtrip tests xyz" $
+ test/Wkb/EndianSpec.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE OverloadedStrings #-}++module Wkb.EndianSpec where++import qualified Data.Binary.Get          as BinaryGet+import qualified Data.ByteString.Builder  as ByteStringBuilder+import           Test.Hspec               (Spec, describe, it, shouldBe)++import qualified Database.Esqueleto.Postgis.Wkb.Endian as Endian++spec :: Spec+spec =+  testGetEndian++testGetEndian :: Spec+testGetEndian =+  describe "get endian" $ do+    it "Returns BigEndian for 0" $+      roundTrip Endian.BigEndian `shouldBe` Endian.BigEndian+    it "Returns LittleEndian for 1" $+      roundTrip Endian.LittleEndian `shouldBe` Endian.LittleEndian+    it "Returns fail for other" $+      BinaryGet.runGetOrFail Endian.getEndianType (ByteStringBuilder.toLazyByteString $ ByteStringBuilder.word8 5) `shouldBe` Left ("", 1, "Invalid EndianType")++roundTrip :: Endian.EndianType -> Endian.EndianType+roundTrip endianType =+  BinaryGet.runGet Endian.getEndianType encodedEndianType+  where encodedEndianType = ByteStringBuilder.toLazyByteString $ Endian.builderEndianType endianType
+ test/Wkb/GeometryCollectionSpec.hs view
@@ -0,0 +1,14 @@++{-# LANGUAGE OverloadedStrings #-}++module Wkb.GeometryCollectionSpec where++import           Test.Hspec      (Spec, describe)++import qualified Data.SpecHelper as SpecHelper++spec :: Spec+spec =+  describe "Test wkb geometry collection parsing" $+    SpecHelper.testRoundTripWkbGeometryParsing "geometry collection" SpecHelper.genGeometryCollection+
+ test/Wkb/GeometrySpec.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE OverloadedStrings #-}++module Wkb.GeometrySpec where++import qualified Data.Binary.Get            as BinaryGet+import qualified Data.ByteString.Builder    as ByteStringBuilder+import           Test.Hspec                 (Spec, describe, it, shouldBe)++import qualified Database.Esqueleto.Postgis.Wkb.Endian   as Endian+import qualified Database.Esqueleto.Postgis.Wkb.Geometry as Geometry++spec :: Spec+spec =+  testValidGetGeometryTypeWithCoords++testValidGetGeometryTypeWithCoords :: Spec+testValidGetGeometryTypeWithCoords =+  describe "get geometry type with coords type for valid data" $+    mapM_ testGetGeometryTypeWithCoords geometryTypes++testGetGeometryTypeWithCoords :: Geometry.WkbGeometryType -> Spec+testGetGeometryTypeWithCoords geometryType =+  it ("Parse " ++ show geometryType) $+    mapM_ test [Endian.BigEndian, Endian.LittleEndian]+    where test endianType =+            roundTrip endianType geometryType `shouldBe` geometryType++roundTrip :: Endian.EndianType -> Geometry.WkbGeometryType -> Geometry.WkbGeometryType+roundTrip endianType geometryType =+  BinaryGet.runGet (Geometry.getWkbGeom endianType) encodedGeometryType+  where encodedGeometryType = ByteStringBuilder.toLazyByteString $ Geometry.builderWkbGeom endianType geometryType++geometryTypes :: [Geometry.WkbGeometryType]+geometryTypes =+  [ Geometry.WkbGeom Geometry.Geometry Geometry.TwoD+  , Geometry.WkbGeom Geometry.Point Geometry.TwoD+  , Geometry.WkbGeom Geometry.LineString Geometry.TwoD+  , Geometry.WkbGeom Geometry.Polygon Geometry.TwoD+  , Geometry.WkbGeom Geometry.MultiPoint Geometry.TwoD+  , Geometry.WkbGeom Geometry.MultiLineString Geometry.TwoD+  , Geometry.WkbGeom Geometry.MultiPolygon Geometry.TwoD+  , Geometry.WkbGeom Geometry.GeometryCollection Geometry.TwoD+  , Geometry.WkbGeom Geometry.Geometry Geometry.Z+  , Geometry.WkbGeom Geometry.Point Geometry.Z+  , Geometry.WkbGeom Geometry.LineString Geometry.Z+  , Geometry.WkbGeom Geometry.Polygon Geometry.Z+  , Geometry.WkbGeom Geometry.MultiPoint Geometry.Z+  , Geometry.WkbGeom Geometry.MultiLineString Geometry.Z+  , Geometry.WkbGeom Geometry.MultiPolygon Geometry.Z+  , Geometry.WkbGeom Geometry.GeometryCollection Geometry.Z+  , Geometry.WkbGeom Geometry.Geometry Geometry.M+  , Geometry.WkbGeom Geometry.Point Geometry.M+  , Geometry.WkbGeom Geometry.LineString Geometry.M+  , Geometry.WkbGeom Geometry.Polygon Geometry.M+  , Geometry.WkbGeom Geometry.MultiPoint Geometry.M+  , Geometry.WkbGeom Geometry.MultiLineString Geometry.M+  , Geometry.WkbGeom Geometry.MultiPolygon Geometry.M+  , Geometry.WkbGeom Geometry.GeometryCollection Geometry.M+  , Geometry.WkbGeom Geometry.Geometry Geometry.ZM+  , Geometry.WkbGeom Geometry.Point Geometry.ZM+  , Geometry.WkbGeom Geometry.LineString Geometry.ZM+  , Geometry.WkbGeom Geometry.Polygon Geometry.ZM+  , Geometry.WkbGeom Geometry.MultiPoint Geometry.ZM+  , Geometry.WkbGeom Geometry.MultiLineString Geometry.ZM+  , Geometry.WkbGeom Geometry.MultiPolygon Geometry.ZM+  , Geometry.WkbGeom Geometry.GeometryCollection Geometry.ZM+  ]
+ test/Wkb/HexParsingSpec.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE OverloadedStrings #-}++module Wkb.HexParsingSpec where++import qualified Data.Geospatial as Geospatial+import           Test.Hspec      (Spec, describe, it, shouldBe)++import qualified Database.Esqueleto.Postgis.Hex        as Hex+import qualified Database.Esqueleto.Postgis.Wkb        as Wkb++spec :: Spec+spec =+  testWkbHexParsing++testWkbHexParsing :: Spec+testWkbHexParsing =+  describe "Test hex encoded wkb point" $ do+    it "Parse valid hex wkb" $+      Wkb.parseHexByteString exampleHexPoint `shouldBe` Right expectedPoint+    it "Not parse valid hex but invalid wkb" $+      Wkb.parseHexByteString (Hex.Hex "deadbeef") `shouldBe` Left "Could not parse wkb: Invalid EndianType"+    it "Some valid, some invalid hex" $+      Wkb.parseHexByteString (Hex.Hex "deadfish") `shouldBe` Left "Invalid hex representation: deadfish"+    it "All bad" $+      Wkb.parseHexByteString (Hex.Hex "cowboyx") `shouldBe` Left "Invalid hex representation: cowboyx"++exampleHexPoint :: Hex.Hex+exampleHexPoint = Hex.Hex "0101000000000000000000f03f0000000000000040"++expectedPoint :: Geospatial.GeospatialGeometry+expectedPoint = Geospatial.Point (Geospatial.GeoPoint (Geospatial.GeoPointXY (Geospatial.PointXY 1.0 2.0)))
+ test/Wkb/LineSpec.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE OverloadedStrings #-}++module Wkb.LineSpec where++import           Test.Hspec      (Spec, describe)++import qualified Data.SpecHelper as SpecHelper++spec :: Spec+spec = do+  testWkbLineParsing+  testWkbMultiLineParsing+++-- Test Wkb Line Parsing++testWkbLineParsing :: Spec+testWkbLineParsing =+  describe "Test wkb line parsing" $+    SpecHelper.testRoundTripWkbGeometryParsing "line" SpecHelper.genLine+++-- Test Wkb MultiLine Parsing++testWkbMultiLineParsing :: Spec+testWkbMultiLineParsing =+  describe "Test wkb multiline parsing" $+    SpecHelper.testRoundTripWkbGeometryParsing "multiLine" SpecHelper.genMultiLine
+ test/Wkb/PointSpec.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE OverloadedStrings #-}++module Wkb.PointSpec where++import qualified Data.Binary.Get             as BinaryGet+import qualified Data.ByteString.Builder     as ByteStringBuilder+import qualified Data.Geospatial             as Geospatial+import qualified HaskellWorks.Hspec.Hedgehog as HedgehogHspec+import           Hedgehog+import           Test.Hspec                  (Spec, describe, it)++import qualified Database.Esqueleto.Postgis.Wkb.Geometry  as Geometry+import qualified Database.Esqueleto.Postgis.Wkb.Point     as Point+import qualified Data.SpecHelper             as SpecHelper++spec :: Spec+spec = do+  testCoordPointParsing+  testWkbPointParsing+  testWkbMultiPointParsing+++-- Test Coord Point Parsing++testCoordPointParsing :: Spec+testCoordPointParsing =+  describe "Test coord point parsing" $+    mapM_ testCoordPointParsing' SpecHelper.coordPointGenerators++testCoordPointParsing' :: (Geometry.CoordinateType, Gen Geospatial.GeoPositionWithoutCRS) -> Spec+testCoordPointParsing' (coordType, genCoordPoint) =+  it ("round trips coord point: " ++ show coordType) $ HedgehogHspec.require $ property $ do+    coordPoint <- forAll genCoordPoint+    endianType <- forAll SpecHelper.genEndianType+    roundTrip endianType coordPoint === coordPoint+  where+    roundTrip endianType coordPoint  =+      BinaryGet.runGet (Point.getCoordPoint endianType coordType) (encodedCoordPoint endianType coordPoint)+    encodedCoordPoint endianType coordPoint =+      ByteStringBuilder.toLazyByteString $ Point.builderCoordPoint endianType coordPoint+++-- Test Wkb Point Parsing++testWkbPointParsing :: Spec+testWkbPointParsing =+  describe "Test wkb point parsing" $+    SpecHelper.testRoundTripWkbGeometryParsing "point" SpecHelper.genPoint+++-- Test Wkb MultiPoint Parsing++testWkbMultiPointParsing :: Spec+testWkbMultiPointParsing =+  describe "Test wkb multipoint parsing" $+    SpecHelper.testRoundTripWkbGeometryParsing "multipoint" SpecHelper.genMultiPoint
+ test/Wkb/PolygonSpec.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE OverloadedStrings #-}++module Wkb.PolygonSpec where++import qualified Data.ByteString.Builder as ByteStringBuilder+import qualified Data.SpecHelper as SpecHelper+import qualified Database.Esqueleto.Postgis.Wkb as Wkb+import Test.Hspec (Spec, describe, it, shouldBe)++spec :: Spec+spec = do+  testWkbPolygonParsing+  testWkbMultiPolygonParsing++-- Test Wkb Polygon Parsing++testWkbPolygonParsing :: Spec+testWkbPolygonParsing =+  describe "Test wkb polygon" $ do+    testValidWkbPolyonParsing+    testInvalidWkbPolyonParsing++testValidWkbPolyonParsing :: Spec+testValidWkbPolyonParsing =+  SpecHelper.testRoundTripWkbGeometryParsing "polygon" SpecHelper.genPolygon++testInvalidWkbPolyonParsing :: Spec+testInvalidWkbPolyonParsing =+  it "does not parse bad wkb polygon" $+    Wkb.parseByteString exampleBadWkbPolygon `shouldBe` Left "Could not parse wkb: First and last points of linear ring are different: first=GeoPointXY (PointXY {_xyX = 1.0, _xyY = 2.0}) last=GeoPointXY (PointXY {_xyX = 7.0, _xyY = 8.0})"+  where+    exampleBadWkbPolygon =+      ByteStringBuilder.toLazyByteString $+        ByteStringBuilder.word8 0+          <> ByteStringBuilder.int32BE 3+          <> ByteStringBuilder.int32BE 1+          <> ByteStringBuilder.int32BE 4+          <> ByteStringBuilder.doubleBE 1.0+          <> ByteStringBuilder.doubleBE 2.0+          <> ByteStringBuilder.doubleBE 3.0+          <> ByteStringBuilder.doubleBE 4.0+          <> ByteStringBuilder.doubleBE 5.0+          <> ByteStringBuilder.doubleBE 6.0+          <> ByteStringBuilder.doubleBE 7.0+          <> ByteStringBuilder.doubleBE 8.0++-- Test Wkb MultiPolygon Parsing++testWkbMultiPolygonParsing :: Spec+testWkbMultiPolygonParsing =+  describe "Test wkb multipolygon parsing" $+    SpecHelper.testRoundTripWkbGeometryParsing "multipolygon" SpecHelper.genMultiPolygon