module Data.Geo.Jord.Angle
(
Angle
, decimalDegrees
, dms
, dmsE
, dmsF
, arcLength
, central
, isNegative
, isWithin
, negate'
, normalise
, asin'
, atan2'
, cos'
, sin'
, getDegrees
, getMinutes
, getSeconds
, getMilliseconds
, toDecimalDegrees
, angle
, readAngle
, readAngleE
, readAngleF
) where
import Control.Applicative
import Control.Monad.Fail
import Data.Fixed
import Data.Geo.Jord.Length
import Data.Geo.Jord.Parse
import Data.Geo.Jord.Quantity
import Data.Maybe
import Prelude hiding (fail, length)
import Text.ParserCombinators.ReadP
import Text.Printf
import Text.Read hiding (get, look, 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
arcLength :: Angle -> Length -> Length
arcLength a r = metres (toMetres r * toRadians a)
central :: Length -> Length -> Angle
central s r = fromRadians (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 = fromRadians (atan2 y x)
asin' :: Double -> Angle
asin' a = fromRadians (asin a)
cos' :: Angle -> Double
cos' a = cos (toRadians a)
sin' :: Angle -> Double
sin' a = sin (toRadians a)
fromRadians :: Double -> Angle
fromRadians r = decimalDegrees (r / pi * 180.0)
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 ()