module Geodetics.LatLongParser (
degreesMinutesSeconds,
degreesMinutesSecondsUnits,
degreesDecimalMinutes,
degreesDecimalMinutesUnits,
dms7,
angle,
latitudeNS,
longitudeEW,
signedLatLong,
latLong
) where
import Control.Monad
import Data.Char
import Text.ParserCombinators.ReadP as P
natural :: ReadP Integer
natural = read <$> munch1 isDigit
decimal :: ReadP Double
decimal = do
str1 <- munch1 isDigit
option (read str1) $ do
str2 <- char '.' *> munch1 isDigit
return $ read $ str1 ++ '.' : str2
signChar :: (Num a) =>
Char
-> Char
-> ReadP a
signChar pos neg = do
c <- char pos +++ char neg
return $ if c == pos then 1 else (-1)
signedDecimal :: ReadP Double
signedDecimal = (*) <$> option 1 (signChar '+' '-') <*> decimal
degreesMinutesSeconds :: ReadP Double
degreesMinutesSeconds = do
d <- fromIntegral <$> natural
guard $ d <= 360
skipSpaces
ms <- option 0 $ do
m <- fromIntegral <$> natural
guard $ m < 60
skipSpaces
s <- option 0 decimal
guard $ s < 60
return $ m / 60 + s / 3600
return $ d + ms
degreesMinutesSecondsUnits :: ReadP Double
degreesMinutesSecondsUnits = do
(s, a) <- gather $ do
d <- fromIntegral <$> option 0 (natural <* char '°')
guard $ d <= 360
skipSpaces
m <- fromIntegral <$> option 0 (natural <* char '\'')
guard $ m < 60
skipSpaces
s <- option 0 (decimal <* char '"')
guard $ s < 60
return $ d + m / 60 + s / 3600
guard $ not $ null s
return a
degreesDecimalMinutes :: ReadP Double
degreesDecimalMinutes = do
d <- fromIntegral <$> natural
skipSpaces
guard $ d <= 360
m <- option 0 decimal
guard $ m < 60
return $ d + m/60
degreesDecimalMinutesUnits :: ReadP Double
degreesDecimalMinutesUnits = do
(s, a) <- gather $ do
d <- fromIntegral <$> option 0 (natural <* char '°')
guard $ d <= 360
m <- option 0 (decimal <* char '\'')
guard $ m < 60
return $ d + m / 60
guard $ not $ null s
return a
dms7 :: ReadP Double
dms7 = do
str <- munch1 isDigit
decs <- option "0" (char '.' *> munch1 isDigit)
let c = length str
(ds, rs) = splitAt (c-4) str
(ms,ss) = splitAt 2 rs
d = read ds
m = read ms
s = read $ ss ++ '.' : decs
guard $ c >= 5 && c <= 7
guard $ m < 60
guard $ s < 60
return $ d + m / 60 + s / 3600
angle :: ReadP Double
angle = choice [
decimal,
degreesMinutesSeconds,
degreesMinutesSecondsUnits,
degreesDecimalMinutes,
degreesDecimalMinutesUnits,
dms7
]
latitudeNS :: ReadP Double
latitudeNS = do
ul <- angle
guard $ ul <= 90
skipSpaces
sgn <- signChar 'N' 'S'
return $ sgn * ul
longitudeEW :: ReadP Double
longitudeEW = do
ul <- angle
guard $ ul <= 180
skipSpaces
sgn <- signChar 'E' 'W'
return $ sgn * ul
signedLatLong :: ReadP (Double, Double)
signedLatLong = do
lat <- signedDecimal
guard $ lat >= (-90)
guard $ lat <= 90
skipSpaces
P.optional $ char ',' >> skipSpaces
long <- signedDecimal
guard $ long >= (-180)
guard $ long < 360
return (lat, if long > 180 then long-180 else long)
latLong :: ReadP (Double, Double)
latLong = latLong1 +++ longLat +++ signedLatLong
where
latLong1 = do
lat <- latitudeNS
skipSpaces
P.optional $ char ',' >> skipSpaces
long <- longitudeEW
return (lat, long)
longLat = do
long <- longitudeEW
skipSpaces
P.optional $ char ',' >> skipSpaces
lat <- latitudeNS
return (lat, long)