-- |

-- Module:      Data.Geo.Jord.Geodetic

-- Copyright:   (c) 2020 Cedric Liegeois

-- License:     BSD3

-- Maintainer:  Cedric Liegeois <ofmooseandmen@yahoo.fr>

-- Stability:   experimental

-- Portability: portable

--

-- Geodetic coordinates of points in specified models (e.g. WGS84) and conversion functions between

-- /n/-vectors and latitude/longitude.

--

-- All functions are implemented using the vector-based approached described in

-- <http://www.navlab.net/Publications/A_Nonsingular_Horizontal_Point_Representation.pdf Gade, K. (2010). A Non-singular Horizontal Position Representation>

--

-- See <http://clynchg3c.com/Technote/geodesy/coorddef.pdf Earth Coordinates>

--

-- In order to use this module you should start with the following imports:

--

-- @

-- import qualified Data.Geo.Jord.Geodetic as Geodetic

-- import qualified Data.Geo.Jord.Length as Length

-- import Data.Geo.Jord.Models

-- @

module Data.Geo.Jord.Geodetic
    (
    -- * positions types

      HorizontalPosition
    , Position
    , HasCoordinates(..)
    , height
    , model
    , model'
    -- * Smart constructors

    , latLongPos
    , latLongPos'
    , latLongHeightPos
    , latLongHeightPos'
    , wgs84Pos
    , wgs84Pos'
    , s84Pos
    , s84Pos'
    , nvectorPos
    , nvectorPos'
    , nvectorHeightPos
    , nvectorHeightPos'
    , atHeight
    , atSurface
    -- * Read/Show positions

    , readHorizontalPosition
    , horizontalPosition
    , readPosition
    , position
    -- * /n/-vector conversions

    , nvectorFromLatLong
    , nvectorToLatLong
    -- * Misc.

    , antipode
    , antipode'
    , northPole
    , southPole
    ) where

import Prelude hiding (read)
import Text.ParserCombinators.ReadP (ReadP, option, readP_to_S, skipSpaces)

import Data.Geo.Jord.Angle (Angle)
import qualified Data.Geo.Jord.Angle as Angle
import qualified Data.Geo.Jord.LatLong as LL (isValidLatLong, isValidLong, latLongDms, showLatLong)
import Data.Geo.Jord.Length (Length)
import qualified Data.Geo.Jord.Length as Length (length, zero)
import qualified Data.Geo.Jord.Math3d as Math3d (V3, scale, v3x, v3y, v3z, vec3)
import Data.Geo.Jord.Model
import Data.Geo.Jord.Models (S84(..), WGS84(..))

-- | Horizontal coordinates: geodetic latitude, longitude & /n/-vector.

data HCoords =
    HCoords Angle Angle !Math3d.V3

-- | Geodetic coordinates (geodetic latitude, longitude as 'Angle's) of an horizontal position

-- in a specified 'Model'.

--

-- The coordinates are also given as a /n/-vector: the normal vector to the surface.

-- /n/-vector orientation:

--     * z-axis points to the North Pole along the body's rotation axis,

--     * x-axis points towards the point where latitude = longitude = 0

--

-- Note: at the poles all longitudes are equal, therefore a position with a latitude of 90° or -90° will have

-- its longitude forcibly set to 0°.

--

-- The "show" instance gives position in degrees, minutes, seconds, milliseconds ('Angle' "show" instance), and the

-- model ('Model' "show" instance).

--

-- The "eq" instance returns True if and only if, both positions have the same latitude, longitude and model.

-- Note: two positions in different models may represent the same location but are not considered equal.

data HorizontalPosition a =
    HorizontalPosition HCoords a

-- | model of given 'HorizontalPosition' (e.g. WGS84).

model :: (Model a) => HorizontalPosition a -> a
model :: HorizontalPosition a -> a
model (HorizontalPosition HCoords
_ a
m) = a
m

-- | Geodetic coordinates (geodetic latitude, longitude as 'Angle's and height as 'Length') of a position

-- in a specified model.

--

-- The "show" instance gives position in degrees, minutes, seconds, milliseconds (HorizontalPosition "show" instance),

-- height ('Length' "show" instance) and the model ('Model' "show" instance).

--

-- The "eq" instance returns True if and only if, both positions have the same horizontal coordinates and height.

--

-- see 'HorizontalPosition'.

data Position a =
    Position HCoords Length a

-- | height of given 'Position' above the surface of the celestial body.

height :: (Model a) => Position a -> Length
height :: Position a -> Length
height (Position HCoords
_ Length
h a
_) = Length
h

-- | model of given 'Position' (e.g. WGS84).

model' :: (Model a) => Position a -> a
model' :: Position a -> a
model' (Position HCoords
_ Length
_ a
m) = a
m

-- | class for data that provide coordinates.

class HasCoordinates a where
    latitude :: a -> Angle -- ^ geodetic latitude

    decimalLatitude :: a -> Double -- ^ geodetic latitude in decimal degrees

    decimalLatitude = Angle -> Double
Angle.toDecimalDegrees (Angle -> Double) -> (a -> Angle) -> a -> Double
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> Angle
forall a. HasCoordinates a => a -> Angle
latitude
    longitude :: a -> Angle -- ^ longitude

    decimalLongitude :: a -> Double -- ^ longitude  in decimal degrees

    decimalLongitude = Angle -> Double
Angle.toDecimalDegrees (Angle -> Double) -> (a -> Angle) -> a -> Double
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> Angle
forall a. HasCoordinates a => a -> Angle
longitude
    nvector :: a -> Math3d.V3 -- ^ /n/-vector; normal vector to the surface of a celestial body.


instance HasCoordinates (HorizontalPosition a) where
    latitude :: HorizontalPosition a -> Angle
latitude (HorizontalPosition (HCoords Angle
lat Angle
_ V3
_) a
_) = Angle
lat
    longitude :: HorizontalPosition a -> Angle
longitude (HorizontalPosition (HCoords Angle
_ Angle
lon V3
_) a
_) = Angle
lon
    nvector :: HorizontalPosition a -> V3
nvector (HorizontalPosition (HCoords Angle
_ Angle
_ V3
nv) a
_) = V3
nv

instance (Model a) => Show (HorizontalPosition a) where
    show :: HorizontalPosition a -> String
show HorizontalPosition a
p = (Angle, Angle) -> String
LL.showLatLong (HorizontalPosition a -> Angle
forall a. HasCoordinates a => a -> Angle
latitude HorizontalPosition a
p, HorizontalPosition a -> Angle
forall a. HasCoordinates a => a -> Angle
longitude HorizontalPosition a
p) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" (" String -> ShowS
forall a. [a] -> [a] -> [a]
++ (a -> String
forall a. Show a => a -> String
show (a -> String)
-> (HorizontalPosition a -> a) -> HorizontalPosition a -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HorizontalPosition a -> a
forall a. Model a => HorizontalPosition a -> a
model (HorizontalPosition a -> String) -> HorizontalPosition a -> String
forall a b. (a -> b) -> a -> b
$ HorizontalPosition a
p) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")"

-- model equality is ensured by @a@

instance (Model a) => Eq (HorizontalPosition a) where
    HorizontalPosition a
p1 == :: HorizontalPosition a -> HorizontalPosition a -> Bool
== HorizontalPosition a
p2 = HorizontalPosition a -> Angle
forall a. HasCoordinates a => a -> Angle
latitude HorizontalPosition a
p1 Angle -> Angle -> Bool
forall a. Eq a => a -> a -> Bool
== HorizontalPosition a -> Angle
forall a. HasCoordinates a => a -> Angle
latitude HorizontalPosition a
p2 Bool -> Bool -> Bool
&& HorizontalPosition a -> Angle
forall a. HasCoordinates a => a -> Angle
longitude HorizontalPosition a
p1 Angle -> Angle -> Bool
forall a. Eq a => a -> a -> Bool
== HorizontalPosition a -> Angle
forall a. HasCoordinates a => a -> Angle
longitude HorizontalPosition a
p2

instance HasCoordinates (Position a) where
    latitude :: Position a -> Angle
latitude (Position (HCoords Angle
lat Angle
_ V3
_) Length
_ a
_) = Angle
lat
    longitude :: Position a -> Angle
longitude (Position (HCoords Angle
_ Angle
lon V3
_) Length
_ a
_) = Angle
lon
    nvector :: Position a -> V3
nvector (Position (HCoords Angle
_ Angle
_ V3
nv) Length
_ a
_) = V3
nv

instance (Model a) => Show (Position a) where
    show :: Position a -> String
show Position a
p =
        (Angle, Angle) -> String
LL.showLatLong (Position a -> Angle
forall a. HasCoordinates a => a -> Angle
latitude Position a
p, Position a -> Angle
forall a. HasCoordinates a => a -> Angle
longitude Position a
p) String -> ShowS
forall a. [a] -> [a] -> [a]
++
        String
" " String -> ShowS
forall a. [a] -> [a] -> [a]
++ (Length -> String
forall a. Show a => a -> String
show (Length -> String)
-> (Position a -> Length) -> Position a -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Position a -> Length
forall a. Model a => Position a -> Length
height (Position a -> String) -> Position a -> String
forall a b. (a -> b) -> a -> b
$ Position a
p) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" (" String -> ShowS
forall a. [a] -> [a] -> [a]
++ (a -> String
forall a. Show a => a -> String
show (a -> String) -> (Position a -> a) -> Position a -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Position a -> a
forall a. Model a => Position a -> a
model' (Position a -> String) -> Position a -> String
forall a b. (a -> b) -> a -> b
$ Position a
p) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
")"

-- model equality is ensured by @a@

instance (Model a) => Eq (Position a) where
    Position a
p1 == :: Position a -> Position a -> Bool
== Position a
p2 = Position a -> Angle
forall a. HasCoordinates a => a -> Angle
latitude Position a
p1 Angle -> Angle -> Bool
forall a. Eq a => a -> a -> Bool
== Position a -> Angle
forall a. HasCoordinates a => a -> Angle
latitude Position a
p2 Bool -> Bool -> Bool
&& Position a -> Angle
forall a. HasCoordinates a => a -> Angle
longitude Position a
p1 Angle -> Angle -> Bool
forall a. Eq a => a -> a -> Bool
== Position a -> Angle
forall a. HasCoordinates a => a -> Angle
longitude Position a
p2 Bool -> Bool -> Bool
&& Position a -> Length
forall a. Model a => Position a -> Length
height Position a
p1 Length -> Length -> Bool
forall a. Eq a => a -> a -> Bool
== Position a -> Length
forall a. Model a => Position a -> Length
height Position a
p2

-- | 'HorizontalPosition' from given geodetic latitude & longitude in __decimal degrees__ in the given model.

--

-- Latitude & longitude values are first converted to 'Angle' to ensure a consistent resolution with the rest of the

-- API, then wrapped to their respective range.

latLongPos :: (Model a) => Double -> Double -> a -> HorizontalPosition a
latLongPos :: Double -> Double -> a -> HorizontalPosition a
latLongPos Double
lat Double
lon = Angle -> Angle -> a -> HorizontalPosition a
forall a. Model a => Angle -> Angle -> a -> HorizontalPosition a
latLongPos' (Double -> Angle
Angle.decimalDegrees Double
lat) (Double -> Angle
Angle.decimalDegrees Double
lon)

-- | 'HorizontalPosition' from given geodetic latitude & longitude in the given model.

--

-- Latitude & longitude values are wrapped to their respective range.

latLongPos' :: (Model a) => Angle -> Angle -> a -> HorizontalPosition a
latLongPos' :: Angle -> Angle -> a -> HorizontalPosition a
latLongPos' Angle
lat Angle
lon a
m = HCoords -> a -> HorizontalPosition a
forall a. HCoords -> a -> HorizontalPosition a
HorizontalPosition (Angle -> Angle -> V3 -> HCoords
HCoords Angle
wlat Angle
wlon V3
nv) a
m
  where
    lon' :: Angle
lon' = Angle -> Angle -> Angle
checkPole Angle
lat Angle
lon
    nv :: V3
nv = (Angle, Angle) -> V3
nvectorFromLatLong (Angle
lat, Angle
lon')
    (Angle
wlat, Angle
wlon) = Angle -> Angle -> V3 -> a -> (Angle, Angle)
forall a. Model a => Angle -> Angle -> V3 -> a -> (Angle, Angle)
wrap Angle
lat Angle
lon' V3
nv a
m

-- | 'Position' from given geodetic latitude & longitude in __decimal degrees__ and height in the given model

--

-- Latitude & longitude values are first converted to 'Angle' to ensure a consistent resolution with the rest of the

-- API, then wrapped to their respective range.

latLongHeightPos :: (Model a) => Double -> Double -> Length -> a -> Position a
latLongHeightPos :: Double -> Double -> Length -> a -> Position a
latLongHeightPos Double
lat Double
lon = Angle -> Angle -> Length -> a -> Position a
forall a. Model a => Angle -> Angle -> Length -> a -> Position a
latLongHeightPos' (Double -> Angle
Angle.decimalDegrees Double
lat) (Double -> Angle
Angle.decimalDegrees Double
lon)

-- | 'Position' from given geodetic latitude & longitude and height in the given model.

-- Latitude & longitude values are wrapped to their respective range.

latLongHeightPos' :: (Model a) => Angle -> Angle -> Length -> a -> Position a
latLongHeightPos' :: Angle -> Angle -> Length -> a -> Position a
latLongHeightPos' Angle
lat Angle
lon Length
h a
m = HorizontalPosition a -> Length -> Position a
forall a. Model a => HorizontalPosition a -> Length -> Position a
atHeight (Angle -> Angle -> a -> HorizontalPosition a
forall a. Model a => Angle -> Angle -> a -> HorizontalPosition a
latLongPos' Angle
lat Angle
lon a
m) Length
h

-- | 'HorizontalPosition' from given geodetic latitude & longitude in __decimal degrees__ in the WGS84 datum.

--

-- Latitude & longitude values are first converted to 'Angle' to ensure a consistent resolution with the rest of the

-- API, then wrapped to their respective range.

--

-- This is equivalent to:

--

-- > Geodetic.latLongPos lat lon WGS84

wgs84Pos :: Double -> Double -> HorizontalPosition WGS84
wgs84Pos :: Double -> Double -> HorizontalPosition WGS84
wgs84Pos Double
lat Double
lon = Double -> Double -> WGS84 -> HorizontalPosition WGS84
forall a. Model a => Double -> Double -> a -> HorizontalPosition a
latLongPos Double
lat Double
lon WGS84
WGS84

-- | 'HorizontalPosition' from given geodetic latitude & longitude and height in the WGS84 datum.

--

-- Latitude & longitude values are wrapped to their respective range.

--

-- This is equivalent to:

--

-- > Geodetic.latLongPos' lat lon WGS84

wgs84Pos' :: Angle -> Angle -> HorizontalPosition WGS84
wgs84Pos' :: Angle -> Angle -> HorizontalPosition WGS84
wgs84Pos' Angle
lat Angle
lon = Angle -> Angle -> WGS84 -> HorizontalPosition WGS84
forall a. Model a => Angle -> Angle -> a -> HorizontalPosition a
latLongPos' Angle
lat Angle
lon WGS84
WGS84

-- | 'HorizontalPosition' from given latitude & longitude in __decimal degrees__ in the spherical datum derived from WGS84.

--

-- Latitude & longitude values are first converted to 'Angle' to ensure a consistent resolution with the rest of the

-- API, then wrapped to their respective range.

--

-- This is equivalent to:

--

-- > Geodetic.latLongPos lat lon S84

s84Pos :: Double -> Double -> HorizontalPosition S84
s84Pos :: Double -> Double -> HorizontalPosition S84
s84Pos Double
lat Double
lon = Double -> Double -> S84 -> HorizontalPosition S84
forall a. Model a => Double -> Double -> a -> HorizontalPosition a
latLongPos Double
lat Double
lon S84
S84

-- | 'Position' from given latitude & longitude in the spherical datum derived from WGS84.

--

-- Latitude & longitude values are wrapped to their respective range.

--

-- This is equivalent to:

--

-- > Geodetic.latLongPos' lat lon h S84

s84Pos' :: Angle -> Angle -> HorizontalPosition S84
s84Pos' :: Angle -> Angle -> HorizontalPosition S84
s84Pos' Angle
lat Angle
lon = Angle -> Angle -> S84 -> HorizontalPosition S84
forall a. Model a => Angle -> Angle -> a -> HorizontalPosition a
latLongPos' Angle
lat Angle
lon S84
S84

-- | 'Position' from given /n/-vector (x, y, z coordinates) in the given model.

--

-- (x, y, z) will be converted first to latitude & longitude to ensure a consistent resolution with the rest of the API.

--

-- This is equivalent to:

--

-- > Geodetic.nvectorPos' (Math3d.vec3 x y z)

nvectorPos :: (Model a) => Double -> Double -> Double -> a -> HorizontalPosition a
nvectorPos :: Double -> Double -> Double -> a -> HorizontalPosition a
nvectorPos Double
x Double
y Double
z = V3 -> a -> HorizontalPosition a
forall a. Model a => V3 -> a -> HorizontalPosition a
nvectorPos' (Double -> Double -> Double -> V3
Math3d.vec3 Double
x Double
y Double
z)

-- | 'HorizontalPosition' from given /n/-vector (x, y, z coordinates) in the given model.

--

-- (x, y, z) will be converted first to latitude & longitude to ensure a consistent resolution with the rest of the API.

nvectorPos' :: (Model a) => Math3d.V3 -> a -> HorizontalPosition a
nvectorPos' :: V3 -> a -> HorizontalPosition a
nvectorPos' V3
v a
m = HCoords -> a -> HorizontalPosition a
forall a. HCoords -> a -> HorizontalPosition a
HorizontalPosition (Angle -> Angle -> V3 -> HCoords
HCoords Angle
lat Angle
wlon V3
nv) a
m
  where
    (Angle
lat, Angle
lon) = V3 -> (Angle, Angle)
nvectorToLatLong V3
v
    lon' :: Angle
lon' = Angle -> Angle -> Angle
checkPole Angle
lat Angle
lon
    nv :: V3
nv = (Angle, Angle) -> V3
nvectorFromLatLong (Angle
lat, Angle
lon')
    wlon :: Angle
wlon = Angle -> a -> Angle
forall a. Model a => Angle -> a -> Angle
convertLon Angle
lon' a
m

-- | 'Position' from given /n/-vector (x, y, z coordinates) and height in the given model.

--

-- (x, y, z) will be converted first to latitude & longitude to ensure a consistent resolution with the rest of the API.

-- This is equivalent to:

--

-- > Geodetic.nvectorHeightPos' (Math3d.vec3 x y z) h

nvectorHeightPos :: (Model a) => Double -> Double -> Double -> Length -> a -> Position a
nvectorHeightPos :: Double -> Double -> Double -> Length -> a -> Position a
nvectorHeightPos Double
x Double
y Double
z = V3 -> Length -> a -> Position a
forall a. Model a => V3 -> Length -> a -> Position a
nvectorHeightPos' (Double -> Double -> Double -> V3
Math3d.vec3 Double
x Double
y Double
z)

-- | 'Position' from given /n/-vector (x, y, z coordinates) and height in the given model.

--

-- (x, y, z) will be converted first to latitude & longitude to ensure a consistent resolution with the rest of the API.

nvectorHeightPos' :: (Model a) => Math3d.V3 -> Length -> a -> Position a
nvectorHeightPos' :: V3 -> Length -> a -> Position a
nvectorHeightPos' V3
v Length
h a
m = HorizontalPosition a -> Length -> Position a
forall a. Model a => HorizontalPosition a -> Length -> Position a
atHeight (V3 -> a -> HorizontalPosition a
forall a. Model a => V3 -> a -> HorizontalPosition a
nvectorPos' V3
v a
m) Length
h

-- | 'Position' from 'HorizontalPosition' & height.

atHeight :: (Model a) => HorizontalPosition a -> Length -> Position a
atHeight :: HorizontalPosition a -> Length -> Position a
atHeight (HorizontalPosition HCoords
c a
m) Length
h = HCoords -> Length -> a -> Position a
forall a. HCoords -> Length -> a -> Position a
Position HCoords
c Length
h a
m

-- | 'HorizontalPosition' from 'Position'.

atSurface :: (Model a) => Position a -> HorizontalPosition a
atSurface :: Position a -> HorizontalPosition a
atSurface (Position HCoords
c Length
_ a
m) = HCoords -> a -> HorizontalPosition a
forall a. HCoords -> a -> HorizontalPosition a
HorizontalPosition HCoords
c a
m

-- | Reads an 'HorizontalPosition, from the given string using 'horizontalPosition', for example:

--

-- >>> Geodetic.readHorizontalPosition "55°36'21''N 013°00'02''E" WGS84

-- Just 55°36'21.000"N,13°0'2.000"E (WGS84)

readHorizontalPosition :: (Model a) => String -> a -> Maybe (HorizontalPosition a)
readHorizontalPosition :: String -> a -> Maybe (HorizontalPosition a)
readHorizontalPosition String
s a
m =
    case ((HorizontalPosition a, String) -> HorizontalPosition a)
-> [(HorizontalPosition a, String)] -> [HorizontalPosition a]
forall a b. (a -> b) -> [a] -> [b]
map (HorizontalPosition a, String) -> HorizontalPosition a
forall a b. (a, b) -> a
fst ([(HorizontalPosition a, String)] -> [HorizontalPosition a])
-> [(HorizontalPosition a, String)] -> [HorizontalPosition a]
forall a b. (a -> b) -> a -> b
$ ((HorizontalPosition a, String) -> Bool)
-> [(HorizontalPosition a, String)]
-> [(HorizontalPosition a, String)]
forall a. (a -> Bool) -> [a] -> [a]
filter (String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null (String -> Bool)
-> ((HorizontalPosition a, String) -> String)
-> (HorizontalPosition a, String)
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (HorizontalPosition a, String) -> String
forall a b. (a, b) -> b
snd) ([(HorizontalPosition a, String)]
 -> [(HorizontalPosition a, String)])
-> [(HorizontalPosition a, String)]
-> [(HorizontalPosition a, String)]
forall a b. (a -> b) -> a -> b
$ ReadP (HorizontalPosition a) -> ReadS (HorizontalPosition a)
forall a. ReadP a -> ReadS a
readP_to_S (a -> ReadP (HorizontalPosition a)
forall a. Model a => a -> ReadP (HorizontalPosition a)
horizontalPosition a
m) String
s of
        [] -> Maybe (HorizontalPosition a)
forall a. Maybe a
Nothing
        HorizontalPosition a
p:[HorizontalPosition a]
_ -> HorizontalPosition a -> Maybe (HorizontalPosition a)
forall a. a -> Maybe a
Just HorizontalPosition a
p

-- | Reads a 'Position' from the given string using 'position', for example:

--

-- >>> Geodetic.readPosition "55°36'21''N 013°00'02''E 1500m" WGS84

-- Just 55°36'21.000"N,13°0'2.000"E 1500.0m (WGS84)

readPosition :: (Model a) => String -> a -> Maybe (Position a)
readPosition :: String -> a -> Maybe (Position a)
readPosition String
s a
m =
    case ((Position a, String) -> Position a)
-> [(Position a, String)] -> [Position a]
forall a b. (a -> b) -> [a] -> [b]
map (Position a, String) -> Position a
forall a b. (a, b) -> a
fst ([(Position a, String)] -> [Position a])
-> [(Position a, String)] -> [Position a]
forall a b. (a -> b) -> a -> b
$ ((Position a, String) -> Bool)
-> [(Position a, String)] -> [(Position a, String)]
forall a. (a -> Bool) -> [a] -> [a]
filter (String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null (String -> Bool)
-> ((Position a, String) -> String) -> (Position a, String) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Position a, String) -> String
forall a b. (a, b) -> b
snd) ([(Position a, String)] -> [(Position a, String)])
-> [(Position a, String)] -> [(Position a, String)]
forall a b. (a -> b) -> a -> b
$ ReadP (Position a) -> ReadS (Position a)
forall a. ReadP a -> ReadS a
readP_to_S (a -> ReadP (Position a)
forall a. Model a => a -> ReadP (Position a)
position a
m) String
s of
        [] -> Maybe (Position a)
forall a. Maybe a
Nothing
        Position a
p:[Position a]
_ -> Position a -> Maybe (Position a)
forall a. a -> Maybe a
Just Position a
p

-- | Parses and returns a 'HorizontalPosition'.

--

-- Supported formats:

--

--     * DD(MM)(SS)[N|S]DDD(MM)(SS)[E|W] - e.g. 553621N0130002E or 0116S03649E or 47N122W

--

--     * 'Angle'[N|S] 'Angle'[E|W] - e.g. 55°36'21''N 13°0'02''E or 11°16'S 36°49'E or 47°N 122°W

horizontalPosition :: (Model a) => a -> ReadP (HorizontalPosition a)
horizontalPosition :: a -> ReadP (HorizontalPosition a)
horizontalPosition a
m = do
    (Angle
lat, Angle
lon) <- a -> ReadP (Angle, Angle)
forall a. Model a => a -> ReadP (Angle, Angle)
LL.latLongDms a
m
    HorizontalPosition a -> ReadP (HorizontalPosition a)
forall (m :: * -> *) a. Monad m => a -> m a
return (Angle -> Angle -> a -> HorizontalPosition a
forall a. Model a => Angle -> Angle -> a -> HorizontalPosition a
latLongPos' Angle
lat Angle
lon a
m)

-- | Parses and returns a 'Position': the beginning of the string is parsed by 'horizontalPosition' and additionally the

-- string may end by a valid 'Length'.

position :: (Model a) => a -> ReadP (Position a)
position :: a -> ReadP (Position a)
position a
m = do
    HorizontalPosition a
hp <- a -> ReadP (HorizontalPosition a)
forall a. Model a => a -> ReadP (HorizontalPosition a)
horizontalPosition a
m
    ReadP ()
skipSpaces
    Length
h <- Length -> ReadP Length -> ReadP Length
forall a. a -> ReadP a -> ReadP a
option Length
Length.zero ReadP Length
Length.length
    Position a -> ReadP (Position a)
forall (m :: * -> *) a. Monad m => a -> m a
return (HorizontalPosition a -> Length -> Position a
forall a. Model a => HorizontalPosition a -> Length -> Position a
atHeight HorizontalPosition a
hp Length
h)

-- | @nvectorToLatLong nv@ returns (latitude, longitude) pair equivalent to the given /n/-vector @nv@.

-- Latitude is always in [-90°, 90°] and longitude in [-180°, 180°].

nvectorToLatLong :: Math3d.V3 -> (Angle, Angle)
nvectorToLatLong :: V3 -> (Angle, Angle)
nvectorToLatLong V3
v = (Angle
lat, Angle
lon)
  where
    x :: Double
x = V3 -> Double
Math3d.v3x V3
v
    y :: Double
y = V3 -> Double
Math3d.v3y V3
v
    z :: Double
z = V3 -> Double
Math3d.v3z V3
v
    lat :: Angle
lat = Double -> Double -> Angle
Angle.atan2 Double
z (Double -> Double
forall a. Floating a => a -> a
sqrt (Double
x Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
x Double -> Double -> Double
forall a. Num a => a -> a -> a
+ Double
y Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
y))
    lon :: Angle
lon = Double -> Double -> Angle
Angle.atan2 Double
y Double
x

-- | @nvectorFromLatLong ll@ returns /n/-vector equivalent to the given (latitude, longitude) pair @ll@.

nvectorFromLatLong :: (Angle, Angle) -> Math3d.V3
nvectorFromLatLong :: (Angle, Angle) -> V3
nvectorFromLatLong (Angle
lat, Angle
lon) = Double -> Double -> Double -> V3
Math3d.vec3 Double
x Double
y Double
z
  where
    cl :: Double
cl = Angle -> Double
Angle.cos Angle
lat
    x :: Double
x = Double
cl Double -> Double -> Double
forall a. Num a => a -> a -> a
* Angle -> Double
Angle.cos Angle
lon
    y :: Double
y = Double
cl Double -> Double -> Double
forall a. Num a => a -> a -> a
* Angle -> Double
Angle.sin Angle
lon
    z :: Double
z = Angle -> Double
Angle.sin Angle
lat

-- | @antipode p@ computes the antipodal position of @p@: the position which is diametrically opposite to @p@.

antipode :: (Model a) => HorizontalPosition a -> HorizontalPosition a
antipode :: HorizontalPosition a -> HorizontalPosition a
antipode HorizontalPosition a
p = V3 -> a -> HorizontalPosition a
forall a. Model a => V3 -> a -> HorizontalPosition a
nvectorPos' (V3 -> V3
anti (V3 -> V3)
-> (HorizontalPosition a -> V3) -> HorizontalPosition a -> V3
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HorizontalPosition a -> V3
forall a. HasCoordinates a => a -> V3
nvector (HorizontalPosition a -> V3) -> HorizontalPosition a -> V3
forall a b. (a -> b) -> a -> b
$ HorizontalPosition a
p) (HorizontalPosition a -> a
forall a. Model a => HorizontalPosition a -> a
model HorizontalPosition a
p)

-- | @antipode p@ computes the antipodal position of @p@: the position which is diametrically opposite to @p@ at the

-- same height.

antipode' :: (Model a) => Position a -> Position a
antipode' :: Position a -> Position a
antipode' Position a
p = V3 -> Length -> a -> Position a
forall a. Model a => V3 -> Length -> a -> Position a
nvectorHeightPos' (V3 -> V3
anti (V3 -> V3) -> (Position a -> V3) -> Position a -> V3
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Position a -> V3
forall a. HasCoordinates a => a -> V3
nvector (Position a -> V3) -> Position a -> V3
forall a b. (a -> b) -> a -> b
$ Position a
p) (Position a -> Length
forall a. Model a => Position a -> Length
height Position a
p) (Position a -> a
forall a. Model a => Position a -> a
model' Position a
p)

anti :: Math3d.V3 -> Math3d.V3
anti :: V3 -> V3
anti V3
v = V3 -> Double -> V3
Math3d.scale V3
v (-Double
1.0)

-- | Horizontal position of the North Pole in the given model.

northPole :: (Model a) => a -> HorizontalPosition a
northPole :: a -> HorizontalPosition a
northPole = Double -> Double -> a -> HorizontalPosition a
forall a. Model a => Double -> Double -> a -> HorizontalPosition a
latLongPos Double
90 Double
0

-- | Horizontal position of the South Pole in the given model.

southPole :: (Model a) => a -> HorizontalPosition a
southPole :: a -> HorizontalPosition a
southPole = Double -> Double -> a -> HorizontalPosition a
forall a. Model a => Double -> Double -> a -> HorizontalPosition a
latLongPos (-Double
90) Double
0

wrap :: (Model a) => Angle -> Angle -> Math3d.V3 -> a -> (Angle, Angle)
wrap :: Angle -> Angle -> V3 -> a -> (Angle, Angle)
wrap Angle
lat Angle
lon V3
nv a
m =
    if Angle -> Angle -> a -> Bool
forall a. Model a => Angle -> Angle -> a -> Bool
LL.isValidLatLong Angle
lat Angle
lon a
m
        then (Angle
lat, Angle
lon)
        else V3 -> a -> (Angle, Angle)
forall a. Model a => V3 -> a -> (Angle, Angle)
llWrapped V3
nv a
m

llWrapped :: (Model a) => Math3d.V3 -> a -> (Angle, Angle)
llWrapped :: V3 -> a -> (Angle, Angle)
llWrapped V3
nv a
m = (Angle
lat, Angle
lon')
  where
    (Angle
lat, Angle
lon) = V3 -> (Angle, Angle)
nvectorToLatLong V3
nv
    lon' :: Angle
lon' = Angle -> a -> Angle
forall a. Model a => Angle -> a -> Angle
convertLon Angle
lon a
m

convertLon :: (Model a) => Angle -> a -> Angle
convertLon :: Angle -> a -> Angle
convertLon Angle
lon a
m =
    case (a -> LongitudeRange
forall a. Model a => a -> LongitudeRange
longitudeRange a
m) of
        LongitudeRange
L180 -> Angle
lon
        LongitudeRange
L360 ->
            if Angle -> a -> Bool
forall a. Model a => Angle -> a -> Bool
LL.isValidLong Angle
lon a
m
                then Angle
lon
                else Angle -> Angle -> Angle
Angle.add Angle
lon (Double -> Angle
Angle.decimalDegrees Double
360)

checkPole :: Angle -> Angle -> Angle
checkPole :: Angle -> Angle -> Angle
checkPole Angle
lat Angle
lon
    | Angle
lat Angle -> Angle -> Bool
forall a. Eq a => a -> a -> Bool
== Double -> Angle
Angle.decimalDegrees Double
90 Bool -> Bool -> Bool
|| Angle
lat Angle -> Angle -> Bool
forall a. Eq a => a -> a -> Bool
== Double -> Angle
Angle.decimalDegrees (-Double
90) = Angle
Angle.zero
    | Bool
otherwise = Angle
lon