-- |
-- Module:      Data.Geo.Jord.Geocentric
-- Copyright:   (c) 2020 Cedric Liegeois
-- License:     BSD3
-- Maintainer:  Cedric Liegeois <ofmooseandmen@yahoo.fr>
-- Stability:   experimental
-- Portability: portable
--
-- Geocentric coordinates of points (X, Y, and Z Cartesian coordinates) in specified models.
--
-- For the Earth the coordinate system is known as ECEF (acronym for earth-centered, earth-fixed),
-- or ECR (initialism for earth-centered rotational).
--
-- In order to use this module you should start with the following imports:
--
-- @
-- import qualified Data.Geo.Jord.Geocentric as Geocentric
-- import qualified Data.Geo.Jord.Length as Length
-- import Data.Geo.Jord.Models
-- @
--
-- see "Data.Geo.Jord.Models" for supported models.
module Data.Geo.Jord.Geocentric
    ( Position(..)
    , coords
    , metresCoords
    , metresPos
    , metresPos'
    , antipode
    , northPole
    , southPole
    ) where

import Data.Geo.Jord.Ellipsoid (polarRadius)
import Data.Geo.Jord.Length (Length)
import qualified Data.Geo.Jord.Length as Length (metres, toMetres)
import qualified Data.Geo.Jord.Math3d as Math3d
import Data.Geo.Jord.Model

-- | Geocentric coordinates (cartesian X, Y, Z) of a position in a specified 'Model'.
--
-- @gx-gy@ plane is the equatorial plane, @gx@ is on the prime meridian, and @gz@ on the polar axis.
--
-- On a spherical celestial body, an /n/-vector is equivalent to a normalised version of a
-- geocentric cartesian coordinate.
data Position a =
    Position
        { gx :: Length -- ^ x-coordinate
        , gy :: Length -- ^ y-coordinate
        , gz :: Length -- ^ z-coordinate
        , model :: a -- ^ model (e.g. WGS84)
        }
    deriving (Eq, Show)

-- | 3d vector representing the (X, Y, Z) coordinates in __metres__ of the given position.
metresCoords :: (Model a) => Position a -> Math3d.V3
metresCoords p = coords p Length.toMetres

-- | @coords p f@ returns the 3d vector representing the (X, Y, Z) coordinates in the unit
-- of @f@. For example:
--
-- >>> Geocentric.coords (Geocentric.metresPos 3194669.145061 3194669.145061 4487701.962256 WGS84) Length.toKilometres
-- V3 {vx = 3194.669145061, vy = 3194.669145061, vz = 4487.701962256}
coords :: (Model a) => Position a -> (Length -> Double) -> Math3d.V3
coords (Position x y z _) f = Math3d.vec3 (f x) (f y) (f z)

-- | Geocentric position from given (X, Y, Z) in __metres__ an given 'Model'.
metresPos :: (Model a) => Double -> Double -> Double -> a -> Position a
metresPos xm ym zm = Position (Length.metres xm) (Length.metres ym) (Length.metres zm)

-- | Geocentric position from given 3d vector (X, Y, Z) in __metres__ an given 'Model'.
metresPos' :: (Model a) => Math3d.V3 -> a -> Position a
metresPos' v = metresPos (Math3d.v3x v) (Math3d.v3y v) (Math3d.v3z v)

-- | @antipode p@ computes the antipodal position of @p@: the position which is diametrically
-- opposite to @p@.
antipode :: (Model a) => Position a -> Position a
antipode p = metresPos (Math3d.v3x avm) (Math3d.v3y avm) (Math3d.v3z avm) (model p)
  where
    c = metresCoords p
    avm = Math3d.scale c (-1.0)

-- | Surface position of the North Pole in the given model.
northPole :: (Model a) => a -> Position a
northPole m = metresPos 0 0 r m
  where
    r = Length.toMetres . polarRadius . surface $ m

-- | Surface position of the South Pole in the given model.
southPole :: (Model a) => a -> Position a
southPole m = metresPos 0 0 (-r) m
  where
    r = Length.toMetres . polarRadius . surface $ m