module Data.Geo.Jord.Geodesic
(
Geodesic
, geodesicPos1
, geodesicPos2
, geodesicBearing1
, geodesicBearing2
, geodesicLength
, directGeodesic
, inverseGeodesic
, destination
, finalBearing
, initialBearing
, surfaceDistance
) where
import Data.Geo.Jord.Internal
import Data.Geo.Jord.Position
data Geodesic a =
Geodesic
{ geodesicPos1 :: Position a
, geodesicPos2 :: Position a
, geodesicBearing1 :: Maybe Angle
, geodesicBearing2 :: Maybe Angle
, geodesicLength :: Length
}
deriving (Eq, Show)
directGeodesic :: (Ellipsoidal a) => Position a -> Angle -> Length -> Maybe (Geodesic a)
directGeodesic p1 b1 d
| d == zero = Just (Geodesic p1 p1 (Just b1) (Just b1) 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 = normalise (radians (atan2 sinAlpha (-x))) (decimalDegrees 360.0)
p2 = latLongHeightPos' (radians lat2) (radians lon2) (height p1) (model p1)
where
lat1 = toRadians . latitude $ p1
lon1 = toRadians . longitude $ p1
ell = surface . model $ p1
(a, b, f) = abf ell
br1 = 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 = toMetres d
sigma = dm / (b * _A)
rec = directRec sigma1 dm _A _B b sigma 0
inverseGeodesic :: (Ellipsoidal a) => Position a -> Position a -> Maybe (Geodesic a)
inverseGeodesic p1 p2
| llEq p1 p2 = Just (Geodesic p1 p2 Nothing Nothing 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 = 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 = normalise (radians a1R) (decimalDegrees 360.0)
b2 = normalise (radians a2R) (decimalDegrees 360.0)
where
lat1 = toRadians . latitude $ p1
lon1 = toRadians . longitude $ p1
lat2 = toRadians . latitude $ p2
lon2 = toRadians . longitude $ p2
ell = surface . 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
finalBearing :: (Ellipsoidal a) => Position a -> Position a -> Maybe Angle
finalBearing p1 p2 = inverseGeodesic p1 p2 >>= geodesicBearing2
initialBearing :: (Ellipsoidal a) => Position a -> Position a -> Maybe Angle
initialBearing p1 p2 = inverseGeodesic p1 p2 >>= geodesicBearing1
surfaceDistance :: (Ellipsoidal a) => Position a -> Position a -> Maybe Length
surfaceDistance p1 p2 = fmap geodesicLength (inverseGeodesic p1 p2)
destination :: (Ellipsoidal a) => Position a -> Angle -> Length -> Maybe (Position a)
destination p b d = fmap geodesicPos2 (directGeodesic p b d)
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 = (toMetres . equatorialRadius $ e, toMetres . polarRadius $ e, flattening e)