{-# LANGUAGE NoImplicitPrelude #-} ------------------------------------------------------------------- -- | -- Module : Data.LineString -- Copyright : (C) 2014-2018 HS-GeoJSON Project -- License : BSD-style (see the file LICENSE.md) -- Maintainer : Andrew Newman -- -- Refer to the GeoJSON Spec -- -- A LinearString is a List with at least 2 elements -- ------------------------------------------------------------------- module Data.LineString ( -- * Type LineString , ListToLineStringError(..) -- * Functions , fromLineString , fromList , makeLineString , lineStringHead , lineStringLast , lineStringLength ) where import Prelude hiding (foldr) import Control.Applicative (Applicative (..)) import Control.Lens (( # ), (^?)) import Control.Monad (mzero) import Data.Aeson (FromJSON (..), ToJSON (..), Value) import Data.Aeson.Types (Parser, typeMismatch) import Data.Foldable (Foldable (..)) import Data.Functor ((<$>)) import Data.Maybe (fromMaybe) import Data.Traversable (Traversable (..)) import Data.Validation (Validate (..), Validation, _Failure, _Success) -- | -- a LineString has at least 2 elements -- data LineString a = LineString a a [a] deriving (Eq) -- | -- When converting a List to a LineString, here is a list of things that can go wrong: -- -- * The list was empty -- * The list only had one element -- data ListToLineStringError = ListEmpty | SingletonList deriving (Eq) -- functions -- | -- returns the element at the head of the string -- lineStringHead :: LineString a -> a lineStringHead (LineString x _ _) = x -- | -- returns the last element in the string -- lineStringLast :: LineString a -> a lineStringLast (LineString _ x xs) = fromMaybe x (safeLast xs) -- | -- returns the number of elements in the list, including the replicated element at the end of the list. -- lineStringLength :: LineString a -> Int lineStringLength (LineString _ _ xs) = 2 + length xs -- | -- This function converts it into a list and appends the given element to the end. -- fromLineString :: LineString a -> [a] fromLineString (LineString x y zs) = x : y : zs -- | -- creates a LineString out of a list of elements, -- if there are enough elements (needs at least 2) elements -- fromList :: (Validate v) => [a] -> v ListToLineStringError (LineString a) fromList [] = _Failure # ListEmpty fromList [_] = _Failure # SingletonList fromList (x:y:zs) = _Success # LineString x y zs -- | -- Creates a LineString -- @makeLineString x y zs@ creates a `LineString` homomorphic to the list @[x, y] ++ zs@ -- makeLineString :: a -- ^ The first element -> a -- ^ The second element -> [a] -- ^ The rest of the optional elements -> LineString a makeLineString = LineString -- instances instance Show ListToLineStringError where show ListEmpty = "List Empty" show SingletonList = "Singleton List" instance (Show a) => Show (LineString a) where show = show . fromLineString instance Functor LineString where fmap f (LineString x y zs) = LineString (f x) (f y) (fmap f zs) -- | This instance of Foldable will run through the entire ring, closing the -- loop by also passing the initial element in again at the end. -- instance Foldable LineString where -- foldr :: (a -> b -> b) -> b -> LineString a -> b foldr f u (LineString x y zs) = f x (f y (foldr f u zs)) instance Traversable LineString where -- sequenceA :: (Traversable t, Applicative f) => t (f a) -> f (t a) sequenceA (LineString fx fy fzs) = LineString <$> fx <*> fy <*> sequenceA fzs instance (ToJSON a) => ToJSON (LineString a) where -- toJSON :: a -> Value toJSON = toJSON . fromLineString instance (FromJSON a, Show a) => FromJSON (LineString a) where -- parseJSON :: Value -> Parser a parseJSON v = do xs <- parseJSON v let vxs = fromListValidated xs maybe (parseError v (vxs ^? _Failure)) return (vxs ^? _Success) -- helpers fromListValidated :: [a] -> Validation ListToLineStringError (LineString a) fromListValidated = fromList parseError :: Value -> Maybe ListToLineStringError -> Parser b parseError v = maybe mzero (\e -> typeMismatch (show e) v) safeLast :: [a] -> Maybe a safeLast [] = Nothing safeLast [x] = Just x safeLast (_:xs) = safeLast xs