packages feed

gps-1.2: Test/GpsTest.hs

import Geo.Computations
import Data.Time
import Data.List
import Data.Ord
import Data.Fixed
import Test.QuickCheck
import Test.Framework (Test, defaultMain, testGroup)
import Test.Framework.Providers.QuickCheck2 (testProperty)
import Control.Applicative
import Control.Monad

instance Arbitrary UTCTime where
  arbitrary = UTCTime <$> arbitrary <*> liftM (secondsToDiffTime . abs) (arbitrary :: Gen Integer)

instance Arbitrary Day where
  arbitrary = fromGregorian <$> liftM ((+ 1000) . (`mod` 1100) .abs) arbitrary <*> liftM abs arbitrary <*> liftM abs arbitrary

instance Arbitrary NominalDiffTime where
  arbitrary = liftM fromIntegral (arbitrary :: Gen Int)

instance Arbitrary Point where
  arbitrary =
    pt      <$> fmap (`mod'` 90) arbitrary -- Lat
            <*> fmap (`mod'` 180) arbitrary -- Lon
            <*> arbitrary -- Time
            <*> arbitrary -- elevation

newtype Trl = Trl [Point]
  deriving (Show)

instance Arbitrary Trl where
  arbitrary = do
    b <- (`mod` 5) `fmap` arbitrary :: Gen Int
    pnts <- mapM (\_ -> arbitrary) [0..abs b]
    return $ Trl (sortBy (comparing pntTime) pnts)

approxEq :: Point -> Point -> Bool
approxEq a b = distance a b <= 0.2 -- error of 13cm has been observed due to floating point issues when using add vector.

pSaneDistance :: Point -> Point -> Bool
pSaneDistance a b = distance a b <= circumferenceOfEarth / 2

pTriangleTheorem :: Point -> Point -> Point -> Bool
pTriangleTheorem a b c = 
    distance a b + distance b c >= distance a c  -- Traditional flat-surface geometry
 || distance a b + distance b c + distance c a == 2 * pi * radiusOfEarth

pAddVector_DistanceHeading_ident :: Point -> Point -> Bool
pAddVector_DistanceHeading_ident a b =
  let v = (distance a b, heading a b)
      c = addVector v a
  in (distance c b) <= 0.01 * (distance a b)

pConvexHull_Has_Extreme_Points :: Trl -> Bool
pConvexHull_Has_Extreme_Points (Trl ts) =
  let ch = convexHull ts
      ts' = sortBy (comparing pntLat) ts
      northMost = last ts'
      southMost = head ts'
  in length ts < 3 || (northMost `elem` ch && southMost `elem` ch)

pConvexHull_Bezier_Const :: Trl -> Double -> Bool
pConvexHull_Bezier_Const (Trl ts) n =
  let ts' = take 10 ts
      ch  = convexHull ts'
      n'  = abs (n `mod'` 1)
      bp  = bezierPoint ts' n'
      ch' = convexHull  (bp:ts')
  in length ts < 3 || ch == ch'

tests :: [Test]
tests =
  [
    testGroup "Coordinate Computations"
    [ testProperty "approxEq_id" (\x -> approxEq x x)
    , testProperty "saneDistance" pSaneDistance
    , testProperty "TriangleTheorem" pTriangleTheorem
    , testProperty "Vector identity" pAddVector_DistanceHeading_ident
    ]
-- These might make some sense in a local scope, but in a global range what
-- is the "left most" point?  On a sphere what is a convex hull?
--  , testGroup "Trail Computations"
--    [ testProperty "Hull has extreme points" pConvexHull_Has_Extreme_Points
--    , testProperty "HullContainsBezier" pConvexHull_Bezier_Const]
  ]


----------------------------------------------------------
------------- * HARNESS * --------------------------------
----------------------------------------------------------
main :: IO ()
main = defaultMain tests