module Data.Geo.Jord.Angle
(
Angle
, decimalDegrees
, dms
, dmsE
, dmsF
, radians
, arcLength
, central
, isNegative
, isWithin
, negate'
, normalise
, asin'
, atan2'
, cos'
, sin'
, getDegrees
, getMinutes
, getSeconds
, getMilliseconds
, toDecimalDegrees
, toRadians
, angle
, readAngle
, readAngleE
, readAngleF
) where
import Control.Applicative
import Control.Monad.Fail
import Data.Fixed
import Data.Geo.Jord.Length
import Data.Geo.Jord.Parser
import Data.Geo.Jord.Quantity
import Data.Maybe
import Prelude hiding (fail, length)
import Text.ParserCombinators.ReadP
import Text.Printf
import Text.Read hiding (pfail)
newtype Angle = Angle
{ milliseconds :: Int
} deriving (Eq)
instance Read Angle where
readsPrec _ = readP_to_S angle
instance Show Angle where
show a =
s ++
show d ++
"°" ++
show (getMinutes a) ++
"'" ++ show (getSeconds a) ++ "." ++ printf "%03d" (getMilliseconds a) ++ "\""
where
d = getDegrees a
s =
if d == 0 && milliseconds a < 0
then "-"
else ""
instance Quantity Angle where
add (Angle millis1) (Angle millis2) = Angle (millis1 + millis2)
sub (Angle millis1) (Angle millis2) = Angle (millis1 - millis2)
zero = Angle 0
decimalDegrees :: Double -> Angle
decimalDegrees dec = Angle (round (dec * 3600000.0))
dms :: Int -> Int -> Int -> Int -> Angle
dms degs mins secs millis =
fromMaybe
(error
("Invalid minutes=" ++
show mins ++ " or seconds=" ++ show secs ++ " or milliseconds=" ++ show millis))
(dmsF degs mins secs millis)
dmsE :: Int -> Int -> Int -> Int -> Either String Angle
dmsE degs mins secs millis
| mins < 0 || mins > 59 = Left ("Invalid minutes: " ++ show mins)
| secs < 0 || secs >= 60 = Left ("Invalid seconds: " ++ show secs)
| millis < 0 || millis >= 1000 = Left ("Invalid milliseconds: " ++ show millis)
| otherwise = Right (decimalDegrees ms)
where
ms =
signed
(fromIntegral (abs degs) + (fromIntegral mins / 60.0 :: Double) +
(fromIntegral secs / 3600.0 :: Double) +
(fromIntegral millis / 3600000.0 :: Double))
(signum degs)
dmsF :: (MonadFail m) => Int -> Int -> Int -> Int -> m Angle
dmsF degs mins secs millis =
case e of
Left err -> fail err
Right a -> return a
where
e = dmsE degs mins secs millis
radians :: Double -> Angle
radians r = decimalDegrees (r / pi * 180.0)
arcLength :: Angle -> Length -> Length
arcLength a r = metres (toMetres r * toRadians a)
central :: Length -> Length -> Angle
central s r = radians (toMetres s / toMetres r)
negate' :: Angle -> Angle
negate' (Angle millis) = Angle (-millis)
normalise :: Angle -> Angle -> Angle
normalise a n = decimalDegrees dec
where
dec = mod' (toDecimalDegrees a + toDecimalDegrees n) 360.0
isNegative :: Angle -> Bool
isNegative (Angle millis) = millis < 0
isWithin :: Angle -> Angle -> Angle -> Bool
isWithin (Angle millis) (Angle low) (Angle high) = millis >= low && millis <= high
atan2' :: Double -> Double -> Angle
atan2' y x = radians (atan2 y x)
asin' :: Double -> Angle
asin' a = radians (asin a)
cos' :: Angle -> Double
cos' a = cos (toRadians a)
sin' :: Angle -> Double
sin' a = sin (toRadians a)
toRadians :: Angle -> Double
toRadians a = toDecimalDegrees a * pi / 180.0
toDecimalDegrees :: Angle -> Double
toDecimalDegrees (Angle millis) = fromIntegral millis / 3600000.0
getDegrees :: Angle -> Int
getDegrees a = signed (field a 3600000.0 360.0) (signum (milliseconds a))
getMinutes :: Angle -> Int
getMinutes a = field a 60000.0 60.0
getSeconds :: Angle -> Int
getSeconds a = field a 1000.0 60.0
getMilliseconds :: Angle -> Int
getMilliseconds (Angle millis) = mod (abs millis) 1000
field :: Angle -> Double -> Double -> Int
field (Angle millis) divisor modulo =
truncate (mod' (fromIntegral (abs millis) / divisor) modulo) :: Int
signed :: (Num a, Num b, Ord b) => a -> b -> a
signed n s
| s < 0 = -n
| otherwise = n
angle :: ReadP Angle
angle = degsMinsSecs <|> decimal
readAngle :: String -> Angle
readAngle s = read s :: Angle
readAngleE :: String -> Either String Angle
readAngleE s =
case readMaybe s of
Nothing -> Left ("couldn't read angle " ++ s)
Just a -> Right a
readAngleF :: (MonadFail m) => String -> m Angle
readAngleF s =
let p = readAngleE s
in case p of
Left e -> fail e
Right l -> return l
degsMinsSecs :: ReadP Angle
degsMinsSecs = do
d' <- fmap fromIntegral integer
degSymbol
(m', s', ms') <- option (0, 0, 0) (minsSecs <|> minsOnly)
dmsF d' m' s' ms'
minsSecs :: ReadP (Int, Int, Int)
minsSecs = do
m' <- natural
minSymbol
s' <- natural
ms' <- option 0 (char '.' >> natural)
secSymbol
return (m', s', ms')
minsOnly :: ReadP (Int, Int, Int)
minsOnly = do
m' <- natural
minSymbol
return (m', 0, 0)
decimal :: ReadP Angle
decimal = do
d <- double
degSymbol
return (decimalDegrees d)
degSymbol :: ReadP ()
degSymbol = do
_ <- char '°' <|> char 'd'
return ()
minSymbol :: ReadP ()
minSymbol = do
_ <- char '\'' <|> char '′' <|> char 'm'
return ()
secSymbol :: ReadP ()
secSymbol = do
_ <- string "\"" <|> string "''" <|> string "″" <|> string "s"
return ()