packages feed

postgresql-types-0.1: src/library/PostgresqlTypes/Lseg.hs

module PostgresqlTypes.Lseg
  ( Lseg (..),

    -- * Accessors
    toX1,
    toY1,
    toX2,
    toY2,

    -- * Constructors
    fromEndpoints,
  )
where

import qualified Data.Attoparsec.Text as Attoparsec
import GHC.Float (castDoubleToWord64, castWord64ToDouble)
import PostgresqlTypes.Algebra
import PostgresqlTypes.Prelude
import PostgresqlTypes.Via
import qualified PtrPeeker
import qualified PtrPoker.Write as Write
import qualified TextBuilder

-- | PostgreSQL @lseg@ type. Line segment in 2D plane.
--
-- The line segment is defined by two endpoints, each with (@x@,@y@) coordinates.
-- Stored as four @64@-bit floating point numbers: (@x1@, @y1@, @x2@, @y2@).
--
-- [PostgreSQL docs](https://www.postgresql.org/docs/18/datatype-geometric.html#DATATYPE-LSEG).
data Lseg
  = Lseg
      -- | X coordinate of first endpoint
      Double
      -- | Y coordinate of first endpoint
      Double
      -- | X coordinate of second endpoint
      Double
      -- | Y coordinate of second endpoint
      Double
  deriving stock (Eq, Ord)
  deriving (Show, Read, IsString) via (ViaIsScalar Lseg)

instance Arbitrary Lseg where
  arbitrary = Lseg <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
  shrink (Lseg x1 y1 x2 y2) =
    [Lseg x1' y1' x2' y2' | (x1', y1', x2', y2') <- shrink (x1, y1, x2, y2)]

instance Hashable Lseg where
  hashWithSalt salt (Lseg x1 y1 x2 y2) =
    salt
      `hashWithSalt` castDoubleToWord64 x1
      `hashWithSalt` castDoubleToWord64 y1
      `hashWithSalt` castDoubleToWord64 x2
      `hashWithSalt` castDoubleToWord64 y2

instance IsScalar Lseg where
  schemaName = Tagged Nothing
  typeName = Tagged "lseg"
  baseOid = Tagged (Just 601)
  arrayOid = Tagged (Just 1018)
  typeParams = Tagged []
  binaryEncoder (Lseg x1 y1 x2 y2) =
    mconcat
      [ Write.bWord64 (castDoubleToWord64 x1),
        Write.bWord64 (castDoubleToWord64 y1),
        Write.bWord64 (castDoubleToWord64 x2),
        Write.bWord64 (castDoubleToWord64 y2)
      ]
  binaryDecoder = do
    x1 <- PtrPeeker.fixed (castWord64ToDouble <$> PtrPeeker.beUnsignedInt8)
    y1 <- PtrPeeker.fixed (castWord64ToDouble <$> PtrPeeker.beUnsignedInt8)
    x2 <- PtrPeeker.fixed (castWord64ToDouble <$> PtrPeeker.beUnsignedInt8)
    y2 <- PtrPeeker.fixed (castWord64ToDouble <$> PtrPeeker.beUnsignedInt8)
    pure (Right (Lseg x1 y1 x2 y2))
  textualEncoder (Lseg x1 y1 x2 y2) =
    "[("
      <> TextBuilder.string (printf "%g" x1)
      <> ","
      <> TextBuilder.string (printf "%g" y1)
      <> "),"
      <> "("
      <> TextBuilder.string (printf "%g" x2)
      <> ","
      <> TextBuilder.string (printf "%g" y2)
      <> ")]"
  textualDecoder = do
    _ <- Attoparsec.char '['
    _ <- Attoparsec.char '('
    x1 <- Attoparsec.double
    _ <- Attoparsec.char ','
    y1 <- Attoparsec.double
    _ <- Attoparsec.char ')'
    _ <- Attoparsec.char ','
    _ <- Attoparsec.char '('
    x2 <- Attoparsec.double
    _ <- Attoparsec.char ','
    y2 <- Attoparsec.double
    _ <- Attoparsec.char ')'
    _ <- Attoparsec.char ']'
    pure (Lseg x1 y1 x2 y2)

-- * Accessors

-- | Extract the X coordinate of the first endpoint.
toX1 :: Lseg -> Double
toX1 (Lseg x1 _ _ _) = x1

-- | Extract the Y coordinate of the first endpoint.
toY1 :: Lseg -> Double
toY1 (Lseg _ y1 _ _) = y1

-- | Extract the X coordinate of the second endpoint.
toX2 :: Lseg -> Double
toX2 (Lseg _ _ x2 _) = x2

-- | Extract the Y coordinate of the second endpoint.
toY2 :: Lseg -> Double
toY2 (Lseg _ _ _ y2) = y2

-- * Constructors

-- | Construct a PostgreSQL 'Lseg' from endpoint coordinates.
fromEndpoints :: Double -> Double -> Double -> Double -> Lseg
fromEndpoints x1 y1 x2 y2 = Lseg x1 y1 x2 y2