module Data.Geo.Jord.Geodesic
(
Geodesic
, startPosition
, endPosition
, initialBearing
, finalBearing
, length
, direct
, inverse
) where
import Prelude hiding (length)
import Data.Geo.Jord.Angle (Angle)
import qualified Data.Geo.Jord.Angle as Angle
import Data.Geo.Jord.Ellipsoid
import Data.Geo.Jord.Geodetic (HorizontalPosition)
import qualified Data.Geo.Jord.Geodetic as Geodetic
import Data.Geo.Jord.Length (Length)
import qualified Data.Geo.Jord.Length as Length
import Data.Geo.Jord.Model (Ellipsoidal, surface)
data Geodesic a =
Geodesic
{ startPosition :: HorizontalPosition a
, endPosition :: HorizontalPosition a
, initialBearing :: Maybe Angle
, finalBearing :: Maybe Angle
, length :: Length
}
deriving (Eq, Show)
direct :: (Ellipsoidal a) => HorizontalPosition a -> Angle -> Length -> Maybe (Geodesic a)
direct p1 b1 d
| d == Length.zero = Just (Geodesic p1 p1 (Just b1) (Just b1) Length.zero)
| otherwise =
case rec of
Nothing -> Nothing
(Just (s, cosS, sinS, cos2S')) -> Just (Geodesic p1 p2 (Just b1) (Just b2) d)
where x = sinU1 * sinS - cosU1 * cosS * cosAlpha1
lat2 =
atan2
(sinU1 * cosS + cosU1 * sinS * cosAlpha1)
((1.0 - f) * sqrt (sinAlpha * sinAlpha + x * x))
lambda = atan2 (sinS * sinAlpha1) (cosU1 * cosS - sinU1 * sinS * cosAlpha1)
_C = f / 16.0 * cosSqAlpha * (4.0 + f * (4.0 - 3.0 * cosSqAlpha))
_L =
lambda -
(1.0 - _C) * f * sinAlpha *
(s + _C * sinS * (cos2S' + _C * cosS * (-1.0 + 2.0 * cos2S' * cos2S')))
lon2 = lon1 + _L
b2 =
Angle.normalise
(Angle.radians (atan2 sinAlpha (-x)))
(Angle.decimalDegrees 360.0)
p2 =
Geodetic.latLongPos'
(Angle.radians lat2)
(Angle.radians lon2)
(Geodetic.model p1)
where
lat1 = Angle.toRadians . Geodetic.latitude $ p1
lon1 = Angle.toRadians . Geodetic.longitude $ p1
ell = surface . Geodetic.model $ p1
(a, b, f) = abf ell
br1 = Angle.toRadians b1
cosAlpha1 = cos br1
sinAlpha1 = sin br1
(tanU1, cosU1, sinU1) = reducedLat lat1 f
sigma1 = atan2 tanU1 cosAlpha1
sinAlpha = cosU1 * sinAlpha1
cosSqAlpha = 1.0 - sinAlpha * sinAlpha
uSq = cosSqAlpha * (a * a - b * b) / (b * b)
_A = 1.0 + uSq / 16384.0 * (4096.0 + uSq * (-768.0 + uSq * (320.0 - 175.0 * uSq)))
_B = uSq / 1024.0 * (256.0 + uSq * (-128.0 + uSq * (74.0 - 47.0 * uSq)))
dm = Length.toMetres d
sigma = dm / (b * _A)
rec = directRec sigma1 dm _A _B b sigma 0
inverse :: (Ellipsoidal a) => HorizontalPosition a -> HorizontalPosition a -> Maybe (Geodesic a)
inverse p1 p2
| p1 == p2 = Just (Geodesic p1 p2 Nothing Nothing Length.zero)
| otherwise =
case rec of
Nothing -> Nothing
(Just (cosL, sinL, s, cosS, sinS, sinSqS, cos2S', cosSqA)) ->
Just (Geodesic p1 p2 (Just b1) (Just b2) d)
where uSq = cosSqA * (a * a - b * b) / (b * b)
_A =
1 +
uSq / 16384.0 * (4096.0 + uSq * (-768.0 + uSq * (320.0 - 175.0 * uSq)))
_B = uSq / 1024.0 * (256.0 + uSq * (-128.0 + uSq * (74.0 - 47.0 * uSq)))
deltaSigma =
_B * sinS *
(cos2S' +
_B / 4.0 *
(cosS * (-1.0 + 2.0 * cos2S' * cos2S') -
_B / 6.0 * cos2S' * (-3.0 + 4.0 * sinS * sinS) *
(-3.0 + 4.0 * cos2S' * cos2S')))
d = Length.metres (b * _A * (s - deltaSigma))
a1R =
if abs sinSqS < epsilon
then 0.0
else atan2 (cosU2 * sinL) (cosU1 * sinU2 - sinU1 * cosU2 * cosL)
a2R =
if abs sinSqS < epsilon
then pi
else atan2 (cosU1 * sinL) (-sinU1 * cosU2 + cosU1 * sinU2 * cosL)
b1 = Angle.normalise (Angle.radians a1R) (Angle.decimalDegrees 360.0)
b2 = Angle.normalise (Angle.radians a2R) (Angle.decimalDegrees 360.0)
where
lat1 = Angle.toRadians . Geodetic.latitude $ p1
lon1 = Angle.toRadians . Geodetic.longitude $ p1
lat2 = Angle.toRadians . Geodetic.latitude $ p2
lon2 = Angle.toRadians . Geodetic.longitude $ p2
ell = surface . Geodetic.model $ p1
(a, b, f) = abf ell
_L = lon2 - lon1
(_, cosU1, sinU1) = reducedLat lat1 f
(_, cosU2, sinU2) = reducedLat lat2 f
antipodal = abs _L > pi / 2.0 || abs (lat2 - lat1) > pi / 2.0
rec = inverseRec _L cosU1 sinU1 cosU2 sinU2 _L f antipodal 0
directRec ::
Double
-> Double
-> Double
-> Double
-> Double
-> Double
-> Int
-> Maybe (Double, Double, Double, Double)
directRec sigma1 dist _A _B b sigma i
| i == 100 = Nothing
| abs (sigma - newSigma) <= 1e-12 = Just (newSigma, cosSigma, sinSigma, cos2Sigma')
| otherwise = directRec sigma1 dist _A _B b newSigma (i + 1)
where
cos2Sigma' = cos (2 * sigma1 + sigma)
sinSigma = sin sigma
cosSigma = cos sigma
deltaSigma =
_B * sinSigma *
(cos2Sigma' +
_B / 4.0 *
(cosSigma * (-1.0 + 2.0 * cos2Sigma' * cos2Sigma') -
_B / 6.0 * cos2Sigma' * (-3.0 + 4.0 * sinSigma * sinSigma) *
(-3.0 + 4.0 * cos2Sigma' * cos2Sigma')))
newSigma = dist / (b * _A) + deltaSigma
inverseRec ::
Double
-> Double
-> Double
-> Double
-> Double
-> Double
-> Double
-> Bool
-> Int
-> Maybe (Double, Double, Double, Double, Double, Double, Double, Double)
inverseRec lambda cosU1 sinU1 cosU2 sinU2 _L f antipodal i
| i == 1000 = Nothing
| sinSqSigma < epsilon = Just (inverseFallback cosL sinL sinSqSigma antipodal)
| iterationCheck > pi = Nothing
| abs (lambda - newLambda) <= 1e-12 =
Just (cosL, sinL, sigma, cosSigma, sinSigma, sinSqSigma, cos2Sigma', cosSqAlpha)
| otherwise = inverseRec newLambda cosU1 sinU1 cosU2 sinU2 _L f antipodal (i + 1)
where
sinL = sin lambda
cosL = cos lambda
sinSqSigma =
(cosU2 * sinL) * (cosU2 * sinL) +
(cosU1 * sinU2 - sinU1 * cosU2 * cosL) * (cosU1 * sinU2 - sinU1 * cosU2 * cosL)
sinSigma = sqrt sinSqSigma
cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosL
sigma = atan2 sinSigma cosSigma
sinAlpha = cosU1 * cosU2 * sinL / sinSigma
cosSqAlpha = 1 - sinAlpha * sinAlpha
cos2Sigma' =
if cosSqAlpha /= 0
then cosSigma - 2.0 * sinU1 * sinU2 / cosSqAlpha
else 0
_C = f / 16.0 * cosSqAlpha * (4.0 + f * (4.0 - 3.0 * cosSqAlpha))
newLambda =
_L +
(1.0 - _C) * f * sinAlpha *
(sigma +
_C * sinSigma * (cos2Sigma' + _C * cosSigma * (-1.0 + 2.0 * cos2Sigma' * cos2Sigma')))
iterationCheck =
if antipodal
then abs newLambda - pi
else abs newLambda
inverseFallback ::
Double
-> Double
-> Double
-> Bool
-> (Double, Double, Double, Double, Double, Double, Double, Double)
inverseFallback cosL sinL sinSqSigma antipodal =
(cosL, sinL, sigma, cosSigma, sinSigma, sinSqSigma, cos2Sigma', cosSqAlpha)
where
sigma =
if antipodal
then pi
else 0
cosSigma =
if antipodal
then (-1)
else 1
sinSigma = 0
cos2Sigma' = 1
cosSqAlpha = 1
epsilon :: Double
epsilon = r
where
r = 1 - encodeFloat (m - 1) e
(m, e) = decodeFloat (1 :: Double)
reducedLat :: Double -> Double -> (Double, Double, Double)
reducedLat lat f = (tanU, cosU, sinU)
where
tanU = (1.0 - f) * tan lat
cosU = 1.0 / sqrt (1 + tanU * tanU)
sinU = tanU * cosU
abf :: Ellipsoid -> (Double, Double, Double)
abf e = (Length.toMetres . equatorialRadius $ e, Length.toMetres . polarRadius $ e, flattening e)