module Data.Internal.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 Data.Monoid ((<>)) import qualified Data.Sequence as Sequence import qualified Data.Internal.Wkb.Endian as Endian import qualified Data.Internal.Wkb.Geometry as Geometry import qualified Data.Internal.Wkb.GeometryCollection as GeometryCollection import qualified Data.Internal.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