packages feed

jord-0.4.2.0: test/Data/Geo/Jord/KinematicsSpec.hs

module Data.Geo.Jord.KinematicsSpec
    ( spec
    ) where

import Data.Geo.Jord
import Data.Maybe (fromJust)
import Test.Hspec

spec :: Spec
spec =
    describe "kinematics" $ do
        describe "position" $ do
            it "computes position at t from p0, bearing and speed" $ do
                let p0 = latLongHeight (readLatLong "531914N0014347W") (metres 15000)
                let p1 = decimalLatLongHeight 53.1882691 0.1332741 (metres 15000)
                let t = Track p0 (decimalDegrees 96.0217) (kilometresPerHour 124.8)
                position84 t (hours 1) `shouldBe` p1
            it "handles poles" $
                -- distance between poles assuming a spherical earth (WGS84) = 20015.114352200002km
                -- track at north pole travelling at 20015.114352200002km/h and true north reaches the
                -- south pole after 1 hour.
             do
                let t = Track (decimalLatLong 90 0) zero (kilometresPerHour 20015.114352200002)
                position84 t (hours 1) `shouldBe` decimalLatLong (-90) 180.0
            it "return p0 if speed is 0" $ do
                let p0 = latLongHeight (readLatLong "531914N0014347W") (metres 15000)
                let t = Track p0 (decimalDegrees 96.0217) zero
                position84 t (hours 1) `shouldBe` p0
            it "return p0 if duration is 0" $ do
                let p0 = latLongHeight (readLatLong "531914N0014347W") (metres 15000)
                let t = Track p0 (decimalDegrees 96.0217) (kilometresPerHour 124.8)
                position84 t zero `shouldBe` p0
        describe "cpa" $ do
            it "handles trailing tracks" $ do
                let p1 = decimalLatLong 20 30
                let px = destination84 p1 (decimalDegrees 20) (kilometres 1)
                let p2 = interpolate p1 px 0.25
                let b1 = fromJust (initialBearing p1 px)
                let b2 = fromJust (initialBearing p2 px)
                let t1 = Track p1 b1 (knots 400)
                let t2 = Track p2 b2 (knots 400)
                let c = cpa84 t1 t2
                -- any time is correct but it should be close to zero since that's
                -- our initial value
                fmap (\r -> toMilliseconds (cpaTime r) < 5000) c `shouldBe` Just True
                fmap cpaDistance c `shouldBe` Just (metres 250.0036)
            it "handles heading tracks" $ do
                let p1 = decimalLatLong 20 30
                let p2 = decimalLatLong 21 31
                let b1 = fromJust (initialBearing p1 p2)
                let b2 = fromJust (initialBearing p2 p1)
                let t1 = Track p1 b1 (knots 400)
                let t2 = Track p2 b2 (knots 400)
                let c = cpa84 t1 t2
                -- distance between p1 and p2 = 152.354309 km
                -- speed = 740.8 km/h
                -- time = 152.354309 / 740.8 / 2
                fmap cpaTime c `shouldBe` Just (milliseconds 370191)
                fmap cpaDistance c `shouldBe` Just zero
            it "handles tracks at the same position" $ do
                let p = decimalLatLong 20 30
                let t1 = Track p (decimalDegrees 45) (knots 300)
                let t2 = Track p (decimalDegrees 135) (knots 500)
                let c = cpa84 t1 t2
                fmap cpaTime c `shouldBe` Just zero
                fmap cpaDistance c `shouldBe` Just zero
            it "computes time to CPA, positions and distance at CPA" $ do
                let p1 = decimalLatLong 20 (-60)
                let b1 = decimalDegrees 10
                let s1 = knots 15
                let p2 = decimalLatLong 34 (-50)
                let b2 = decimalDegrees 220
                let s2 = knots 300
                let t1 = Track p1 b1 s1
                let t2 = Track p2 b2 s2
                let c = cpa84 t1 t2
                fmap cpaTime c `shouldBe` Just (milliseconds 11396155)
                fmap cpaDistance c `shouldBe` Just (kilometres 124.2317453)
            it "returns Nothing if time to CPA is in the past" $ do
                let t1 = Track (decimalLatLong 30 30) (decimalDegrees 45) (knots 400)
                let t2 = Track (decimalLatLong 30.01 30) (decimalDegrees 315) (knots 400)
                cpa84 t1 t2 `shouldBe` Nothing
        describe "intercept" $ do
            it "returns Nothing if target and interceptor are at the same position" $
                intercept84
                    (Track (decimalLatLong 30 30) (decimalDegrees 45) (knots 400))
                    (decimalLatLong 30 30) `shouldBe`
                Nothing
            it "returns Nothing if interceptor is behing target" $ do
                let t = Track (decimalLatLong 45 67) (decimalDegrees 54) (knots 400)
                let ip = decimalLatLong 44 66
                intercept84 t ip `shouldBe` Nothing
            it "handles interceptor on the great circle of target and in front" $ do
                let tp = decimalLatLong 20 30
                let px = destination84 tp (decimalDegrees 20) (kilometres 1)
                let ip = interpolate tp px 0.25
                let b = fromJust (initialBearing tp px)
                let t = Track tp b (knots 400)
                let i = intercept84 t ip
                fmap interceptorSpeed i `shouldBe` Just zero
                fmap interceptPosition i `shouldBe` Just ip
                fmap interceptTime i `shouldBe` Just (seconds 1.215)
            it "returns Nothing if interceptor is behing target" $ do
                let t = Track (decimalLatLong 45 67) (decimalDegrees 181) (knots 400)
                let ip = decimalLatLong 44 66
                let i = intercept84 t ip
                fmap interceptorSpeed i `shouldBe` Just (knots 228.5538171521)
                fmap interceptTime i `shouldBe` Just (seconds 808.770)
                let interceptor =
                        Track
                            ip
                            (fromJust (fmap interceptorBearing i))
                            (fromJust (fmap interceptorSpeed i))
                fmap interceptPosition i `shouldBe`
                    Just (position84 interceptor (fromJust (fmap interceptTime i)))
            it "returns the minimum speed required for intercept to take place" $ do
                let t = Track (decimalLatLong 34 (-50)) (decimalDegrees 220) (knots 600)
                let ip = decimalLatLong 20 (-60)
                let i = intercept84 t ip
                fmap interceptorSpeed i `shouldBe` Just (knots 52.633367756059)
                fmap interceptTime i `shouldBe` Just (seconds 5993.831)
                let interceptor =
                        Track
                            ip
                            (fromJust (fmap interceptorBearing i))
                            (fromJust (fmap interceptorSpeed i))
                fmap interceptPosition i `shouldBe`
                    Just (position84 interceptor (fromJust (fmap interceptTime i)))
        describe "interceptBySpeed" $ do
            it "returns Nothing if target and interceptor are at the same position" $
                interceptBySpeed84
                    (Track (decimalLatLong 30 30) (decimalDegrees 45) (knots 400))
                    (decimalLatLong 30 30)
                    (knots 400) `shouldBe`
                Nothing
            it "returns Nothing if interceptor speed is below minimum speed" $ do
                let t = Track (decimalLatLong 34 (-50)) (decimalDegrees 220) (knots 600)
                let ip = decimalLatLong 20 (-60)
                interceptBySpeed84 t ip (knots 50) `shouldBe` Nothing
            it "returns the speed needed for intercept to take place" $ do
                let t = Track (decimalLatLong 34 (-50)) (decimalDegrees 220) (knots 600)
                let ip = decimalLatLong 20 (-60)
                let i = interceptBySpeed84 t ip (knots 700)
                fmap interceptTime i `shouldBe` Just (seconds 2764.692)
                fmap interceptorBearing i `shouldBe` Just (decimalDegrees 25.93541277)
                fmap interceptDistance i `shouldBe` Just (kilometres 995.5960805999999)
            it "returns the same as intercept when called with minimum speed" $ do
                let t = Track (decimalLatLong 45 50) (decimalDegrees 54) (knots 500)
                let ip = decimalLatLong 70 30
                let mi = intercept84 t ip
                let i = interceptBySpeed84 t ip (fromJust (fmap interceptorSpeed mi))
                fmap interceptTime i `shouldBe` fmap interceptTime mi
        describe "interceptByTime" $ do
            it "returns Nothing if duration is zero" $
                interceptByTime84
                    (Track (decimalLatLong 30 30) (decimalDegrees 45) (knots 400))
                    (decimalLatLong 34 (-50))
                    zero `shouldBe`
                Nothing
            it "returns Nothing if duration is negative" $
                interceptByTime84
                    (Track (decimalLatLong 30 30) (decimalDegrees 45) (knots 400))
                    (decimalLatLong 34 (-50))
                    (seconds (-1)) `shouldBe`
                Nothing
            it "returns Nothing if target and interceptor are at the same position" $
                interceptByTime84
                    (Track (decimalLatLong 30 30) (decimalDegrees 45) (knots 400))
                    (decimalLatLong 30 30)
                    (seconds 10) `shouldBe`
                Nothing
            it "returns the speed needed for intercept to take place" $ do
                let t = Track (decimalLatLong 34 (-50)) (decimalDegrees 220) (knots 600)
                let ip = decimalLatLong 20 (-60)
                let d = seconds 2700
                let i = interceptByTime84 t ip d
                fmap interceptorSpeed i `shouldBe` Just (knots 730.959238)
                fmap interceptorBearing i `shouldBe` Just (decimalDegrees 26.1199030)
                fmap interceptPosition i `shouldBe` Just (decimalLatLong 28.1366797 (-55.4559475))
                fmap interceptDistance i `shouldBe` Just (metres 1015302.3815)
                fmap interceptTime i `shouldBe` Just (seconds 2700)
            it "handles the poles" $
                -- distance between poles assuming a spherical earth (WGS84) = 20015.114352200002km
                -- target at north pole travelling at 500km/h and true north can be intercepted from
                -- the south pole by an interceptor travelling at ~ 19515.114352200002km/h and 180 degrees.
             do
                let t = Track (decimalLatLong 90 0) zero (kilometresPerHour 500)
                let ip = decimalLatLong (-90) 0
                let i = interceptByTime84 t ip (seconds 3600)
                fmap interceptorSpeed i `shouldBe` Just (kilometresPerHour 19515.11434)
                fmap interceptorBearing i `shouldBe` Just (decimalDegrees 180)
            it "handles the interceptor being at the intercept position at t" $ do
                let tp = decimalLatLong 34 (-50)
                let t = Track tp (decimalDegrees 220) (knots 600)
                let d = seconds 3600
                let ip = position84 t d
                let i = interceptByTime84 t ip d
                fmap interceptorSpeed i `shouldBe` Just zero
                fmap interceptorBearing i `shouldBe` initialBearing ip tp