module Data.Geo.Jord.Geodetics
(
GreatCircle
, greatCircle
, greatCircleE
, greatCircleF
, greatCircleBearing
, angularDistance
, antipode
, crossTrackDistance
, crossTrackDistance84
, destination
, destination84
, finalBearing
, initialBearing
, interpolate
, intersections
, insideSurface
, mean
, surfaceDistance
, surfaceDistance84
) where
import Control.Monad.Fail
import Data.Fixed
import Data.Geo.Jord.Angle
import Data.Geo.Jord.AngularPosition
import Data.Geo.Jord.Earth (r84)
import Data.Geo.Jord.LatLong
import Data.Geo.Jord.Length
import Data.Geo.Jord.NVector
import Data.Geo.Jord.Quantity
import Data.Geo.Jord.Transform
import Data.Geo.Jord.Vector3d
import Data.List (subsequences)
import Data.Maybe (fromMaybe)
import Prelude hiding (fail)
data GreatCircle = GreatCircle
{ normal :: Vector3d
, dscr :: String
} deriving (Eq)
instance Show GreatCircle where
show = dscr
greatCircle :: (NTransform a, Show a) => a -> a -> GreatCircle
greatCircle p1 p2 =
fromMaybe
(error (show p1 ++ " and " ++ show p2 ++ " do not define a unique Great Circle"))
(greatCircleF p1 p2)
greatCircleE :: (NTransform a) => a -> a -> Either String GreatCircle
greatCircleE p1 p2
| v1 == v2 = Left "Invalid Great Circle: positions are equal"
| (realToFrac (vnorm (vadd v1 v2)) :: Nano) == 0 =
Left "Invalid Great Circle: positions are antipodal"
| otherwise =
Right (GreatCircle (vcross v1 v2) ("passing by " ++ show (ll p1) ++ " & " ++ show (ll p2)))
where
v1 = vector3d p1
v2 = vector3d p2
greatCircleF :: (NTransform a, MonadFail m) => a -> a -> m GreatCircle
greatCircleF p1 p2 =
case e of
Left err -> fail err
Right gc -> return gc
where
e = greatCircleE p1 p2
greatCircleBearing :: (NTransform a) => a -> Angle -> GreatCircle
greatCircleBearing p b =
GreatCircle (vsub n' e') ("passing by " ++ show (ll p) ++ " heading on " ++ show b)
where
v = vector3d p
e = vcross (vec northPole) v
n = vcross v e
e' = vscale e (cos' b / vnorm e)
n' = vscale n (sin' b / vnorm n)
angularDistance :: (NTransform a) => a -> a -> Maybe a -> Angle
angularDistance p1 p2 n = angularDistance' v1 v2 vn
where
v1 = vector3d p1
v2 = vector3d p2
vn = fmap vector3d n
antipode :: (NTransform a) => a -> a
antipode p = fromNVector (angular (vscale (vector3d nv) (-1.0)) h)
where
(AngularPosition nv h) = toNVector p
crossTrackDistance :: (NTransform a) => a -> GreatCircle -> Length -> Length
crossTrackDistance p gc =
arcLength (sub (angularDistance' (normal gc) (vector3d p) Nothing) (decimalDegrees 90))
crossTrackDistance84 :: (NTransform a) => a -> GreatCircle -> Length
crossTrackDistance84 p gc = crossTrackDistance p gc r84
destination :: (NTransform a) => a -> Angle -> Length -> Length -> a
destination p b d r
| toMetres d == 0.0 = p
| otherwise = fromNVector (angular vd h)
where
(AngularPosition nv h) = toNVector p
v = vec nv
ed = vunit (vcross (vec northPole) v)
nd = vcross v ed
ta = central d r
de = vadd (vscale nd (cos' b)) (vscale ed (sin' b))
vd = vadd (vscale v (cos' ta)) (vscale de (sin' ta))
destination84 :: (NTransform a) => a -> Angle -> Length -> a
destination84 p b d = destination p b d r84
finalBearing :: (Eq a, NTransform a) => a -> a -> Maybe Angle
finalBearing p1 p2 = fmap (\b -> normalise b (decimalDegrees 180)) (initialBearing p2 p1)
initialBearing :: (Eq a, NTransform a) => a -> a -> Maybe Angle
initialBearing p1 p2
| p1 == p2 = Nothing
| otherwise = Just (normalise (angularDistance' gc1 gc2 (Just v1)) (decimalDegrees 360))
where
v1 = vector3d p1
v2 = vector3d p2
gc1 = vcross v1 v2
gc2 = vcross v1 (vec northPole)
interpolate :: (NTransform a) => a -> a -> Double -> a
interpolate p0 p1 f
| f < 0 || f > 1 = error ("fraction must be in range [0..1], was " ++ show f)
| f == 0 = p0
| f == 1 = p1
| otherwise = fromNVector (angular iv ih)
where
(AngularPosition nv0 h0) = toNVector p0
(AngularPosition nv1 h1) = toNVector p1
v0 = vec nv0
v1 = vec nv1
iv = vunit (vadd v0 (vscale (vsub v1 v0) f))
ih = lrph h0 h1 f
intersections :: (NTransform a) => GreatCircle -> GreatCircle -> Maybe (a, a)
intersections gc1 gc2
| (vnorm i :: Double) == 0.0 = Nothing
| otherwise
, let ni = fromNVector (angular (vunit i) zero) = Just (ni, antipode ni)
where
i = vcross (normal gc1) (normal gc2)
insideSurface :: (Eq a, NTransform a) => a -> [a] -> Bool
insideSurface p ps
| null ps = False
| head ps == last ps = insideSurface p (init ps)
| length ps < 3 = False
| otherwise =
let aSum =
foldl
(\a v' -> add a (uncurry angularDistance' v' (Just v)))
(decimalDegrees 0)
(egdes (map (vsub v) vs))
in abs (toDecimalDegrees aSum) > 180.0
where
v = vector3d p
vs = fmap vector3d ps
mean :: (NTransform a) => [a] -> Maybe a
mean [] = Nothing
mean [p] = Just p
mean ps =
if null antipodals
then Just (fromNVector (angular (vunit (foldl vadd vzero vs)) zero))
else Nothing
where
vs = fmap vector3d ps
ts = filter (\l -> length l == 2) (subsequences vs)
antipodals =
filter (\t -> (realToFrac (vnorm (vadd (head t) (last t)) :: Double) :: Nano) == 0) ts
surfaceDistance :: (NTransform a) => a -> a -> Length -> Length
surfaceDistance p1 p2 = arcLength (angularDistance p1 p2 Nothing)
surfaceDistance84 :: (NTransform a) => a -> a -> Length
surfaceDistance84 p1 p2 = surfaceDistance p1 p2 r84
angularDistance' :: Vector3d -> Vector3d -> Maybe Vector3d -> Angle
angularDistance' v1 v2 n = atan2' sinO cosO
where
sign = maybe 1 (signum . vdot (vcross v1 v2)) n
sinO = sign * vnorm (vcross v1 v2)
cosO = vdot v1 v2
egdes :: [Vector3d] -> [(Vector3d, Vector3d)]
egdes ps = zip ps (tail ps ++ [head ps])
lrph :: Length -> Length -> Double -> Length
lrph h0 h1 f = metres h
where
h0' = toMetres h0
h1' = toMetres h1
h = h0' + (h1' - h0') * f
vector3d :: (NTransform a) => a -> Vector3d
vector3d = vec . pos . toNVector
angular :: Vector3d -> Length -> AngularPosition NVector
angular v = nvectorHeight (nvector (vx v) (vy v) (vz v))
ll :: (NTransform a) => a -> LatLong
ll = nvectorToLatLong . pos . toNVector