packages feed

geodetics 0.1.2 → 1.0.0

raw patch · 13 files changed

+585/−570 lines, 13 filesdep +Streamdep −dimensionaldep −semigroupsdep ~arraydep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: Stream

Dependencies removed: dimensional, semigroups

Dependency ranges changed: array, base

API changes (from Hackage documentation)

- Geodetics.Ellipsoids: helmertFromWSG84 :: Ellipsoid a => a -> ECEF -> ECEF
- Geodetics.Ellipsoids: helmertToWSG84 :: Ellipsoid a => a -> ECEF -> ECEF
+ Geodetics.Ellipsoids: _2 :: Int
+ Geodetics.Ellipsoids: _3 :: Int
+ Geodetics.Ellipsoids: _4 :: Int
+ Geodetics.Ellipsoids: _5 :: Int
+ Geodetics.Ellipsoids: _6 :: Int
+ Geodetics.Ellipsoids: _7 :: Int
+ Geodetics.Ellipsoids: arcminute :: Double
+ Geodetics.Ellipsoids: arcsecond :: Double
+ Geodetics.Ellipsoids: degree :: Double
+ Geodetics.Ellipsoids: helmertFromWGS84 :: Ellipsoid a => a -> ECEF -> ECEF
+ Geodetics.Ellipsoids: helmertToWGS84 :: Ellipsoid a => a -> ECEF -> ECEF
+ Geodetics.Ellipsoids: kilometer :: Double
- Geodetics.Altitude: altitude :: HasAltitude a => a -> Length Double
+ Geodetics.Altitude: altitude :: HasAltitude a => a -> Double
- Geodetics.Altitude: setAltitude :: HasAltitude a => Length Double -> a -> a
+ Geodetics.Altitude: setAltitude :: HasAltitude a => Double -> a -> a
- Geodetics.Ellipsoids: Helmert :: Length Double -> Dimensionless Double -> Dimensionless Double -> Helmert
+ Geodetics.Ellipsoids: Helmert :: Double -> Double -> Double -> Helmert
- Geodetics.Ellipsoids: LocalEllipsoid :: String -> Length Double -> Dimensionless Double -> Helmert -> LocalEllipsoid
+ Geodetics.Ellipsoids: LocalEllipsoid :: String -> Double -> Double -> Helmert -> LocalEllipsoid
- Geodetics.Ellipsoids: [cX, cY, cZ] :: Helmert -> Length Double
+ Geodetics.Ellipsoids: [cX, cY, cZ] :: Helmert -> Double
- Geodetics.Ellipsoids: [flatRLocal] :: LocalEllipsoid -> Dimensionless Double
+ Geodetics.Ellipsoids: [flatRLocal] :: LocalEllipsoid -> Double
- Geodetics.Ellipsoids: [helmertScale] :: Helmert -> Dimensionless Double
+ Geodetics.Ellipsoids: [helmertScale] :: Helmert -> Double
- Geodetics.Ellipsoids: [majorRadiusLocal] :: LocalEllipsoid -> Length Double
+ Geodetics.Ellipsoids: [majorRadiusLocal] :: LocalEllipsoid -> Double
- Geodetics.Ellipsoids: [rX, rY, rZ] :: Helmert -> Dimensionless Double
+ Geodetics.Ellipsoids: [rX, rY, rZ] :: Helmert -> Double
- Geodetics.Ellipsoids: add3 :: Num a => Vec3 (Quantity d a) -> Vec3 (Quantity d a) -> Vec3 (Quantity d a)
+ Geodetics.Ellipsoids: add3 :: Num a => Vec3 a -> Vec3 a -> Vec3 a
- Geodetics.Ellipsoids: cross3 :: Num a => Vec3 (Quantity d1 a) -> Vec3 (Quantity d2 a) -> Vec3 (Quantity (d1 * d2) a)
+ Geodetics.Ellipsoids: cross3 :: Num a => Vec3 a -> Vec3 a -> Vec3 a
- Geodetics.Ellipsoids: dot3 :: Num a => Vec3 (Quantity d1 a) -> Vec3 (Quantity d2 a) -> Quantity (d1 * d2) a
+ Geodetics.Ellipsoids: dot3 :: Num a => Vec3 a -> Vec3 a -> a
- Geodetics.Ellipsoids: eccentricity'2 :: Ellipsoid e => e -> Dimensionless Double
+ Geodetics.Ellipsoids: eccentricity'2 :: Ellipsoid e => e -> Double
- Geodetics.Ellipsoids: eccentricity2 :: Ellipsoid e => e -> Dimensionless Double
+ Geodetics.Ellipsoids: eccentricity2 :: Ellipsoid e => e -> Double
- Geodetics.Ellipsoids: flatR :: Ellipsoid a => a -> Dimensionless Double
+ Geodetics.Ellipsoids: flatR :: Ellipsoid a => a -> Double
- Geodetics.Ellipsoids: flattening :: Ellipsoid e => e -> Dimensionless Double
+ Geodetics.Ellipsoids: flattening :: Ellipsoid e => e -> Double
- Geodetics.Ellipsoids: invert3 :: Fractional a => Matrix3 (Quantity d a) -> Matrix3 (Quantity ((d * d) / ((d * d) * d)) a)
+ Geodetics.Ellipsoids: invert3 :: Fractional a => Matrix3 a -> Matrix3 a
- Geodetics.Ellipsoids: isometricLatitude :: Ellipsoid e => e -> Angle Double -> Angle Double
+ Geodetics.Ellipsoids: isometricLatitude :: Ellipsoid e => e -> Double -> Double
- Geodetics.Ellipsoids: latitudeRadius :: Ellipsoid e => e -> Angle Double -> Length Double
+ Geodetics.Ellipsoids: latitudeRadius :: Ellipsoid e => e -> Double -> Double
- Geodetics.Ellipsoids: majorRadius :: Ellipsoid a => a -> Length Double
+ Geodetics.Ellipsoids: majorRadius :: Ellipsoid a => a -> Double
- Geodetics.Ellipsoids: meridianRadius :: Ellipsoid e => e -> Angle Double -> Length Double
+ Geodetics.Ellipsoids: meridianRadius :: Ellipsoid e => e -> Double -> Double
- Geodetics.Ellipsoids: minorRadius :: Ellipsoid e => e -> Length Double
+ Geodetics.Ellipsoids: minorRadius :: Ellipsoid e => e -> Double
- Geodetics.Ellipsoids: negate3 :: Num a => Vec3 (Quantity d a) -> Vec3 (Quantity d a)
+ Geodetics.Ellipsoids: negate3 :: Num a => Vec3 a -> Vec3 a
- Geodetics.Ellipsoids: normal :: Ellipsoid e => e -> Angle Double -> Length Double
+ Geodetics.Ellipsoids: normal :: Ellipsoid e => e -> Double -> Double
- Geodetics.Ellipsoids: primeVerticalRadius :: Ellipsoid e => e -> Angle Double -> Length Double
+ Geodetics.Ellipsoids: primeVerticalRadius :: Ellipsoid e => e -> Double -> Double
- Geodetics.Ellipsoids: scale3 :: Num a => Vec3 (Quantity d a) -> Quantity d' a -> Vec3 (Quantity (d * d') a)
+ Geodetics.Ellipsoids: scale3 :: Num a => Vec3 a -> a -> Vec3 a
- Geodetics.Ellipsoids: transform3 :: Num a => Matrix3 (Quantity d a) -> Vec3 (Quantity d' a) -> Vec3 (Quantity (d * d') a)
+ Geodetics.Ellipsoids: transform3 :: Num a => Matrix3 a -> Vec3 a -> Vec3 a
- Geodetics.Ellipsoids: type ECEF = Vec3 (Length Double)
+ Geodetics.Ellipsoids: type ECEF = Vec3 Double
- Geodetics.Geodetic: Geodetic :: Angle Double -> Length Double -> e -> Geodetic e
+ Geodetics.Geodetic: Geodetic :: Double -> Double -> e -> Geodetic e
- Geodetics.Geodetic: [geoAlt] :: Geodetic e -> Length Double
+ Geodetics.Geodetic: [geoAlt] :: Geodetic e -> Double
- Geodetics.Geodetic: [latitude, longitude] :: Geodetic e -> Angle Double
+ Geodetics.Geodetic: [latitude, longitude] :: Geodetic e -> Double
- Geodetics.Geodetic: earthToGeo :: Ellipsoid e => e -> ECEF -> (Angle Double, Angle Double, Length Double)
+ Geodetics.Geodetic: earthToGeo :: Ellipsoid e => e -> ECEF -> (Double, Double, Double)
- Geodetics.Geodetic: geometricalDistance :: Ellipsoid e => Geodetic e -> Geodetic e -> Length Double
+ Geodetics.Geodetic: geometricalDistance :: Ellipsoid e => Geodetic e -> Geodetic e -> Double
- Geodetics.Geodetic: geometricalDistanceSq :: Ellipsoid e => Geodetic e -> Geodetic e -> Area Double
+ Geodetics.Geodetic: geometricalDistanceSq :: Ellipsoid e => Geodetic e -> Geodetic e -> Double
- Geodetics.Geodetic: groundDistance :: Ellipsoid e => Geodetic e -> Geodetic e -> Maybe (Length Double, Dimensionless Double, Dimensionless Double)
+ Geodetics.Geodetic: groundDistance :: Ellipsoid e => Geodetic e -> Geodetic e -> Maybe (Double, Double, Double)
- Geodetics.Geodetic: properAngle :: Angle Double -> Angle Double
+ Geodetics.Geodetic: properAngle :: Double -> Double
- Geodetics.Geodetic: showAngle :: Angle Double -> String
+ Geodetics.Geodetic: showAngle :: Double -> String
- Geodetics.Geodetic: type ECEF = Vec3 (Length Double)
+ Geodetics.Geodetic: type ECEF = Vec3 Double
- Geodetics.Grid: GridOffset :: Length Double -> GridOffset
+ Geodetics.Grid: GridOffset :: Double -> GridOffset
- Geodetics.Grid: GridPoint :: Length Double -> r -> GridPoint r
+ Geodetics.Grid: GridPoint :: Double -> r -> GridPoint r
- Geodetics.Grid: [deltaEast, deltaNorth, deltaAltitude] :: GridOffset -> Length Double
+ Geodetics.Grid: [deltaEast, deltaNorth, deltaAltitude] :: GridOffset -> Double
- Geodetics.Grid: [eastings, northings, altGP] :: GridPoint r -> Length Double
+ Geodetics.Grid: [eastings, northings, altGP] :: GridPoint r -> Double
- Geodetics.Grid: fromGridDigits :: Length Double -> String -> Maybe (Length Double, Length Double)
+ Geodetics.Grid: fromGridDigits :: Double -> String -> Maybe (Double, Double)
- Geodetics.Grid: offsetBearing :: GridOffset -> Angle Double
+ Geodetics.Grid: offsetBearing :: GridOffset -> Double
- Geodetics.Grid: offsetDistance :: GridOffset -> Length Double
+ Geodetics.Grid: offsetDistance :: GridOffset -> Double
- Geodetics.Grid: offsetDistanceSq :: GridOffset -> Area Double
+ Geodetics.Grid: offsetDistanceSq :: GridOffset -> Double
- Geodetics.Grid: offsetScale :: Dimensionless Double -> GridOffset -> GridOffset
+ Geodetics.Grid: offsetScale :: Double -> GridOffset -> GridOffset
- Geodetics.Grid: polarOffset :: Length Double -> Angle Double -> GridOffset
+ Geodetics.Grid: polarOffset :: Double -> Double -> GridOffset
- Geodetics.Grid: toGridDigits :: Length Double -> Int -> Length Double -> Maybe (Integer, String)
+ Geodetics.Grid: toGridDigits :: Double -> Int -> Double -> Maybe (Integer, String)
- Geodetics.Path: Path :: (Length Double -> (Geodetic e, Angle Double, Angle Double)) -> PathValidity -> Path e
+ Geodetics.Path: Path :: (Double -> (Geodetic e, Double, Double)) -> PathValidity -> Path e
- Geodetics.Path: [pathFunc] :: Path e -> Length Double -> (Geodetic e, Angle Double, Angle Double)
+ Geodetics.Path: [pathFunc] :: Path e -> Double -> (Geodetic e, Double, Double)
- Geodetics.Path: bisect :: Path e -> (Geodetic e -> Ordering) -> Length Double -> Length Double -> Length Double -> Maybe (Length Double)
+ Geodetics.Path: bisect :: Path e -> (Geodetic e -> Ordering) -> Double -> Double -> Double -> Maybe Double
- Geodetics.Path: intersect :: Ellipsoid e => Length Double -> Length Double -> Length Double -> Int -> Path e -> Path e -> Maybe (Length Double, Length Double)
+ Geodetics.Path: intersect :: Ellipsoid e => Double -> Double -> Double -> Int -> Path e -> Path e -> Maybe (Double, Double)
- Geodetics.Path: pathValidAt :: Path e -> Length Double -> Bool
+ Geodetics.Path: pathValidAt :: Path e -> Double -> Bool
- Geodetics.Path: rayPath :: Ellipsoid e => Geodetic e -> Angle Double -> Angle Double -> Path e
+ Geodetics.Path: rayPath :: Ellipsoid e => Geodetic e -> Double -> Double -> Path e
- Geodetics.Path: rhumbPath :: Ellipsoid e => Geodetic e -> Angle Double -> Path e
+ Geodetics.Path: rhumbPath :: Ellipsoid e => Geodetic e -> Double -> Path e
- Geodetics.Path: type PathValidity = (Length Double, Length Double)
+ Geodetics.Path: type PathValidity = (Double, Double)
- Geodetics.Stereographic: mkGridStereo :: Ellipsoid e => Geodetic e -> GridOffset -> Dimensionless Double -> GridStereo e
+ Geodetics.Stereographic: mkGridStereo :: Ellipsoid e => Geodetic e -> GridOffset -> Double -> GridStereo e
- Geodetics.TransverseMercator: mkGridTM :: Ellipsoid e => Geodetic e -> GridOffset -> Dimensionless Double -> GridTM e
+ Geodetics.TransverseMercator: mkGridTM :: Ellipsoid e => Geodetic e -> GridOffset -> Double -> GridTM e

Files

changelog.md view
@@ -20,3 +20,6 @@  Version 0.1.2: Fixed bugs #16 and #17: Unicode PRIME and DOUBLE PRIME now allowed in    position strings, and the degree symbol is allowed for decimal degrees.++Version 1.0.0: Removed dependency on Dimensional library. This is a breaking change:+   hence the major version bump. Also fixed bug #18 (and #19).
geodetics.cabal view
@@ -1,20 +1,20 @@+cabal-version:  3.0 name:           geodetics-version:        0.1.2-cabal-version:  >= 1.10+version:        1.0.0 build-type:     Simple author:         Paul Johnson <paul@cogito.org.uk>-data-files:+extra-doc-files:                 AddingProjections.txt,                 LICENSE,                 README.md,                 changelog.md,                 ToDo.txt-license:        BSD3-copyright:      Paul Johnson 2018.+license:        BSD-3-Clause+copyright:      Paul Johnson 2018,2024 synopsis:       Terrestrial coordinate systems and geodetic calculations. description:    Precise geographical coordinates (latitude & longitude), with conversion between                 different reference frames and projections.-                .+                                 Certain distinguished reference frames and grids are given distinct                 types so that coordinates expressed within them cannot be confused with                 from coordinates in other frames.@@ -22,7 +22,7 @@ maintainer:     Paul Johnson <paul@cogito.org.uk> homepage:       https://github.com/PaulJohnson/geodetics category:       Geography-tested-with:    GHC==8.6.3+tested-with:    GHC==9.10.1  source-repository head   type:     git@@ -31,10 +31,9 @@ library   hs-source-dirs:  src   build-depends:-                   base >= 4.6 && < 5,-                   dimensional >= 1.3,-                   array >= 0.4,-                   semigroups >= 0.9+                   base >= 4.17 && < 5,+                   array >= 0.1 && < 0.6,+                   Stream >= 0.4.6 && < 0.5   ghc-options:     -Wall   exposed-modules:                    Geodetics.Altitude,@@ -55,12 +54,11 @@   build-depends:   geodetics,                    base >= 4.6 && < 5,                    HUnit >= 1.2,-                   dimensional >= 1.3,                    QuickCheck >= 2.4,                    test-framework >= 0.4.1,                    test-framework-quickcheck2,                    test-framework-hunit,-                   array >= 0.4,+                   array,                    checkers   hs-source-dirs:                    test
src/Geodetics/Altitude.hs view
@@ -2,16 +2,15 @@   HasAltitude (..) ) where -import Numeric.Units.Dimensional.Prelude --- | All geographical coordinate systems need the concept of#+-- | All geographical coordinate systems need the concept of -- altitude above a reference point, usually associated with -- local sea level. --  -- Minimum definition: altitude, setAltitude. class HasAltitude a where-   altitude :: a -> Length Double-   setAltitude :: Length Double -> a -> a+   altitude :: a -> Double+   setAltitude :: Double -> a -> a    -- | Set altitude to zero.    groundPosition :: a -> a-   groundPosition = setAltitude _0+   groundPosition = setAltitude 0
src/Geodetics/Ellipsoids.hs view
@@ -1,19 +1,11 @@ {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE NoImplicitPrelude #-}-{-# LANGUAGE PatternGuards #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RoleAnnotations #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}  {- | An Ellipsoid is a reasonable best fit for the surface of the Earth over some defined area. WGS84 is the standard used for the whole@@ -22,12 +14,18 @@ -}  module Geodetics.Ellipsoids (-   -- ** Helmert transform between geodetic reference systems+   -- * Useful constants+   degree,+   arcminute,+   arcsecond,+   kilometer,+   _2, _3, _4, _5, _6, _7,+   -- * Helmert transform between geodetic reference systems    Helmert (..),    inverseHelmert,    ECEF,    applyHelmert,-   -- ** Ellipsoid models of the Geoid+   -- * Ellipsoid models of the Geoid    Ellipsoid (..),    WGS84 (..),    LocalEllipsoid (..),@@ -35,13 +33,13 @@    minorRadius,    eccentricity2,    eccentricity'2,-   -- ** Auxiliary latitudes and related Values+   -- * Auxiliary latitudes and related Values    normal,    latitudeRadius,    meridianRadius,    primeVerticalRadius,    isometricLatitude,-   -- ** Tiny linear algebra library for 3D vectors+   -- * Tiny linear algebra library for 3D vectors    Vec3,    Matrix3,    add3,@@ -54,14 +52,35 @@    cross3 ) where -import Data.Monoid (Monoid)-import Data.Semigroup (Semigroup, (<>))-import Numeric.Units.Dimensional-import Numeric.Units.Dimensional.Prelude-import qualified Numeric.Units.Dimensional.Dimensions.TypeLevel as T--- import Prelude ()  -- Numeric instances. +-- | All angles in this library are in radians. This is one degree in radians.+degree :: Double+degree = pi/180 +-- | One arc-minute in radians.+arcminute :: Double+arcminute = degree / 60++-- | One arc-second in radians.+arcsecond :: Double+arcsecond = arcminute / 60+++-- | All distances in this library are in meters. This is one kilometer in meters.+kilometer :: Double+kilometer = 1000++-- | Lots of geodetic calculations involve integer powers. Writing e.g. @x ^ 2@ causes+-- GHC to complain that the @2@ has ambiguous type. @x ** 2@ doesn't complain+-- but is much slower. So for convenience, here are small integers with type @Int@.+_2, _3, _4, _5, _6, _7 :: Int+_2 = 2+_3 = 3+_4 = 4+_5 = 5+_6 = 6+_7 = 7+ -- | 3d vector as @(X,Y,Z)@. type Vec3 a = (a,a,a) @@ -70,31 +89,29 @@   -- | Multiply a vector by a scalar.-scale3 :: (Num a) =>-   Vec3 (Quantity d a) -> Quantity d' a -> Vec3 (Quantity (d T.* d') a)+scale3 :: (Num a) =>  Vec3 a -> a -> Vec3 a scale3 (x,y,z) s = (x*s, y*s, z*s)   -- | Negation of a vector.-negate3 :: (Num a) => Vec3 (Quantity d a) -> Vec3 (Quantity d a)+negate3 :: (Num a) => Vec3 a -> Vec3 a negate3 (x,y,z) = (negate x, negate y, negate z)  -- | Add two vectors-add3 :: (Num a) => Vec3 (Quantity d a) -> Vec3 (Quantity d a) -> Vec3 (Quantity d a)+add3 :: (Num a) => Vec3 a -> Vec3 a -> Vec3 a add3 (x1,y1,z1) (x2,y2,z2) = (x1+x2, y1+y2, z1+z2)   -- | Multiply a matrix by a vector in the Dimensional type system. transform3 :: (Num a) =>-   Matrix3 (Quantity d a) -> Vec3 (Quantity d' a) -> Vec3 (Quantity (d T.* d') a)+   Matrix3 a -> Vec3 a -> Vec3 a transform3 (tx,ty,tz) v = (t tx v, t ty v, t tz v)    where       t (x1,y1,z1) (x2,y2,z2) = x1*x2 + y1*y2 + z1*z2   -- | Inverse of a 3x3 matrix.-invert3 :: (Fractional a) =>-   Matrix3 (Quantity d a) -> Matrix3 (Quantity ((d T.* d)/(d T.* d T.* d)) a)+invert3 :: (Fractional a) => Matrix3 a -> Matrix3 a invert3 ((x1,y1,z1),          (x2,y2,z2),          (x3,y3,z3)) =@@ -111,21 +128,21 @@   -- | Dot product of two vectors-dot3 :: (Num a) =>-   Vec3 (Quantity d1 a) -> Vec3 (Quantity d2 a) -> Quantity (d1 T.* d2) a+dot3 :: (Num a) => Vec3 a -> Vec3 a -> a dot3 (x1,y1,z1) (x2,y2,z2) = x1*x2 + y1*y2 + z1*z2  -- | Cross product of two vectors-cross3 :: (Num a) =>-   Vec3 (Quantity d1 a) -> Vec3 (Quantity d2 a) -> Vec3 (Quantity (d1 T.* d2) a)+cross3 :: (Num a) => Vec3 a -> Vec3 a -> Vec3 a cross3 (x1,y1,z1) (x2,y2,z2) = (y1*z2 - z1*y2, z1*x2 - x1*z2, x1*y2 - y1*x2)  --- | The 7 parameter Helmert transformation. The monoid instance allows composition.+-- | The 7 parameter Helmert transformation. The monoid instance allows composition but+-- is only accurate for the small values used in practical ellipsoids. data Helmert = Helmert {-   cX, cY, cZ :: Length Double,-   helmertScale :: Dimensionless Double,  -- ^ Parts per million-   rX, rY, rZ :: Dimensionless Double } deriving (Eq, Show)+   cX, cY, cZ :: Double,  -- ^ Offset in meters+   helmertScale :: Double,  -- ^ Parts per million+   rX, rY, rZ :: Double  -- ^ Rotation around each axis in radians.+} deriving (Eq, Show)  instance Semigroup Helmert where     h1 <> h2 = Helmert (cX h1 + cX h2) (cY h1 + cY h2) (cZ h1 + cZ h2)@@ -133,7 +150,7 @@                        (rX h1 + rX h2) (rY h1 + rY h2) (rZ h1 + rZ h2)  instance Monoid Helmert where-   mempty = Helmert (0 *~ meter) (0 *~ meter) (0 *~ meter) _0 _0 _0 _0+   mempty = Helmert 0 0 0 0 0 0 0    mappend = (<>)  -- | The inverse of a Helmert transformation.@@ -145,7 +162,7 @@  -- | Earth-centred, Earth-fixed coordinates as a vector. The origin and axes are -- not defined: use with caution.-type ECEF = Vec3 (Length Double)+type ECEF = Vec3 Double  -- | Apply a Helmert transformation to earth-centered coordinates. applyHelmert:: Helmert -> ECEF -> ECEF@@ -154,7 +171,7 @@       cY h + s * (        rZ h  * x +        y - rX h * z),       cZ h + s * (negate (rY h) * x + rX h * y +        z))    where-      s = _1 + helmertScale h * (1e-6 *~ one)+      s = 1 + helmertScale h * 1e-6   -- | An Ellipsoid is defined by the major radius and the inverse flattening (which define its shape),@@ -170,17 +187,17 @@ -- > helmertToWGS84 = applyHelmert . helmert -- > helmertFromWGS84 e . helmertToWGS84 e = id class (Show a, Eq a) => Ellipsoid a where-   majorRadius :: a -> Length Double-   flatR :: a -> Dimensionless Double+   majorRadius :: a -> Double+   flatR :: a -> Double       -- ^ Inverse of the flattening.-   helmert :: a -> Helmert-   helmertToWSG84 :: a -> ECEF -> ECEF+   helmert :: a -> Helmert  -- ^ The Helmert parameters relative to WGS84,+   helmertToWGS84 :: a -> ECEF -> ECEF       -- ^ The Helmert transform that will convert a position wrt       -- this ellipsoid into a position wrt WGS84.-   helmertToWSG84 e = applyHelmert (helmert e)-   helmertFromWSG84 :: a -> ECEF -> ECEF+   helmertToWGS84 e = applyHelmert (helmert e)+   helmertFromWGS84 :: a -> ECEF -> ECEF       -- ^ And its inverse.-   helmertFromWSG84 e = applyHelmert (inverseHelmert $ helmert e)+   helmertFromWGS84 e = applyHelmert (inverseHelmert $ helmert e)   -- | The WGS84 geoid, major radius 6378137.0 meters, flattening = 1 / 298.257223563@@ -197,11 +214,11 @@    show _ = "WGS84"  instance Ellipsoid WGS84 where-   majorRadius _ = 6378137.0 *~ meter-   flatR _ = 298.257223563 *~ one+   majorRadius _ = 6378137.0+   flatR _ = 298.257223563    helmert _ = mempty-   helmertToWSG84 _ = id-   helmertFromWSG84 _ = id+   helmertToWGS84 _ = id+   helmertFromWGS84 _ = id   -- | Ellipsoids other than WGS84, used within a defined geographical area where@@ -211,9 +228,10 @@ -- Creating two different local ellipsoids with the same name is a Bad Thing. data LocalEllipsoid = LocalEllipsoid {    nameLocal :: String,-   majorRadiusLocal :: Length Double,-   flatRLocal :: Dimensionless Double,-   helmertLocal :: Helmert } deriving (Eq)+   majorRadiusLocal :: Double,+   flatRLocal :: Double,+   helmertLocal :: Helmert+} deriving (Eq)  instance Show LocalEllipsoid where     show = nameLocal@@ -225,48 +243,48 @@   -- | Flattening (f) of an ellipsoid.-flattening :: (Ellipsoid e) => e -> Dimensionless Double-flattening e = _1 / flatR e+flattening :: (Ellipsoid e) => e -> Double+flattening e = 1 / flatR e --- | The minor radius of an ellipsoid.-minorRadius :: (Ellipsoid e) => e -> Length Double-minorRadius e = majorRadius e * (_1 - flattening e)+-- | The minor radius of an ellipsoid in meters.+minorRadius :: (Ellipsoid e) => e -> Double+minorRadius e = majorRadius e * (1 - flattening e)   -- | The eccentricity squared of an ellipsoid.-eccentricity2 :: (Ellipsoid e) => e -> Dimensionless Double-eccentricity2 e = _2 * f - (f * f) where f = flattening e+eccentricity2 :: (Ellipsoid e) => e -> Double+eccentricity2 e = 2 * f - f^_2 where f = flattening e  -- | The second eccentricity squared of an ellipsoid.-eccentricity'2 :: (Ellipsoid e) => e -> Dimensionless Double-eccentricity'2 e = (f * (_2 - f)) / (_1 - f * f) where f = flattening e+eccentricity'2 :: (Ellipsoid e) => e -> Double+eccentricity'2 e = (f * (2 - f)) / (1 - f^_2) where f = flattening e  --- | Distance from the surface at the specified latitude to the+-- | Distance in meters from the surface at the specified latitude to the -- axis of the Earth straight down. Also known as the radius of -- curvature in the prime vertical, and often denoted @N@.-normal :: (Ellipsoid e) => e -> Angle Double -> Length Double-normal e lat = majorRadius e / sqrt (_1 - eccentricity2 e * sin lat ^ pos2)+normal :: (Ellipsoid e) => e -> Double -> Double+normal e lat = majorRadius e / sqrt (1 - eccentricity2 e * sin lat ^ _2)   -- | Radius of the circle of latitude: the distance from a point--- at that latitude to the axis of the Earth.-latitudeRadius :: (Ellipsoid e) => e -> Angle Double -> Length Double+-- at that latitude to the axis of the Earth, in meters.+latitudeRadius :: (Ellipsoid e) => e -> Double -> Double latitudeRadius e lat = normal e lat * cos lat  --- | Radius of curvature in the meridian at the specified latitude.+-- | Radius of curvature in the meridian at the specified latitude, in meters -- Often denoted @M@.-meridianRadius :: (Ellipsoid e) => e -> Angle Double -> Length Double+meridianRadius :: (Ellipsoid e) => e -> Double -> Double meridianRadius e lat =-   majorRadius e * (_1 - eccentricity2 e)-   / sqrt ((_1 - eccentricity2 e * sin lat ^ pos2) ^ pos3)+   majorRadius e * (1 - eccentricity2 e)+   / sqrt ((1 - eccentricity2 e * sin lat ^ _2) ^ _3)  --- | Radius of curvature of the ellipsoid perpendicular to the meridian at the specified latitude.-primeVerticalRadius :: (Ellipsoid e) => e -> Angle Double -> Length Double+-- | Radius of curvature of the ellipsoid perpendicular to the meridian at the specified latitude, in meters.+primeVerticalRadius :: (Ellipsoid e) => e -> Double -> Double primeVerticalRadius e lat =-   majorRadius e / sqrt (_1 - eccentricity2 e * sin lat ^ pos2)+   majorRadius e / sqrt (1 - eccentricity2 e * sin lat ^ _2)   -- | The isometric latitude. The isometric latitude is conventionally denoted by ψ@@ -275,7 +293,7 @@ -- Mercator projection. The name "isometric" arises from the fact that at any point -- on the ellipsoid equal increments of ψ and longitude λ give rise to equal distance -- displacements along the meridians and parallels respectively.-isometricLatitude :: (Ellipsoid e) => e -> Angle Double -> Angle Double+isometricLatitude :: (Ellipsoid e) => e -> Double -> Double isometricLatitude ellipse lat = atanh sinLat - e * atanh (e * sinLat)    where       sinLat = sin lat
src/Geodetics/Geodetic.hs view
@@ -1,5 +1,5 @@ module Geodetics.Geodetic (-   -- ** Geodetic Coordinates+   -- * Geodetic Coordinates    Geodetic (..),    readGroundPosition,    toLocal,@@ -10,25 +10,21 @@    groundDistance,    properAngle,    showAngle,-   -- ** Earth Centred Earth Fixed Coordinates+   -- * Earth Centred Earth Fixed Coordinates    ECEF,    geoToEarth,    earthToGeo,-   -- ** Re-exported for convenience+   -- * Re-exported for convenience    WGS84 (..) ) where   import Data.Char (chr)-import Data.Function import Data.Maybe-import Data.Monoid import Geodetics.Altitude import Geodetics.Ellipsoids import Geodetics.LatLongParser-import Numeric.Units.Dimensional.Prelude hiding ((.)) import Text.ParserCombinators.ReadP-import qualified Prelude as P  -- | Defines a three-D position on or around the Earth using latitude, -- longitude and altitude with respect to a specified ellipsoid, with@@ -60,8 +56,8 @@ -- the same to within a given tolerance then use "geometricDistance" -- (or its squared variant to avoid an extra @sqrt@ operation). data Geodetic e = Geodetic {-   latitude, longitude :: Angle Double,-   geoAlt :: Length Double,+   latitude, longitude :: Double,  -- ^ In radians.+   geoAlt :: Double,  -- ^ In meters.    ellipsoid :: e } @@ -70,8 +66,7 @@       showAngle (abs $ latitude g),  " ", letter "SN" (latitude g),  ", ",       showAngle (abs $ longitude g), " ", letter "WE" (longitude g), ", ",       show (altitude g), " ", show (ellipsoid g)]-      where letter s n = [s !! (if n < _0 then 0 else 1)]-+      where letter s n = [s !! (if n < 0 then 0 else 1)]   -- | Read the latitude and longitude of a ground position and@@ -94,25 +89,24 @@ readGroundPosition e str =    case map fst $ filter (null . snd) $ readP_to_S latLong str of       [] -> Nothing-      (lat,long) : _ -> Just $ groundPosition $ Geodetic (lat *~ degree) (long *~ degree) undefined e+      (lat,long) : _ -> Just $ groundPosition $ Geodetic (lat * degree) (long * degree) undefined e   -- | Show an angle as degrees, minutes and seconds to two decimal places.-showAngle :: Angle Double -> String+showAngle :: Double -> String showAngle a-   | isNaN a1       = "NaN"  -- Not a Nangle-   | isInfinite a1  = sgn ++ "Infinity"+   | isNaN a        = "NaN"  -- Not a Nangle+   | isInfinite a   = sgn ++ "Infinity"    | otherwise      = concat [sgn, show d, [chr 0xB0, ' '],                               show m, "\8242 ",                               show s, ".", dstr, "\8243" ]    where-      a1 = a /~ one-      sgn = if a < _0 then "-" else ""+      sgn = if a < 0 then "-" else ""       centisecs :: Integer-      centisecs = P.abs $ P.round $ (a /~ degree) P.* 360000  -- hundredths of arcsec per degree.-      (d, m1) = centisecs `P.divMod` 360000-      (m, s1) = m1 `P.divMod` 6000   -- hundredths of arcsec per arcmin-      (s, ds) = s1 `P.divMod` 100+      centisecs = abs $ round $ (a / (arcsecond / 100))+      (d, m1) = centisecs `divMod` 360000+      (m, s1) = m1 `divMod` 6000   -- hundredths of arcsec per arcmin+      (s, ds) = s1 `divMod` 100       dstr = reverse $ take 2 $ reverse (show ds) ++ "00" -- Decimal fraction with zero padding.  @@ -128,8 +122,8 @@ antipode g = Geodetic lat long (geoAlt g) (ellipsoid g)    where       lat = negate $ latitude g-      long' = longitude g - 180 *~ degree-      long | long' < _0  = long' + 360 *~ degree+      long' = longitude g - 180 * degree+      long | long' < 0  = long' + 360 * degree            | otherwise  = long'  @@ -140,7 +134,7 @@ geoToEarth geo = (       (n + h) * coslat * coslong,       (n + h) * coslat * sinlong,-      (n * (_1 - eccentricity2 e) + h) * sinlat)+      (n * (1 - eccentricity2 e) + h) * sinlat)    where       n = normal e $ latitude geo       e = ellipsoid geo@@ -156,26 +150,27 @@ -- -- Uses the closed form solution of H. Vermeille: Direct -- transformation from geocentric coordinates to geodetic coordinates.--- Journal of Geodesy Volume 76, Number 8 (2002), 451-454-earthToGeo :: (Ellipsoid e) => e -> ECEF -> (Angle Double, Angle Double, Length Double)-earthToGeo e (x,y,z) = (phi, atan2 y x, sqrt (l ^ pos2 + p2) - norm)+-- Journal of Geodesy Volume 76, Number 8 (2002), 451-454. Result is in the form+-- @(latitude, longitude, altitude)@.+earthToGeo :: (Ellipsoid e) => e -> ECEF -> (Double, Double, Double)+earthToGeo e (x,y,z) = (phi, atan2 y x, sqrt (l ^ _2 + p2) - norm)    where       -- Naming: numeric suffix inicates power. Hence x2 = x * x, x3 = x2 * x, etc.-      p2 = x ^ pos2 + y ^ pos2+      p2 = x * x + y * y       a = majorRadius e-      a2 = a ^ pos2+      a2 = a * a       e2 = eccentricity2 e-      e4 = e2 ^ pos2-      zeta = (_1-e2) * (z ^ pos2 / a2)-      rho = (p2 / a2 + zeta - e4) / _6-      rho2 = rho ^ pos2+      e4 = e2 * e2+      zeta = (1-e2) * (z * z / a2)+      rho = (p2 / a2 + zeta - e4) / 6+      rho2 = rho * rho       rho3 = rho * rho2-      s = e4 * zeta * p2 / (_4 * a2)-      t = cbrt (s + rho3 + sqrt (s * (s + _2 * rho3)))+      s = e4 * zeta * p2 / (4 * a2)+      t = (s + rho3 + sqrt (s * (s + 2 * rho3))) ** (1/3) -- Cube root       u = rho + t + rho2 / t-      v = sqrt (u ^ pos2 + e4 * zeta)-      w = e2 * (u + v - zeta) / (_2 * v)-      kappa = _1 + e2 * (sqrt (u + v + w ^ pos2) + w) / (u + v)+      v = sqrt (u * u + e4 * zeta)+      w = e2 * (u + v - zeta) / (2 * v)+      kappa = 1 + e2 * (sqrt (u + v + w * w) + w) / (u + v)       phi = atan (kappa * z / sqrt p2)       norm = normal e phi       l = z + e2 * norm * sin phi@@ -203,12 +198,12 @@ -- points. They must be on the same ellipsoid. -- Note that this is not the geodetic distance taken by following -- the curvature of the earth.-geometricalDistance :: (Ellipsoid e) => Geodetic e -> Geodetic e -> Length Double+geometricalDistance :: (Ellipsoid e) => Geodetic e -> Geodetic e -> Double geometricalDistance g1 g2 = sqrt $ geometricalDistanceSq g1 g2 --- | The square of the absolute distance. Comes out as "Area" type of course.-geometricalDistanceSq :: (Ellipsoid e) => Geodetic e -> Geodetic e -> Area Double-geometricalDistanceSq g1 g2 = (x1-x2) ^ pos2 + (y1-y2) ^ pos2 + (z1-z2) ^ pos2+-- | The square of the absolute distance.+geometricalDistanceSq :: (Ellipsoid e) => Geodetic e -> Geodetic e -> Double+geometricalDistanceSq g1 g2 = (x1-x2) ^ _2 + (y1-y2) ^ _2 + (z1-z2) ^ _2    where       (x1,y1,z1) = geoToEarth g1       (x2,y2,z2) = geoToEarth g2@@ -229,23 +224,19 @@ -- equations\". T. Vincenty. Survey Review XXII 176, April -- 1975. <http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf> groundDistance :: (Ellipsoid e) => Geodetic e -> Geodetic e ->-                  Maybe (Length Double, Dimensionless Double, Dimensionless Double)+                  Maybe (Double, Double, Double) groundDistance p1 p2 = do      (_, (lambda, (cos2Alpha, delta, sinDelta, cosDelta, cos2DeltaM))) <--       listToMaybe $ dropWhile converging $ take 100 $ zip lambdas $ tail lambdas+       listToMaybe $ dropWhile converging $ take 100 $ zip lambdas $ drop 1 lambdas      let-       uSq = cos2Alpha * (a^pos2 - b^pos2) / b^pos2-       bigA = _1 + uSq/(16384*~one) * ((4096*~one) + uSq *-                                      (((-768)*~one) + uSq * ((320*~one)-                                                            - (175*~one)*uSq)))-       bigB = uSq/(1024*~one) * ((256*~one) +-                                 uSq * (((-128)*~one) +-                                        uSq * ((74*~one) - (47*~one)*uSq)))+       uSq = cos2Alpha * (a^ _2 - b^ _2) / b^ _2+       bigA = 1 + uSq/16384 * (4096 + uSq * ((-768) + uSq * ((320 - 175*uSq))))+       bigB =     uSq/1024  * (256  + uSq * ((-128) + uSq * ((74 -  47* uSq))))        deltaDelta =          bigB * sinDelta * (cos2DeltaM +-                             bigB/_4 * (cosDelta * (_2 * cos2DeltaM^pos2 - _1)-                                        - bigB/_6 * cos2DeltaM * (_4 * sinDelta^pos2 - _3)-                                          * (_4 * cos2DeltaM - _3)))+                             bigB/4 * (cosDelta * (2 * cos2DeltaM^ _2 - 1)+                                       - bigB/6 * cos2DeltaM * (4 * sinDelta^ _2 - 3)+                                          * (4 * cos2DeltaM - 3)))        s = b * bigA * (delta - deltaDelta)        alpha1 = atan2(cosU2 * sin lambda) (cosU1 * sinU2 - sinU1 * cosU2 * cos lambda)        alpha2 = atan2(cosU1 * sin lambda) (cosU1 * sinU2 * cos lambda - sinU1 * cosU2)@@ -255,8 +246,8 @@     a = majorRadius $ ellipsoid p1     b = minorRadius $ ellipsoid p1     l = abs $ longitude p1 - longitude p2-    u1 = atan ((_1-f) * tan (latitude p1))-    u2 = atan ((_1-f) * tan (latitude p2))+    u1 = atan ((1-f) * tan (latitude p1))+    u2 = atan ((1-f) * tan (latitude p2))     sinU1 = sin u1     cosU1 = cos u1     sinU2 = sin u2@@ -266,25 +257,25 @@       where         sinLambda = sin lambda         cosLambda = cos lambda-        sinDelta = sqrt((cosU2 * sinLambda) ^ pos2 +-                        (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) ^ pos2)+        sinDelta = sqrt((cosU2 * sinLambda) ^ _2 ++                        (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) ^ _2)         cosDelta = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda         delta = atan2 sinDelta cosDelta-        sinAlpha = if sinDelta == _0 then _0 else cosU1 * cosU2 * sinLambda / sinDelta-        cos2Alpha = _1 - sinAlpha ^ pos2-        cos2DeltaM = if cos2Alpha == _0-                     then _0-                     else cosDelta - _2 * sinU1 * sinU2 / cos2Alpha-        c = f/(16 *~ one) * cos2Alpha * (_4 + f * (_4 - _3 * cos2Alpha))-        lambda1 = l + (_1-c) * f * sinAlpha+        sinAlpha = if sinDelta == 0 then 0 else cosU1 * cosU2 * sinLambda / sinDelta+        cos2Alpha = 1 - sinAlpha ^ _2+        cos2DeltaM = if cos2Alpha == 0+                     then 0+                     else cosDelta - 2 * sinU1 * sinU2 / cos2Alpha+        c = (f/16) * cos2Alpha * (4 + f * (4 - 3 * cos2Alpha))+        lambda1 = l + (1-c) * f * sinAlpha                   * (delta + c * sinDelta-                     * (cos2DeltaM + c * cosDelta *(_2 * cos2DeltaM ^ pos2 - _1)))+                     * (cos2DeltaM + c * cosDelta *(2 * cos2DeltaM ^ _2 - 1)))     lambdas = iterate (nextLambda . fst) (l, undefined)-    converging ((l1,_),(l2,_)) = abs (l1 - l2) > (1e-14 *~ one)+    converging ((l1,_),(l2,_)) = abs (l1 - l2) > 1e-14   -- | Add or subtract multiples of 2*pi so that for all @t@, @-pi < properAngle t < pi@.-properAngle :: Angle Double -> Angle Double+properAngle :: Double -> Double properAngle t    | r1 <= negate pi    = r1 + pi2    | r1 > pi            = r1 - pi2@@ -292,6 +283,6 @@    where       pf :: Double -> (Int, Double)       pf = properFraction  -- Shut up GHC warning about defaulting to Integer.-      (_,r) = pf (t/pi2 /~ one)-      r1 = (r *~ one) * pi2-      pi2 = pi * _2+      (_,r) = pf (t/pi2)+      r1 = r * pi2+      pi2 = pi * 2
src/Geodetics/Grid.hs view
@@ -1,11 +1,11 @@-{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}+{-# LANGUAGE FunctionalDependencies #-}  module Geodetics.Grid (-   -- ** Grid types+   -- * Grid types    GridClass (..),    GridPoint (..),    GridOffset (..),-   -- ** Grid operations+   -- * Grid operations    polarOffset,    offsetScale,    offsetNegate,@@ -14,25 +14,21 @@    offsetDistanceSq,    offsetBearing,    gridOffset,-   -- ** Unsafe conversion+   -- * Unsafe conversion    unsafeGridCoerce,-   -- ** Utility functions for grid references+   -- * Utility functions for grid references    fromGridDigits,    toGridDigits ) where  import Data.Char-import Data.Function-import Data.Monoid (Monoid)-import Data.Semigroup (Semigroup, (<>)) import Geodetics.Altitude+import Geodetics.Ellipsoids import Geodetics.Geodetic-import Numeric.Units.Dimensional.Prelude hiding ((.))-import qualified Prelude as P  -- | A Grid is a two-dimensional projection of the ellipsoid onto a plane. Any given type of grid can -- usually be instantiated with parameters such as a tangential point or line, and these parameters--- will include the terrestrial reference frame ("Ellipsoid" in this library) used as a foundation. +-- will include the terrestrial reference frame ("Ellipsoid" in this library) used as a foundation. -- Hence conversion from a geodetic to a grid point requires the \"basis\" for the grid in question, -- and grid points carry that basis with them because without it there is no defined relationship -- between the grid points and terrestrial positions.@@ -42,17 +38,17 @@    gridEllipsoid :: r -> e  --- | A point on the specified grid. +-- | A point on the specified grid. data GridPoint r = GridPoint {-   eastings, northings, altGP :: Length Double,+   eastings, northings, altGP :: Double,    gridBasis :: r } deriving (Show)   instance Eq (GridPoint r) where-   p1 == p2  = -      eastings p1 == eastings p2 && -      northings p1 == northings p2 && +   p1 == p2  =+      eastings p1 == eastings p2 &&+      northings p1 == northings p2 &&       altGP p1 == altGP p2  instance HasAltitude (GridPoint g) where@@ -60,14 +56,13 @@    setAltitude h gp = gp{altGP = h}  ---- | A vector relative to a point on a grid.+-- | A vector relative to a point on a grid. All distances are in meters. -- Operations that use offsets will only give -- meaningful results if all the points come from the same grid.--- +-- -- The monoid instance is the sum of offsets. data GridOffset = GridOffset {-   deltaEast, deltaNorth, deltaAltitude :: Length Double+   deltaEast, deltaNorth, deltaAltitude :: Double } deriving (Eq, Show)  instance Semigroup GridOffset where@@ -76,20 +71,20 @@                         (deltaAltitude g1 + deltaAltitude g2)  instance Monoid GridOffset where-   mempty = GridOffset _0 _0 _0+   mempty = GridOffset 0 0 0    mappend = (<>) --- | An offset defined by a distance and a bearing to the right of North.+-- | An offset defined by a distance (m) and a bearing (radians) to the right of North. -- -- There is no elevation parameter because we are using a plane to approximate an ellipsoid, -- so elevation would not provide a useful result.  If you want to work with elevations -- then "rayPath" will give meaningful results.-polarOffset :: Length Double -> Angle Double -> GridOffset-polarOffset r d = GridOffset (r * sin d) (r * cos d) _0+polarOffset :: Double -> Double -> GridOffset+polarOffset r d = GridOffset (r * sin d) (r * cos d) 0   -- | Scale an offset by a scalar.-offsetScale :: Dimensionless Double -> GridOffset -> GridOffset+offsetScale :: Double -> GridOffset -> GridOffset offsetScale s off = GridOffset (deltaEast off * s)                                (deltaNorth off * s)                                (deltaAltitude off * s)@@ -103,36 +98,36 @@  -- Add an offset on to a point to get another point. applyOffset :: GridOffset -> GridPoint g -> GridPoint g-applyOffset off p = GridPoint (eastings p + deltaEast off) +applyOffset off p = GridPoint (eastings p + deltaEast off)                            (northings p + deltaNorth off)                            (altitude p + deltaAltitude off)                            (gridBasis p)   -- | The distance represented by an offset.-offsetDistance :: GridOffset -> Length Double+offsetDistance :: GridOffset -> Double offsetDistance = sqrt . offsetDistanceSq   -- | The square of the distance represented by an offset.-offsetDistanceSq :: GridOffset -> Area Double-offsetDistanceSq off = -   deltaEast off ^ pos2 + deltaNorth off ^ pos2 + deltaAltitude off ^ pos2+offsetDistanceSq :: GridOffset -> Double+offsetDistanceSq off =+   deltaEast off ^ _2 + deltaNorth off ^ _2 + deltaAltitude off ^ _2 -              + -- | The direction represented by an offset, as bearing to the right of North.-offsetBearing :: GridOffset -> Angle Double+offsetBearing :: GridOffset -> Double offsetBearing off = atan2 (deltaEast off) (deltaNorth off)  --- | The offset required to move from p1 to p2.             +-- | The offset required to move from p1 to p2. gridOffset :: GridPoint g -> GridPoint g -> GridOffset gridOffset p1 p2 = GridOffset (eastings p2 - eastings p1)                               (northings p2 - northings p1)                               (altitude p2 - altitude p1)  --- | Coerce a grid point of one type into a grid point of a different type, +-- | Coerce a grid point of one type into a grid point of a different type, -- but with the same easting, northing and altitude. This is unsafe because it -- will produce a different position unless the two grids are actually equal. --@@ -148,41 +143,41 @@ -- in units of one tenth of the grid square, the second one hundredth, and so on. -- The first result is the lower limit of the result, and the second is the size -- of the specified offset.--- So for instance @fromGridDigits (100 *~ kilo meter) "237"@ will return+-- So for instance @fromGridDigits (100 * kilometer) "237"@ will return -- -- > Just (23700 meters, 100 meters) -- -- If there are any non-digits in the string then the function returns @Nothing@.-fromGridDigits :: Length Double -> String -> Maybe (Length Double, Length Double)+fromGridDigits :: Double -> String -> Maybe (Double, Double) fromGridDigits sq ds = if all isDigit ds then Just (d, p) else Nothing    where-      n = length ds-      d = sum $ zipWith (*) -         (map ((*~ one) . fromIntegral . digitToInt) ds) -         (tail $ iterate (/ (10 *~ one)) sq)-      p = sq / ((10 *~ one) ** (fromIntegral n *~ one))-      +      n :: Integer+      n = fromIntegral $ length ds+      d = sum $ zipWith (*)+         (map (fromIntegral . digitToInt) ds)+         (drop 1 $ iterate (/ 10) sq)+      p = sq / fromIntegral ((10 :: Int) ^ n)+ -- | Convert a distance into a digit string suitable for printing as part -- of a grid reference. The result is the nearest position to the specified -- number of digits, expressed as an integer count of squares and a string of digits. -- If any arguments are invalid then @Nothing@ is returned. toGridDigits ::-   Length Double    -- ^ Size of enclosing grid square. Must be at least 1 km.+   Double           -- ^ Size of enclosing grid square. Must be at least 1000m.    -> Int           -- ^ Number of digits to return. Must be positive.-   -> Length Double -- ^ Offset to convert into grid.+   -> Double        -- ^ Offset to convert into grid (m).    -> Maybe (Integer, String) toGridDigits sq n d =-   if sq < (1 *~ kilo meter) || n < 0 || d < _0 +   if sq < 1000 || n < 0 || d < 0    then Nothing    else       Just (sqs, pad)    where       p :: Integer-      p = 10 P.^ n-      unit :: Length Double-      unit = sq / (fromIntegral p *~ one)-      u = round ((d / unit) /~ one)+      p = 10 ^ n+      unit :: Double+      unit = sq / fromIntegral p+      u = round (d / unit)       (sqs, d1) = u `divMod` p       s = show d1-      pad = if n == 0 then "" else replicate (n P.- length s) '0' ++ s-      +      pad = if n == 0 then "" else replicate (n - length s) '0' ++ s
src/Geodetics/LatLongParser.hs view
@@ -1,9 +1,10 @@ -- | The default reader for Geodetic ground positions is flexible but slow. If you are -- going to read positions in a known format and performance matters then use one of -- the more specialised parsers here.+--+-- All angles are returned in degrees.  module Geodetics.LatLongParser (-    degreesMinutesSeconds,    degreesMinutesSecondsUnits,    degreesDecimalMinutes,@@ -78,7 +79,8 @@    return $ d + ms  --- | Parse an unsigned angle written using degrees, minutes and seconds with units (° ' \"). At least one component must be specified.+-- | Parse an unsigned angle written using degrees, minutes and seconds with units (° ' \").+-- At least one component must be specified. degreesMinutesSecondsUnits :: ReadP Double degreesMinutesSecondsUnits = do    (s, a) <- gather $ do
src/Geodetics/Path.hs view
@@ -6,12 +6,10 @@ import Control.Monad import Geodetics.Ellipsoids import Geodetics.Geodetic-import Numeric.Units.Dimensional.Prelude-import Prelude ()  --- | Lower and upper exclusive bounds within which a path is valid. -type PathValidity = (Length Double, Length Double)+-- | Lower and upper exclusive distance bounds within which a path is valid. +type PathValidity = (Double, Double)  -- | A path is a parametric function of distance along the path. The result is the -- position, and the direction of the path at that point as heading and elevation angles.@@ -24,18 +22,19 @@ -- Outside its validity the path function may -- return anything or bottom. data Path e = Path {-      pathFunc :: Length Double -> (Geodetic e, Angle Double, Angle Double),+      pathFunc :: Double -> (Geodetic e, Double, Double),+         -- ^ Takes a length and returns a position, and direction as heading and elevation angles.       pathValidity :: PathValidity    }     -- | Convenience value for paths that are valid for all distances. alwaysValid :: PathValidity alwaysValid = (negate inf, inf) where-   inf = (1.0 *~ meter) / (0 *~ one)  -- Assumes IEE arithmetic.+   inf = 1.0 / 0  -- Assumes IEE arithmetic.   -- | True if the path is valid at that distance.-pathValidAt :: Path e -> Length Double -> Bool+pathValidAt :: Path e -> Double -> Bool pathValidAt path d = d > x1 && d < x2    where (x1,x2) = pathValidity path @@ -50,9 +49,9 @@ bisect ::     Path e     -> (Geodetic e -> Ordering)        -- ^ Evaluation function.-   -> Length Double                   -- ^ Required accuracy in terms of distance along the path.-   -> Length Double -> Length Double  -- ^ Initial bounds.-   -> Maybe (Length Double)+   -> Double                          -- ^ Required accuracy in terms of distance along the path.+   -> Double -> Double                -- ^ Initial bounds.+   -> Maybe Double bisect path f t b1 b2 = do       guard $ pathValidAt path b1       guard $ pathValidAt path b2@@ -65,7 +64,7 @@       hasRoot (v1, v2) = snd v1 <= EQ && EQ <= snd v2       sortPair (v1, v2) = if snd v1 <= snd v2 then (v1, v2) else (v2, v1)       bisect1 ((d1, r1), (d2, r2)) =-         let d3 = (d1 + d2) / _2+         let d3 = (d1 + d2) / 2              r3 = f' d3              c1 = ((d1, r1), (d3, r3))              c2 = ((d3, r3), (d2, r2))@@ -85,16 +84,16 @@ -- -- If either estimate departs from its path validity then @Nothing@ is returned. intersect :: (Ellipsoid e) =>-   Length Double -> Length Double     -- ^ Starting estimates.-   -> Length Double                   -- ^ Required accuracy.+   Double -> Double                   -- ^ Starting estimates.+   -> Double                          -- ^ Required accuracy.    -> Int                             -- ^ Iteration limit. Returns @Nothing@ if this is reached.      -> Path e -> Path e                -- ^ Paths to intersect.-   -> Maybe (Length Double, Length Double)+   -> Maybe (Double, Double) intersect d1 d2 accuracy n path1 path2    | not $ pathValidAt path1 d1     = Nothing    | not $ pathValidAt path2 d2     = Nothing    | n <= 0                         = Nothing-   | mag < (1e-15 *~ one)           = Nothing+   | mag < 1e-15                    = Nothing    | mag3 (nv1 `cross3` nv2) * r <= accuracy = Just (d1, d2)        -- Assumes that sin (accuracy/r) == accuracy/r    | otherwise = @@ -104,8 +103,8 @@    where       (pt1, h1, _) = pathFunc path1 d1       (pt2, h2, _) = pathFunc path2 d2-      vectors :: Angle Double -> Angle Double -> Angle Double -                 -> (Vec3 (Dimensionless Double), Vec3 (Dimensionless Double))+      vectors :: Double -> Double -> Double +                 -> (Vec3 Double, Vec3 Double)       vectors lat lon b = (           -- Unit vector of normal to surface at (lat,lon)          (cosLat*cosLon, cosLat*sinLon, sinLat),@@ -125,7 +124,7 @@       (nv2, gc2) = vectors (latitude pt2) (longitude pt2) h2       nv3 = gc1 `cross3` gc2         -- Intersection of the great circles       mag = mag3 nv3-      nv3a = scale3 nv3 (_1 / mag)   -- Scale to unit. See outer function for case when mag3 == 0+      nv3a = scale3 nv3 (1 / mag)   -- Scale to unit. See outer function for case when mag3 == 0       nv3b = negate3 nv3a            -- Antipodal result. Take the closest.       -- Find "nearest" intersection, defined as smaller of sum of distances to current points.       d1a = gcDist gc1 nv1 nv3a * r@@ -135,7 +134,7 @@       -- Signed angle between v1 and v2,        gcDist norm v1 v2 =           let c = v1 `cross3` v2 -         in (if c `dot3` norm < _0 then negate else id) $ atan2 (mag3 c) (v1 `dot3` v2) +         in (if c `dot3` norm < 0 then negate else id) $ atan2 (mag3 c) (v1 `dot3` v2)        r = majorRadius $ ellipsoid pt1            {- Note on derivation of "intersect"@@ -170,8 +169,8 @@ -- | A ray from a point heading in a straight line in 3 dimensions.  rayPath :: (Ellipsoid e) =>     Geodetic e          -- ^ Start point.-   -> Angle Double     -- ^ Bearing.-   -> Angle Double     -- ^ Elevation.+   -> Double           -- ^ Bearing.+   -> Double           -- ^ Elevation.    -> Path e rayPath pt1 bearing elevation = Path ray alwaysValid    where@@ -181,14 +180,14 @@             (lat,long,alt) = earthToGeo (ellipsoid pt1) pt2'  -- Geodetic of result point.             (dE,dN,dU) = transform3 (trans3 $ ecefMatrix lat long) delta  -- Direction of ray at result point.             elevation2 = asin dU-            bearing2 = if dE == _0 && dN == _0 then bearing else atan2 dE dN  -- Allow for vertical elevation.+            bearing2 = if dE == 0 && dN == 0 then bearing else atan2 dE dN  -- Allow for vertical elevation.                    ecefMatrix lat long =   -- Transform matrix for vectors from (East, North, Up) to (X,Y,Z).          ((negate sinLong, negate cosLong*sinLat, cosLong*cosLat),               --    East X      North X               Up X           (       cosLong, negate sinLong*sinLat, sinLong*cosLat),               --    East Y      North Y               Up Y-          (  _0           ,      cosLat         , sinLat))+          (             0,      cosLat         , sinLat))               --    East Z      North Z               Up Z          where             sinLong = sin long@@ -215,24 +214,24 @@ -- the approximation is accurate to within a few meters over 1000km. rhumbPath :: (Ellipsoid e) =>    Geodetic e            -- ^ Start point.-   -> Angle Double       -- ^ Course.+   -> Double             -- ^ Course.    -> Path e rhumbPath pt course = Path rhumb validity    where-      rhumb distance = (Geodetic lat (properAngle lon) _0 (ellipsoid pt), course, _0)+      rhumb distance = (Geodetic lat (properAngle lon) 0 (ellipsoid pt), course, 0)          where             lat' = lat0 + distance * cosC / m0   -- Kaplan Eq 13.-            lat = lat0 + (m0 / (a*(_1-e2))) * ((_1-_3*e2/_4)*(lat'-lat0)-                                              + (_3*e2/_8)*(sin (_2*lat') - sin (_2*lat0)))-            lon | abs cosC > 1e-7 *~ one +            lat = lat0 + (m0 / (a*(1-e2))) * ((1-3*e2/4)*(lat'-lat0)+                                            + (3*e2/8)*(sin (2*lat') - sin (2*lat0)))+            lon | abs cosC > 1e-7                      = lon0 + tanC * (q lat - q0)     -- Kaplan Eq 16.                 | otherwise-                     = lon0 + distance * sinC / latitudeRadius (ellipsoid pt) ((lat0 + lat')/_2)+                     = lon0 + distance * sinC / latitudeRadius (ellipsoid pt) ((lat0 + lat')/2)       validity-         | cosC > _0  = ((negate pi/_2 - latitude pt) * b / cosC, (pi/_2 - latitude pt) * b / cosC)-         | otherwise  = ((pi/_2 - latitude pt) * b / cosC, (negate pi/_2 - latitude pt) * b / cosC)+         | cosC > 0  = ((negate pi/2 - latitude pt) * b / cosC, (pi/2 - latitude pt) * b / cosC)+         | otherwise  = ((pi/2 - latitude pt) * b / cosC, (negate pi/2 - latitude pt) * b / cosC)       q0 = q lat0-      q phi = log (tan (pi/_4+phi/_2)) + e * log ((_1-eSinPhi)/(_1+eSinPhi)) / _2+      q phi = log (tan (pi/4+phi/2)) + e * log ((1-eSinPhi)/(1+eSinPhi)) / 2          where                                -- Factor out expression from Eq 16 of Kaplan             eSinPhi = e * sin phi       sinC = sin course@@ -255,11 +254,11 @@    -> Path e latitudePath pt = Path line alwaysValid    where-      line distance = (pt2, pi/_2, _0) +      line distance = (pt2, pi/2, 0)          where             pt2 = Geodetic                 (latitude pt) (longitude pt + distance / r)-               _0 (ellipsoid pt)+               0 (ellipsoid pt)       r = latitudeRadius (ellipsoid pt) (latitude pt)  @@ -270,4 +269,4 @@ longitudePath :: (Ellipsoid e) =>    Geodetic e    -- ^ Start point.    -> Path e-longitudePath pt = rhumbPath pt _0+longitudePath pt = rhumbPath pt 0
src/Geodetics/Stereographic.hs view
@@ -10,30 +10,28 @@    mkGridStereo ) where - import Geodetics.Ellipsoids import Geodetics.Geodetic import Geodetics.Grid-import Numeric.Units.Dimensional.Prelude-import Prelude () +import qualified Data.Stream as Stream  -- | A stereographic projection with its origin at an arbitrary point on Earth, other than the poles. data GridStereo e = GridStereo {       gridTangent :: Geodetic e, -- ^ Point where the plane of projection touches the ellipsoid. Often known as the Natural Origin.       gridOrigin :: GridOffset,  -- ^ Grid position of the tangent point. Often known as the False Origin.-      gridScale :: Dimensionless Double, -- ^ Scaling factor that balances the distortion between the center and the edges. +      gridScale :: Double, -- ^ Scaling factor that balances the distortion between the center and the edges.                                          -- Should be slightly less than unity.              -- Memoised parameters derived from the tangent point.-      gridR :: Length Double,-      gridN, gridC, gridSin, gridCos :: Dimensionless Double,-      gridLatC :: Angle Double,-      gridG, gridH :: Length Double+      gridR :: Double,+      gridN, gridC, gridSin, gridCos :: Double,+      gridLatC :: Double,+      gridG, gridH :: Double    } deriving (Show)     -- | Create a stereographic projection. The tangency point must not be one of the poles.  -mkGridStereo :: (Ellipsoid e) => Geodetic e -> GridOffset -> Dimensionless Double -> GridStereo e+mkGridStereo :: (Ellipsoid e) => Geodetic e -> GridOffset -> Double -> GridStereo e mkGridStereo tangent origin scale = GridStereo {       gridTangent = tangent,       gridOrigin = origin,@@ -42,7 +40,7 @@       gridN = n,       gridC = c,       gridSin = sinLatC1,-      gridCos = sqrt $ _1 - sinLatC1 * sinLatC1,+      gridCos = sqrt $ 1 - sinLatC1 * sinLatC1,       gridLatC = asin sinLatC1,       gridG = g,       gridH = h@@ -51,43 +49,43 @@       -- The reference seems to use χO to refer to two slightly different values.        -- Here these will be called LatC0 and LatC1.       ellipse = ellipsoid tangent-      op :: Num a => Quantity d a -> Quantity d a    -- Values of longitude, tangent longitude, E and N-      op = if latitude tangent < _0 then negate else id  -- must be negated in the southern hemisphere.+      op :: Num a => a -> a    -- Values of longitude, tangent longitude, E and N+      op = if latitude tangent < 0 then negate else id  -- must be negated in the southern hemisphere.       lat0 = op $ latitude tangent       sinLat0 = sin lat0       e2 = eccentricity2 ellipse       e = sqrt e2       r = sqrt $ meridianRadius ellipse lat0 * primeVerticalRadius ellipse lat0-      n = sqrt $ _1 + ((e2 * cos lat0 ^ pos4)/(_1 - e2))-      s1 = (_1 + sinLat0) / (_1 - sinLat0)-      s2 = (_1 - e * sinLat0) / (_1 + e * sinLat0)+      n = sqrt $ 1 + ((e2 * cos lat0 ^ _4)/(1 - e2))+      s1 = (1 + sinLat0) / (1 - sinLat0)+      s2 = (1 - e * sinLat0) / (1 + e * sinLat0)       w1 = (s1 * s2 ** e) ** n-      sinLatC0 = (w1 - _1)/(w1 + _1)-      c = ((n + sin lat0) * (_1 - sinLatC0)) / ((n - sin lat0) * (_1 + sinLatC0))+      sinLatC0 = (w1 - 1)/(w1 + 1)+      c = ((n + sin lat0) * (1 - sinLatC0)) / ((n - sin lat0) * (1 + sinLatC0))       w2 = c * w1-      sinLatC1 = (w2 - _1)/(w2 + _1)-      g = _2 * r * scale * tan (pi/_4 - latC1/_2)-      h = _4 * r * scale * tan latC1 + g+      sinLatC1 = (w2 - 1)/(w2 + 1)+      g = 2 * r * scale * tan (pi/4 - latC1/2)+      h = 4 * r * scale * tan latC1 + g       latC1 = asin sinLatC1         instance (Ellipsoid e) => GridClass (GridStereo e) e where    toGrid grid geo = applyOffset (gridOrigin grid) $ GridPoint east north (geoAlt geo) grid       where-         op :: Num a => Quantity d a -> Quantity d a    -- Values of longitude, tangent longitude, E and N-         op = if latitude (gridTangent grid) < _0 then negate else id  -- must be negated in the southern hemisphere.-         sinLatC = (w - _1)/(w + _1)-         cosLatC = sqrt $ _1 - sinLatC * sinLatC+         op :: Num a => a -> a    -- Values of longitude, tangent longitude, E and N+         op = if latitude (gridTangent grid) < 0 then negate else id  -- must be negated in the southern hemisphere.+         sinLatC = (w - 1)/(w + 1)+         cosLatC = sqrt $ 1 - sinLatC * sinLatC          longC = gridN grid * (op (longitude geo) - long0) + long0          w = gridC grid * (sA * sB ** e) ** gridN grid-         sA = (_1+sinLat) / (_1 - sinLat)-         sB = (_1 - e*sinLat) / (_1 + e*sinLat)+         sA = (1+sinLat) / (1 - sinLat)+         sB = (1 - e*sinLat) / (1 + e*sinLat)          sinLat = sin $ op $ latitude geo          e = sqrt $ eccentricity2 $ ellipsoid geo          long0 = op $ longitude $ gridTangent grid-         b = _1 + sinLatC * gridSin grid + cosLatC * gridCos grid * cos (longC - long0)-         east = _2 * gridR grid * gridScale grid * cosLatC * sin (longC - long0) / b-         north = _2 * gridR grid * gridScale grid * (sinLatC * gridCos grid - cosLatC * gridSin grid * cos (longC - long0)) / b+         b = 1 + sinLatC * gridSin grid + cosLatC * gridCos grid * cos (longC - long0)+         east = 2 * gridR grid * gridScale grid * cosLatC * sin (longC - long0) / b+         north = 2 * gridR grid * gridScale grid * (sinLatC * gridCos grid - cosLatC * gridSin grid * cos (longC - long0)) / b        fromGrid gp =        {- trace (    -- Remove comment brackets for debugging.@@ -97,8 +95,8 @@          "\n   lat1 = " ++ show lat1 ++ "\n   latN = " ++ show latN ) $ -}          Geodetic (op latN) (op long) height $ gridEllipsoid grid       where-         op :: Num a => Quantity d a -> Quantity d a                   -- Values of longitude, tangent longitude, E and N-         op = if latitude (gridTangent grid) < _0 then negate else id  -- must be negated in the southern hemisphere.+         op :: Num a => a -> a                   -- Values of longitude, tangent longitude, E and N+         op = if latitude (gridTangent grid) < 0 then negate else id  -- must be negated in the southern hemisphere.          GridPoint east north height _ = applyOffset (offsetNegate $ gridOrigin grid) gp          east' = east          north' = north@@ -106,16 +104,15 @@          long0 = op $ longitude $ gridTangent grid          i = atan2 east' (gridH grid + north')          j = atan2 east' (gridG grid - north') - i-         latC = gridLatC grid + _2 * atan2 (north' - east' * tan (j/_2)) (_2 * gridR grid * gridScale grid)-         longC = j + _2 * i + long0+         latC = gridLatC grid + 2 * atan2 (north' - east' * tan (j/2)) (2 * gridR grid * gridScale grid)+         longC = j + 2 * i + long0          sinLatC = sin latC          long = (longC - long0) / gridN grid + long0-         isoLat = log ((_1 + sinLatC) / (gridC grid * (_1 - sinLatC))) / (_2 * gridN grid)-         lat1 = _2 * atan (exp isoLat) - pi/_2-         next lat = lat - (isoN - isoLat) * cos lat * (_1 - e2 * sin lat ^ pos2) / (_1 - e2)+         isoLat = log ((1 + sinLatC) / (gridC grid * (1 - sinLatC))) / (2 * gridN grid)+         lat1 = 2 * atan (exp isoLat) - pi/2+         next lat = lat - (isoN - isoLat) * cos lat * (1 - e2 * sin lat ^ _2) / (1 - e2)             where isoN = isometricLatitude (gridEllipsoid grid) lat                   e2 = eccentricity2 $ gridEllipsoid grid-         lats = iterate next lat1-         latN = snd $ head $ dropWhile (\(v1, v2) -> abs (v1-v2) > 0.01 *~ arcsecond) $ zip lats $ tail lats -            +         lats = Stream.iterate next lat1+         latN = snd $ Stream.head $ Stream.dropWhile (\(v1, v2) -> abs (v1-v2) > 0.01 * arcsecond) $ Stream.zip lats $ Stream.drop 1 lats    gridEllipsoid = ellipsoid . gridTangent
src/Geodetics/TransverseMercator.hs view
@@ -5,60 +5,60 @@    mkGridTM ) where -import Data.Function-import Data.Monoid import Geodetics.Ellipsoids import Geodetics.Geodetic import Geodetics.Grid-import Numeric.Units.Dimensional.Prelude hiding ((.))-import Prelude () +import qualified Data.Stream as Stream+ -- | A Transverse Mercator projection gives an approximate mapping of the ellipsoid on to a 2-D grid. It models -- a sheet curved around the ellipsoid so that it touches it at one north-south line (hence making it part of -- a slightly elliptical cylinder).+--+-- The calculations here are based on \"Transverse Mercator Projection: Constants, Formulae and Methods\"+-- by the Ordnance Survey, March 1983.+-- Retrieved from http://www.threelittlemaids.co.uk/magdec/transverse_mercator_projection.pdf data GridTM e = GridTM {    trueOrigin :: Geodetic e,       -- ^ A point on the line where the projection touches the ellipsoid (altitude is ignored).    falseOrigin :: GridOffset,-      -- ^ The grid position of the true origin. Used to avoid negative coordinates over +      -- ^ The grid position of the true origin. Used to avoid negative coordinates over       -- the area of interest. The altitude gives a vertical offset from the ellipsoid.-   gridScale :: Dimensionless Double,-      -- ^ A scaling factor that balances the distortion between the east & west edges and the middle +   gridScale :: Double,+      -- ^ A scaling factor that balances the distortion between the east & west edges and the middle       -- of the projection.-      +    -- Remaining elements are memoised parameters computed from the ellipsoid underlying the true origin.-   gridN1, gridN2, gridN3, gridN4 :: Dimensionless Double+   gridN1, gridN2, gridN3, gridN4 :: Double } deriving (Show)   -- | Create a Transverse Mercator grid.-mkGridTM :: (Ellipsoid e) => +mkGridTM :: (Ellipsoid e) =>    Geodetic e               -- ^ True origin.    -> GridOffset            -- ^ Vector from true origin to false origin.-   -> Dimensionless Double  -- ^ Scale factor.+   -> Double                -- ^ Scale factor.    -> GridTM e mkGridTM origin offset sf =    GridTM {trueOrigin = origin,            falseOrigin = offset,            gridScale = sf,-           gridN1 = _1 + n + (_5/_4) * n^pos2 + (_5/_4) * n^pos3,-           gridN2 = _3 * n + _3 * n^pos2 + ((21*~one)/_8) * n^pos3,-           gridN3 = ((15*~one)/_8) * (n^pos2 + n^pos3),-           gridN4 = ((35*~one)/(24*~one)) * n^pos3+           gridN1 = 1 + n + (5/4) * n^ _2 + (5/4) * n^ _3,+           gridN2 = 3 * n + 3 * n^ _2 + (21/8) * n^ _3,+           gridN3 = (15/8) * (n^ _2 + n^ _3),+           gridN4 = (35/24) * n^ _3         }-    where +    where        f = flattening $ ellipsoid origin-       n = f / (_2-f)  -- Equivalent to (a-b)/(a+b) where b = (1-f)*a--+       n = f / (2-f)  -- Equivalent to (a-b)/(a+b) where b = (1-f)*a   -- | Equation C3 from reference [1].-m :: (Ellipsoid e) => GridTM e -> Dimensionless Double -> Length Double-m grid lat = bF0 * (gridN1 grid * dLat +m :: (Ellipsoid e) => GridTM e -> Double -> Double+m grid lat = bF0 * (gridN1 grid * dLat                     - gridN2 grid * sin dLat * cos sLat-                    + gridN3 grid * sin (_2 * dLat) * cos (_2 * sLat) -                    - gridN4 grid * sin (_3 * dLat) * cos (_3 * sLat))+                    + gridN3 grid * sin (2 * dLat) * cos (2 * sLat)+                    - gridN4 grid * sin (3 * dLat) * cos (3 * sLat))    where       dLat = lat - latitude (trueOrigin grid)       sLat = lat + latitude (trueOrigin grid)@@ -66,77 +66,90 @@   instance (Ellipsoid e) => GridClass (GridTM e) e where-   fromGrid p = Geodetic-      (lat' - east' ^ pos2 * tanLat / (_2 * rho * v)  -- Term VII-            + east' ^ pos4 * (tanLat / ((24 *~ one) * rho * v ^ pos3)) -                           * (_5 + _3 * tanLat ^ pos2 + eta2 - _9 * tanLat ^ pos2 * eta2)  -- Term VIII-            - east' * east' ^ pos5 * (tanLat / ((720 *~ one) * rho * v ^ pos5))-                           * (61 *~ one + (90 *~ one) * tanLat ^ pos2 + (45 *~ one) * tanLat ^ pos4)) -- Term IX-      (longitude (trueOrigin grid) -            + east' / (cosLat * v)  -- Term X-            - (east' ^ pos3 / (_6 * cosLat * v ^ pos3)) * (v / rho + _2 * tanLat ^ pos2)  -- Term XI-            + (east' ^ pos5 / ((120 *~ one) * cosLat * v ^ pos5)) -                 * (_5 + (28 *~ one) * tanLat ^ pos2  + (24 *~ one) * tanLat ^ pos4)  -- Term XII-            - (east' ^ pos5 * east' ^ pos2 / ((5040 *~ one) * cosLat * v * v * v ^ pos5))-                 * ((61 *~ one) + (662 *~ one) * tanLat ^ pos2 + (1320 *~ one) * tanLat ^ pos4 + (720 *~ one) * tanLat * tanLat ^ pos5)) -- Term XIIa-     (0 *~ meter) (gridEllipsoid grid)-            -            +   fromGrid p = -- trace traceMsg $+      Geodetic+         (lat' - east' ^ _2 * term_VII + east' ^ _4 * term_VIII - east' ^ _6 * term_IX)+         (longitude (trueOrigin grid)+               + east' * term_X - east' ^ _3 * term_XI + east' ^ _5 * term_XII - east' ^ _7 * term_XIIa)+         (altGP p)+         (gridEllipsoid grid)       where          GridPoint east' north' _ _ = falseOrigin grid `applyOffset` p-         lat' = fst $ head $ dropWhile ((> 0.01 *~ milli meter) . snd) -               $ tail $ iterate next (latitude $ trueOrigin grid, 1 *~ meter) +         lat' = fst $ Stream.head $ Stream.dropWhile ((> 1e-5) . abs . snd)+               $ Stream.tail $ Stream.iterate next (latitude $ trueOrigin grid, 1)             where-               next (phi, _) = let delta = north' - m grid phi in (phi + delta / aF0, delta) -               -- head and tail are safe because iterate returns an infinite list.-          +               next (phi, _) = let delta = north' - m grid phi in (phi + delta / aF0, delta)+         -- Terms defined in [1]+         term_VII  = tanLat / (2 * rho * v)+         term_VIII = (tanLat / (24 * rho * v ^ _3))  * (5 + 3 * tanLat ^ _2 + eta2 - 9 * tanLat ^ _2 * eta2)+         term_IX   = (tanLat / (720 * rho * v ^ _5)) * (61 + 90 * tanLat ^ _2 + 45 * tanLat ^ _4)+         term_X    = 1                                                                 / (cosLat * v)+         term_XI   = (v / rho + 2 * tanLat ^ _2)                                       / (6 * cosLat * v ^ _3)+         term_XII  = ( 5 +  28 * tanLat ^ _2 +   24 * tanLat ^ _4)                     / (120 * cosLat * v ^ _5)+         term_XIIa = (61 + 662 * tanLat ^ _2 + 1320 * tanLat ^ _4 + 720 * tanLat ^ _6) / (5040 * cosLat * v ^ _7)++         -- Trace message for debugging. Uncomment this code to inspect intermediate values.+         {-+         traceMsg = concat [+            "lat' = ", show lat', "\n",+            "v    = ", show v, "\n",+            "rho  = ", show rho, "\n",+            "eta2 = ", show eta2, "\n",+            "VII  = ", show term_VII, "\n",+            "VIII = ", show term_VIII, "\n",+            "IX   = ", show term_IX, "\n",+            "X    = ", show term_X, "\n",+            "XI   = ", show term_XI, "\n",+            "XII  = ", show term_XII, "\n",+            "XIIa = ", show term_XIIa, "\n"]+         -}          sinLat = sin lat'          cosLat = cos lat'          tanLat = tan lat'-         sinLat2 = sinLat ^ pos2-         v = aF0 / sqrt (_1 - e2 * sinLat2)-         rho = aF0 * (_1 - e2) * (_1 - e2 * sinLat2) ** ((-1.5) *~ one)-         eta2 = v / rho - _1-               -               +         sinLat2 = sinLat * sinLat+         v = aF0 / sqrt (1 - e2 * sinLat2)+         rho = v * (1 - e2) / (1 - e2 * sinLat2)+         eta2 = v / rho - 1+          aF0 = majorRadius (gridEllipsoid grid) * gridScale grid          e2 = eccentricity2 $ gridEllipsoid grid          grid = gridBasis p-         -   toGrid grid geo = applyOffset (off  `mappend` (offsetNegate $ falseOrigin grid)) $ -                     GridPoint _0 _0 _0 grid++   toGrid grid geo = -- trace traceMsg $ +      applyOffset (off  `mappend` offsetNegate (falseOrigin grid)) $ GridPoint 0 0 0 grid       where-         v = aF0 / sqrt (_1 - e2 * sinLat2)-         rho = aF0 * (_1 - e2) * (_1 - e2 * sinLat2) ** ((-1.5) *~ one)-         eta2 = v / rho - _1+         v = aF0 / sqrt (1 - e2 * sinLat2)+         rho = v * (1 - e2) / (1 - e2 * sinLat2)+         eta2 = v / rho - 1          off = GridOffset                   (dLong * term_IV-                   + dLong ^ pos3 * term_V-                   + dLong ^ pos5 * term_VI)-                  (m grid lat + dLong ^ pos2 * term_II-                     + dLong ^ pos4 * term_III -                     + dLong * dLong ^ pos5 * term_IIIa)-                  (0 *~ meter)+                   + dLong ^ _3 * term_V+                   + dLong ^ _5 * term_VI)+                  (m grid lat + dLong ^ _2 * term_II+                     + dLong ^ _4 * term_III+                     + dLong ^ _6 * term_IIIa)+                  0          -- Terms defined in [1].-         term_II   = (v/_2) * sinLat * cosLat-         term_III  = (v/(24*~one)) * sinLat * cosLat ^ pos3 -                     * (_5 - tanLat ^ pos2 + _9 * eta2)-         term_IIIa = (v/(720*~one)) * sinLat * cosLat ^ pos5 -                     * ((61 *~ one) - (58 *~ one) * tanLat ^ pos2 + tanLat ^ pos4)+         term_II   = (v/2) * sinLat * cosLat+         term_III  = (v/24) * sinLat * cosLat ^ _3+                     * (5 - tanLat ^ _2 + 9 * eta2)+         term_IIIa = (v/720) * sinLat * cosLat ^ _5+                     * (61 - 58 * tanLat ^ _2 + tanLat ^ _4)          term_IV   = v * cosLat-         term_V    = (v/_6) * cosLat ^ pos3 * (v/rho - tanLat ^ pos2)-         term_VI   = (v/(120*~one)) * cosLat ^ pos5 -                     * (_5 - (18*~one) * tanLat ^ pos2 -                              + tanLat ^ pos4 + (14*~one) * eta2-                              - (58*~one) * tanLat ^ pos2 * eta2)-         {- -         -- Trace message for debugging. Uncomment this code for easy access to intermediate values.+         term_V    = (v/6) * cosLat ^ _3 * (v/rho - tanLat ^ _2)+         term_VI   = (v/120) * cosLat ^ _5+                     * (5 - 18 * tanLat ^ _2+                              + tanLat ^ _4 + 14 * eta2+                              - 58 * tanLat ^ _2 * eta2)++         -- Trace message for debugging. Uncomment this code to inspect intermediate values.+         {-          traceMsg = concat [             "v    = ", show v, "\n",             "rho  = ", show rho, "\n",             "eta2 = ", show eta2, "\n",             "M    = ", show $ m grid lat, "\n",-            "I    = ", show $ m grid lat + deltaNorth (falseOrigin grid), "\n",+            "I    = ", show $ m grid lat - deltaNorth (falseOrigin grid), "\n",  --              "II   = ", show term_II, "\n",             "III  = ", show term_III, "\n",             "IIIa = ", show term_IIIa, "\n",@@ -151,8 +164,8 @@          sinLat = sin lat          cosLat = cos lat          tanLat = tan lat-         sinLat2 = sinLat ^ pos2-         aF0 = (majorRadius $ gridEllipsoid grid) * gridScale grid+         sinLat2 = sinLat * sinLat+         aF0 = majorRadius (gridEllipsoid grid) * gridScale grid          e2 = eccentricity2 $ gridEllipsoid grid-         +    gridEllipsoid = ellipsoid . trueOrigin
src/Geodetics/UK.hs view
@@ -9,38 +9,33 @@    toUkGridReference ) where -import Control.Applicative import Control.Monad import Data.Array import Data.Char-import Data.Monoid import Geodetics.Geodetic import Geodetics.Grid import Geodetics.Ellipsoids import Geodetics.TransverseMercator-import Numeric.Units.Dimensional.Prelude-import qualified Prelude as P   --- | Ellipsoid definition for Great Britain. Airy 1830 offset from the centre of the Earth +-- | Ellipsoid definition for Great Britain. Airy 1830 offset from the centre of the Earth -- and rotated slightly.--- --- The Helmert parameters are from the Ordnance Survey document +--+-- The Helmert parameters are from the Ordnance Survey document -- \"A Guide to Coordinate Systems in Great Britain\", which notes that it -- can be in error by as much as 5 meters and should not be used in applications--- requiring greater accuracy.  A more precise conversion requires a large table +-- requiring greater accuracy.  A more precise conversion requires a large table -- of corrections for historical inaccuracies in the triangulation of the UK. data OSGB36 = OSGB36 deriving (Eq, Show)  instance Ellipsoid OSGB36 where-   majorRadius _ = 6377563.396 *~ meter-   flatR _ = 299.3249646 *~ one+   majorRadius _ = 6377563.396+   flatR _ = 299.3249646    helmert _ = Helmert {-      cX = 446.448 *~ meter, cY = (-125.157) *~ meter, cZ = 542.06 *~ meter,-      helmertScale = (-20.4894) *~ one,-      rX = 0.1502 *~ arcsecond, rY = 0.247 *~ arcsecond, rZ = 0.8421 *~ arcsecond }-+      cX = 446.448, cY = (-125.157), cZ = 542.06,+      helmertScale = (-20.4894),+      rX = 0.1502 * arcsecond, rY = 0.247 * arcsecond, rZ = 0.8421 * arcsecond }  -- | The UK National Grid is a Transverse Mercator projection with a true origin at -- 49 degrees North, 2 degrees West on OSGB36, and a false origin 400km West and 100 km North of@@ -56,73 +51,74 @@  ukTrueOrigin :: Geodetic OSGB36 ukTrueOrigin = Geodetic {-   latitude = 49 *~ degree,-   longitude = (-2) *~ degree,-   geoAlt = 0 *~ meter,+   latitude = 49 * degree,+   longitude = (-2) * degree,+   geoAlt = 0,    ellipsoid = OSGB36 } -ukFalseOrigin :: GridOffset -ukFalseOrigin = GridOffset ((-400) *~ kilo meter) (100 *~ kilo meter) (0 *~ meter)+ukFalseOrigin :: GridOffset+ukFalseOrigin = GridOffset ((-400) * kilometer) (100 * kilometer) (0 * kilometer)   -- | Numerical definition of the UK national grid. ukGrid :: GridTM OSGB36-ukGrid = mkGridTM ukTrueOrigin ukFalseOrigin -   ((10 *~ one) ** (0.9998268 *~ one - _1))+ukGrid = mkGridTM ukTrueOrigin ukFalseOrigin+   (10 ** (0.9998268 - 1))   -- | Size of a UK letter-pair grid square.-ukGridSquare :: Length Double-ukGridSquare = 100 *~ kilo meter+ukGridSquare :: Double+ukGridSquare = 100 * kilometer  --- | Convert a grid reference to a position, if the reference is valid. --- This returns the position of the south-west corner of the nominated +-- | Convert a grid reference to a position, if the reference is valid.+-- This returns the position of the south-west corner of the nominated -- grid square and an offset to its centre. Altitude is set to zero. fromUkGridReference :: String -> Maybe (GridPoint UkNationalGrid, GridOffset)-fromUkGridReference str = if length str < 2 then Nothing else do-      let -         c1:c2:ds = str-         n = length ds-      guard $ even n-      let (dsE, dsN) = splitAt (n `div` 2) ds-      (east, sq) <- fromGridDigits ukGridSquare dsE-      (north, _) <- fromGridDigits ukGridSquare dsN-      base <- fromUkGridLetters c1 c2-      let half = sq / (2 *~ one)-      return (applyOffset (GridOffset east north (0 *~ meter)) base,-              GridOffset half half (0 *~ meter))+fromUkGridReference str =+      case str of+         c1:c2:ds -> do+            let n = length ds+            guard $ even n+            let (dsE, dsN) = splitAt (n `div` 2) ds+            (east, sq) <- fromGridDigits ukGridSquare dsE+            (north, _) <- fromGridDigits ukGridSquare dsN+            base <- fromUkGridLetters c1 c2+            let half = sq / 2+            return (applyOffset (GridOffset east north 0) base,+                  GridOffset half half 0)+         _ -> Nothing -        + -- | The south west corner of the nominated grid square, if it is a legal square. -- This function works for all pairs of letters except 'I' (as that is not used). -- In practice only those pairs covering the UK are actually considered meaningful. fromUkGridLetters :: Char -> Char -> Maybe (GridPoint UkNationalGrid) fromUkGridLetters c1 c2 = applyOffset <$> (mappend <$> g1 <*> g2) <*> letterOrigin    where-      letterOrigin = Just $ GridPoint ((-1000) *~ kilo meter) ((-500) *~ kilo meter) m0 UkNationalGrid-      gridIndex c = -         if inRange ('A', 'H') c then Just $ ord c P.- ord 'A'  -- 'I' is not used.-         else if inRange ('J', 'Z') c then Just $ ord c P.- ord 'B'+      letterOrigin = Just $ GridPoint ((-1000) * kilometer) ((-500) * kilometer) m0 UkNationalGrid+      gridIndex c =+         if inRange ('A', 'H') c then Just $ ord c - ord 'A'  -- 'I' is not used.+         else if inRange ('J', 'Z') c then Just $ ord c - ord 'B'          else Nothing       gridSquare c = do -- Maybe monad          g <- gridIndex c-         let (y,x) = g `divMod` 5 -         return (fromIntegral x *~ one, _4 - fromIntegral y *~ one)+         let (y,x) = g `divMod` 5+         return (fromIntegral x, 4 - fromIntegral y)       g1 = do          (x,y) <- gridSquare c1-         return $ GridOffset (x * (500 *~ kilo meter)) (y * (500 *~ kilo meter)) m0+         return $ GridOffset (x * (500 * kilometer)) (y * (500 * kilometer)) m0       g2 = do          (x,y) <- gridSquare c2-         return $ GridOffset (x * (100 *~ kilo meter)) (y * (100 *~ kilo meter)) m0-      m0 = 0 *~ meter+         return $ GridOffset (x * (100 * kilometer)) (y * (100 * kilometer)) m0+      m0 = 0   -- | Find the nearest UK grid reference point to a specified position. The Int argument is the number of--- digits precision, so 2 for a 4-figure reference and 3 for a 6-figure reference, although any value +-- digits precision, so 2 for a 4-figure reference and 3 for a 6-figure reference, although any value -- between 0 and 5 can be used (giving a 1 meter precision). -- Altitude is ignored. If the result is outside the area defined by the two letter grid codes then -- @Nothing@ is returned.@@ -130,16 +126,15 @@ toUkGridReference n p    | n < 0         = error "toUkGridReference: precision argument must not be negative."    | otherwise     = do-      (gx, strEast) <- toGridDigits ukGridSquare n $ eastings p + 1000 *~ kilo meter-      (gy, strNorth) <- toGridDigits ukGridSquare n $ northings p + 500 *~ kilo meter-      let (gx1, gx2) = (fromIntegral gx) `divMod` 5-          (gy1, gy2) = (fromIntegral gy) `divMod` 5+      (gx, strEast) <- toGridDigits ukGridSquare n $ eastings p + 1000 * kilometer+      (gy, strNorth) <- toGridDigits ukGridSquare n $ northings p + 500 * kilometer+      let (gx1, gx2) = fromIntegral gx `divMod` 5+          (gy1, gy2) = fromIntegral gy `divMod` 5       guard (gx1 < 5 && gy1 < 5)       let c1 = gridSquare gx1 gy1           c2 = gridSquare gx2 gy2       return $ c1 : c2 : strEast ++ strNorth    where-      gridSquare x y = letters ! (4 P.- y, x)+      gridSquare x y = letters ! (4 - y, x)       letters :: Array (Int, Int) Char       letters = listArray ((0,0),(4,4)) $ ['A'..'H'] ++ ['J'..'Z']-   
test/ArbitraryInstances.hs view
@@ -5,7 +5,6 @@  module ArbitraryInstances where -import Control.Applicative import Control.Monad import Geodetics.Altitude import Geodetics.Geodetic@@ -14,67 +13,67 @@ import Geodetics.Path import Geodetics.Stereographic as SG import Geodetics.TransverseMercator as TM-import Numeric.Units.Dimensional.Prelude-import qualified Prelude () import Test.QuickCheck  +-- | Shrink an angle so that shrunk values are round numbers of degrees.+shrinkAngle :: Double -> [Double]+shrinkAngle v = (degree *) <$> shrink (v/degree) --- | Shrink using a dimension, so that shrunk values are round numbers in that dimension.-shrinkDimension :: forall a (d :: Dimension) (m :: Metricality) .-                   (Fractional a, Arbitrary a) => Unit m d a -> Quantity d a -> [Quantity d a]-shrinkDimension u v = (*~ u) <$> shrink (v /~ u)+-- | Shrink a distance so that shrunk values are round numbers of kilometers.+shrinkDistance :: Double -> [Double]+shrinkDistance v = (kilometer *) <$> shrink (v/kilometer)  -- | Wrapper for arbitrary angles.-newtype Bearing = Bearing (Dimensionless Double)+newtype Bearing = Bearing Double  instance Show Bearing where    show (Bearing b) = "Bearing " ++ showAngle b  instance Arbitrary Bearing where-   arbitrary = Bearing <$> (*~ degree) <$> choose (-180,180)-   shrink (Bearing b) = Bearing <$> shrinkDimension degree b+   arbitrary = Bearing <$> (* degree) <$> choose (-180,180)+   shrink (Bearing b) = Bearing <$> shrinkAngle b        -newtype Azimuth = Azimuth (Dimensionless Double)+newtype Azimuth = Azimuth Double  instance Show Azimuth where    show (Azimuth a) = "Azimuth " ++ showAngle a     instance Arbitrary Azimuth where-   arbitrary = Azimuth <$> (*~ degree) <$> choose (0,90)-   shrink (Azimuth a) = Azimuth <$> shrinkDimension degree a+   arbitrary = Azimuth <$> (* degree) <$> choose (0,90)+   shrink (Azimuth a) = Azimuth <$> shrinkAngle a         -- | Wrapper for arbitrary distances up to 10,000 km-newtype Distance = Distance (Length Double) deriving (Show)+newtype Distance = Distance Double deriving (Show)  instance Arbitrary Distance where-   arbitrary = Distance <$> (*~ kilo meter) <$> choose (0,10000)-   shrink (Distance d) = Distance <$> shrinkDimension (kilo meter) d+   arbitrary = Distance <$> (* kilometer) <$> choose (0,10000)+   shrink (Distance d) = Distance <$> shrinkDistance d      -- | Wrapper for arbitrary distances up to 1,000 km-newtype Distance2 = Distance2 (Length Double) deriving (Show)+newtype Distance2 = Distance2 Double deriving (Show)  instance Arbitrary Distance2 where-   arbitrary = Distance2 <$> (*~ kilo meter) <$> choose (0,1000)-   shrink (Distance2 d) = Distance2 <$> shrinkDimension (kilo meter) d+   arbitrary = Distance2 <$> (* kilometer) <$> choose (0,1000)+   shrink (Distance2 d) = Distance2 <$> shrinkDistance d  -- | Wrapper for arbitrary altitudes up to 10 km-newtype Altitude = Altitude (Length Double) deriving (Show)+newtype Altitude = Altitude Double deriving (Show)  instance Arbitrary Altitude where-   arbitrary = Altitude <$> (*~ kilo meter) <$> choose (0,10)-   shrink (Altitude h) = Altitude <$> shrinkDimension (kilo meter) h+   arbitrary = Altitude <$> (* kilometer) <$> choose (0,10)+   shrink (Altitude h) = Altitude <$> shrinkDistance h   -- | Wrapper for arbitrary dimensionless numbers (-10 .. 10)-newtype Scalar = Scalar (Dimensionless Double) deriving (Show)+newtype Scalar = Scalar Double deriving (Show)  instance Arbitrary Scalar where-   arbitrary = Scalar <$> (*~ one) <$> choose (-10,10)-   shrink (Scalar s) = Scalar <$> shrinkDimension one s+   arbitrary = Scalar <$> choose (-10,10)+   shrink (Scalar s) = Scalar <$> shrink s   -- | Wrapper for arbitrary grid references.@@ -92,21 +91,21 @@   -- | Generate in range +/- <arg> m.-genOffset :: Double -> Gen (Length Double)-genOffset d = (*~ meter) <$> choose (-d, d)+genOffset :: Double -> Gen Double+genOffset d = choose (-d, d) -genAlt :: Gen (Length Double)-genAlt = (*~ meter) <$> choose (0,10000)+genAlt :: Gen Double+genAlt = choose (0,10000)  -genLatitude :: Gen (Dimensionless Double)-genLatitude = (*~ degree) <$> choose (-90,90)+genLatitude :: Gen Double+genLatitude = (* degree) <$> choose (-90,90) -genLongitude :: Gen (Dimensionless Double)-genLongitude = (*~ degree) <$> choose (-180,180)+genLongitude :: Gen Double+genLongitude = (* degree) <$> choose (-180,180) -genSeconds :: Gen (Dimensionless Double)-genSeconds = (*~ arcsecond) <$> choose (-10,10)+genSeconds :: Gen Double+genSeconds = (* arcsecond) <$> choose (-10,10)       -- | Shrinking with the original value preserved. Used for shrinking records.  See @@ -114,30 +113,18 @@ shrink' :: (Arbitrary a) => a -> [a] shrink' x = x : shrink x --- | Shrink a quantity in the given units.-shrinkQuantity :: forall a (d :: Dimension) (m :: Metricality).-                  (Arbitrary a, Fractional a) => Unit m d a -> Quantity d a -> [Quantity d a]-shrinkQuantity u q = map (*~ u) $ shrink' $ q /~ u--shrinkLength :: (Arbitrary a, Fractional a) => Length a -> [Length a]-shrinkLength = shrinkQuantity meter--shrinkUnit :: (Arbitrary a, Fractional a) => Dimensionless a -> [Dimensionless a]-shrinkUnit = shrinkQuantity one--shrinkAngle :: (Arbitrary a, Floating a) => Dimensionless a -> [Dimensionless a]-shrinkAngle = shrinkQuantity degree+shrinkAngle' :: Double -> [Double]+shrinkAngle' a = a : shrinkAngle a   instance Arbitrary Helmert where    arbitrary =        Helmert <$> genOffset 300 <*> genOffset 300 <*> genOffset 300 <*> -         ((*~ one) <$> choose (-5,10)) <*>-         genSeconds <*> genSeconds <*> genSeconds+         (choose (-5,10)) <*> genSeconds <*> genSeconds <*> genSeconds    shrink h = -      tail $ Helmert <$> shrinkLength (cX h) <*> shrinkLength (cY h) <*> shrinkLength (cZ h) <*>-         shrinkUnit (helmertScale h) <*>-         shrinkUnit (rX h) <*> shrinkUnit (rY h) <*> shrinkUnit (rZ h)      +      drop 1 $ Helmert <$> shrink' (cX h) <*> shrink' (cY h) <*> shrink' (cZ h) <*>+         shrink' (helmertScale h) <*>+         shrink' (rX h) <*> shrink' (rY h) <*> shrink' (rZ h)         instance Arbitrary WGS84 where@@ -148,55 +135,55 @@ instance Arbitrary LocalEllipsoid where    arbitrary =       LocalEllipsoid <$> (("Local_" ++) <$> replicateM 3 (choose ('A','Z'))) <*>  -- name-         ((*~ meter) <$> choose (6378100, 6378400)) <*>                  -- majorRadius-         ((*~ one) <$> choose (297,300)) <*>                             -- flatR-         arbitrary                                                       -- helmert-   shrink e = tail $ LocalEllipsoid (nameLocal e) (majorRadius e) (flatR e) <$> shrink' (helmert e)+         (choose (6378100, 6378400)) <*>                    -- majorRadius+         (choose (297,300)) <*>                             -- flatR+         arbitrary                                          -- helmert+   shrink e = drop 1 $ LocalEllipsoid (nameLocal e) (majorRadius e) (flatR e) <$> shrink' (helmert e)           instance (Ellipsoid e, Arbitrary e) => Arbitrary (Geodetic e) where    arbitrary =        Geodetic <$> genLatitude <*> genLongitude <*> genOffset 1 <*> arbitrary    shrink g = -      tail $ Geodetic <$> shrinkAngle (latitude g) <*> shrinkAngle (longitude g) <*> -         shrinkLength (altitude g) <*> shrink' (ellipsoid g)+      drop 1 $ Geodetic <$> shrinkAngle' (latitude g) <*> shrinkAngle' (longitude g) <*> +         shrink' (altitude g) <*> shrink' (ellipsoid g)  instance (Ellipsoid e, Arbitrary e) => Arbitrary (GridPoint (GridTM e)) where    arbitrary = GridPoint <$> genOffset 100000 <*> genOffset 100000 <*> genOffset 1 <*> arbitrary-   shrink p = tail $ GridPoint <$> -      shrinkLength (eastings p) <*> -      shrinkLength (northings p) <*> -      shrinkLength (altitude p) <*> +   shrink p = drop 1 $ GridPoint <$> +      shrink' (eastings p) <*> +      shrink' (northings p) <*> +      shrink' (altitude p) <*>        shrink' (gridBasis p)   instance (Ellipsoid e, Arbitrary e) => Arbitrary (GridPoint (GridStereo e)) where    arbitrary = GridPoint <$> genOffset 100000 <*> genOffset 100000 <*> genOffset 1 <*> arbitrary-   shrink p = tail $ GridPoint <$> -      shrinkLength (eastings p) <*> -      shrinkLength (northings p) <*> -      shrinkLength (altitude p) <*> +   shrink p = drop 1 $ GridPoint <$> +      shrink' (eastings p) <*> +      shrink' (northings p) <*> +      shrink' (altitude p) <*>        shrink' (gridBasis p)   instance (Ellipsoid e, Arbitrary e) => Arbitrary (GridTM e) where-   arbitrary = mkGridTM <$> arbitrary <*> arbitrary <*> ((*~ one) <$> choose (0.95,1.0))-   shrink tm = tail $ mkGridTM <$> shrink' (trueOrigin tm) <*> shrink' (falseOrigin tm) <*> [TM.gridScale tm]+   arbitrary = mkGridTM <$> arbitrary <*> arbitrary <*> choose (0.95,1.0)+   shrink tm = drop 1 $ mkGridTM <$> shrink' (trueOrigin tm) <*> shrink' (falseOrigin tm) <*> [TM.gridScale tm]         instance Arbitrary GridOffset where    arbitrary = GridOffset <$> genOffset 100000 <*> genOffset 100000 <*> genAlt-   shrink d = tail $ GridOffset <$> -      shrinkLength (deltaEast d) <*> shrinkLength (deltaNorth d) <*> shrinkLength (deltaAltitude d)+   shrink d = drop 1 $ GridOffset <$> +      shrink' (deltaEast d) <*> shrink' (deltaNorth d) <*> shrink' (deltaAltitude d)   instance (Ellipsoid e, Arbitrary e) => Arbitrary (GridStereo e) where-   arbitrary = mkGridStereo <$> arbitrary <*> arbitrary <*> ((*~ one) <$> choose (0.95,1.0))-   shrink sg = tail $ mkGridStereo <$> shrink' (gridTangent sg) <*> shrink' (gridOrigin sg) <*> [SG.gridScale sg]+   arbitrary = mkGridStereo <$> arbitrary <*> arbitrary <*> choose (0.95,1.0)+   shrink sg = drop 1 $ mkGridStereo <$> shrink' (gridTangent sg) <*> shrink' (gridOrigin sg) <*> [SG.gridScale sg]      -- | Wrapper for arbitrary rays, along with creation parameters for printing and shrinking.-data Ray e = Ray (Geodetic e) (Angle Double) (Angle Double)+data Ray e = Ray (Geodetic e) Double Double  instance (Ellipsoid e) => Show (Ray e) where    show (Ray p0 b e ) = "(Ray " ++ show p0 ++ ", " ++ showAngle b ++ ", " ++ showAngle e ++ ")"@@ -207,13 +194,13 @@ instance (Ellipsoid e, Arbitrary e) => Arbitrary (Ray e) where    arbitrary = do       p0 <- arbitrary-      b <- (*~ degree) <$> choose (-180,180)-      e <- (*~ degree) <$> choose (0,90)+      b <- (* degree) <$> choose (-180,180)+      e <- (* degree) <$> choose (0,90)       return $ Ray p0 b e-   shrink (Ray p0 b e) = tail $ do+   shrink (Ray p0 b e) = drop 1 $ do       p0' <- shrink' p0-      b' <- shrinkAngle b-      e' <- shrinkAngle e+      b' <- shrinkAngle' b+      e' <- shrinkAngle' e       return $ Ray p0' b' e'             @@ -230,15 +217,15 @@    show rp2 = show (pt1, Bearing b1) ++ show (pt2, Bearing b2)       where           (p1, p2) = mk2RhumbPaths rp2-         (pt1, b1, _) = pathFunc p1 (0 *~ meter)-         (pt2, b2, _) = pathFunc p2 (0 *~ meter)+         (pt1, b1, _) = pathFunc p1 0+         (pt2, b2, _) = pathFunc p2 0            instance Arbitrary RhumbPaths2 where    arbitrary = RP2 -      <$> arbitrary `suchThat` ((< 70 *~ degree) . abs . latitude)+      <$> arbitrary `suchThat` ((< (70 * degree)) . abs . latitude)       <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary    shrink rp = -      tail $ RP2 <$> +      drop 1 $ RP2 <$>           shrink' (rp2Point0 rp) <*>           shrink' (rp2Bearing0 rp) <*>           shrink' (rp2Distance rp) <*> 
test/Main.hs view
@@ -3,9 +3,6 @@ module Main where  import Data.Maybe-import Data.Monoid-import Numeric.Units.Dimensional.Prelude-import qualified Prelude as P import Test.Framework (Test, defaultMainWithOpts, testGroup) import Test.Framework.Options (TestOptions, TestOptions'(..)) import Test.Framework.Runners.Options (RunnerOptions, RunnerOptions'(..))@@ -44,7 +41,7 @@ instance EqProp GridOffset where   (GridOffset a b c) =-= (GridOffset a' b' c') =     eq True $ a ≈ a' && b ≈ b' && c ≈ c'-    where x ≈ y = abs (x - y) < 0.00001 *~ meter+    where x ≈ y = abs (x - y) < 0.00001  instance EqProp Helmert where   (Helmert cX' cY' cZ' s rX' rY' rZ') =-= (Helmert cX'' cY'' cZ'' s' rX'' rY'' rZ'') =@@ -52,8 +49,8 @@                    s ≈- s',                    rX' ≈- rX'', rY' ≈- rY'', rZ' ≈- rZ''] -    where x ≈ y = abs (x - y) < 0.00001 *~ meter-          x ≈- y = abs (x - y) < (_1 / (_5 * _2) ** (_5))+    where x ≈ y = abs (x - y) < 0.00001+          x ≈- y = abs (x - y) < (1 / (5 * 2) ^ _5)  tests :: [Test] tests = [@@ -67,6 +64,9 @@       testProperty "Grid Offset 2" prop_offset2,       testProperty "Grid Offset 3" prop_offset3,       testProperty "Grid 1" prop_grid1 ],+   testGroup "TransverseMercator" [+      testCase "fromGrid . toGrid == id" $ HU.assertBool "" prop_tmGridInverse+      ],    testGroup "UK" [       testProperty "UK Grid 1" prop_ukGrid1,       testGroup "UK Grid 2" $ map ukGridTest2 ukSampleGrid,@@ -95,38 +95,38 @@  -- | The positions are within 30 cm. samePlace :: (Ellipsoid e) => Geodetic e -> Geodetic e -> Bool-samePlace p1 p2 = geometricalDistance p1 p2 < 0.3 *~ meter+samePlace p1 p2 = geometricalDistance p1 p2 < 0.3   -- | The positions are within 10 m. closeEnough :: (Ellipsoid e) => Geodetic e -> Geodetic e -> Bool-closeEnough p1 p2 = geometricalDistance p1 p2 < 10 *~ meter+closeEnough p1 p2 = geometricalDistance p1 p2 < 10   -- | The angles are within 0.01 arcsec-sameAngle :: Angle Double -> Angle Double -> Bool-sameAngle v1 v2 = abs (properAngle (v1 - v2)) < 0.01 *~ arcsecond+sameAngle :: Double -> Double -> Bool+sameAngle v1 v2 = abs (properAngle (v1 - v2)) < 0.01 * arcsecond  -- | The grid positions are within 1mm sameGrid :: (GridClass r e) => GridPoint r -> GridPoint r -> Bool sameGrid p1 p2 = check eastings && check northings && check altitude-   where check f = f p1 - f p2 < 1 *~ milli meter+   where check f = f p1 - f p2 < 1e-3   -- | Grid offsets are within 1mm. sameOffset :: GridOffset -> GridOffset -> Bool sameOffset go1 go2 = check deltaNorth && check deltaEast && check deltaAltitude-   where check f = f go1 - f go2 < 1 *~ milli meter+   where check f = f go1 - f go2 < 1e-3   -- | The grid X and Y are both within 1 meter closeGrid :: (GridClass r e) => GridPoint r -> GridPoint r -> Bool closeGrid p1 p2 = check eastings && check northings && check altitude-   where check f = f p1 - f p2 < 1 *~ meter+   where check f = f p1 - f p2 < 1  -- | Degrees, minutes and seconds into radians.-dms :: Int -> Int -> Double -> Dimensionless Double-dms d m s = fromIntegral d *~ degree + fromIntegral m *~ arcminute + s *~ arcsecond+dms :: Int -> Int -> Double -> Double+dms d m s = fromIntegral d * degree + fromIntegral m * arcminute + s * arcsecond  -- | Round-trip from local to WGS84 and back is identity (approximately) prop_WGS84_and_back :: Geodetic LocalEllipsoid -> Bool@@ -138,46 +138,46 @@ prop_zero_ground p =    case groundDistance p p of       Nothing -> False-      Just (d, _, _) -> abs d < 1 *~ milli meter+      Just (d, _, _) -> abs d < 1e-3   -- | Sample pairs of points with bearings and distances. -- The Oracle for these values is the @FORWARD@ program from --  <http://www.ngs.noaa.gov/TOOLS/Inv_Fwd/Inv_Fwd.html>-worldLines :: [(String, Geodetic WGS84, Geodetic WGS84, Length Double, Dimensionless Double, Dimensionless Double)]+worldLines :: [(String, Geodetic WGS84, Geodetic WGS84, {-Length-} Double, {-Angle-} Double, {-Angle-} Double)] worldLines = [-   ("Ordinary", Geodetic (40*~degree) (30*~degree) _0 WGS84, Geodetic (30*~degree) (50*~degree) _0 WGS84,-      2128852.999*~meter, 115.19596706*~degree, 126.79044315*~degree),-   ("Over Pole", Geodetic (60*~degree) (0*~degree) _0 WGS84, Geodetic (60*~degree) (180*~degree) _0 WGS84,-      6695785.820*~meter, 0*~degree, 180*~degree),-   ("Equator to Pole", Geodetic (0*~degree) (0*~degree) _0 WGS84, Geodetic (90*~degree) (180*~degree) _0 WGS84,-      10001965.729*~meter, 0*~degree, 180*~degree)]+   ("Ordinary", Geodetic (40 * degree) (30 * degree) 0 WGS84, Geodetic (30 * degree) (50 * degree) 0 WGS84,+      2128852.999, 115.19596706 * degree, 126.79044315 * degree),+   ("Over Pole", Geodetic (60 * degree) (0 * degree) 0 WGS84, Geodetic (60 * degree) (180 * degree) 0 WGS84,+      6695785.820, 0 * degree, 180 * degree),+   ("Equator to Pole", Geodetic (0 * degree) (0 * degree) 0 WGS84, Geodetic (90 * degree) (180 * degree) 0 WGS84,+      10001965.729, 0 * degree, 180 * degree)]  -worldLineTests :: (String, Geodetic WGS84, Geodetic WGS84, Length Double, Dimensionless Double, Dimensionless Double) -> Test+worldLineTests :: (String, Geodetic WGS84, Geodetic WGS84, Double, Double, Double) -> Test worldLineTests (str, g1, g2, d, a, b) = testCase str $ HU.assertBool "" $ ok $ groundDistance g1 g2    where       ok Nothing = False       ok (Just (d1, a1, b1)) =-         abs (d - d1) < 0.01 *~ meter-         && abs (a - a1) < 0.01 *~ arcsecond-         && abs (b - b1) < 0.01 *~ arcsecond+         abs (d - d1) < 0.01+         && abs (a - a1) < 0.01 * arcsecond+         && abs (b - b1) < 0.01 * arcsecond  -- | Sample points for UK tests. The oracle for these values is the script at -- <http://www.movable-type.co.uk/scripts/latlong-convert-coords.html>, which uses -- the same Helmert transform as this library. Hence the results should match to within 30 cm. ukPoints :: [(String, Geodetic WGS84, Geodetic OSGB36)] ukPoints = [-   ("Greenwich",        Geodetic (dms 51 28 40.86) (dms 0 0 (-5.83)) _0 WGS84,-                        Geodetic (dms 51 28 39.00) (dms 0 0 0) _0 OSGB36),-   ("Edinburgh Castle", Geodetic (dms 55 56 56.30) (dms (-3) (-12) (-2.73)) _0 WGS84,-                        Geodetic (dms 55 56 56.51) (dms (-3) (-11) (-57.61)) _0 OSGB36),-   ("Lands End",        Geodetic (dms 50 03 56.68) (dms (-5) (-42) (-51.20)) _0 WGS84,-                        Geodetic (dms 50 03 54.51) (dms (-5) (-42) (-47.87)) _0 OSGB36),-   ("Gt. Yarmouth Pier",Geodetic (dms 52 36 29.33) (dms 1 44 27.79) _0 WGS84,-                        Geodetic (dms 52 36 27.84) (dms 1 44 34.52) _0 OSGB36),-   ("Stanhope",         Geodetic (dms 54 44 49.08) (dms (-2) 0 (-19.89)) _0 WGS84,-                        Geodetic (dms 54 44 48.71) (dms (-2) 0 (-14.41)) _0 OSGB36) ]+   ("Greenwich",        Geodetic (dms 51 28 40.86) (dms 0 0 (-5.83)) 0 WGS84,+                        Geodetic (dms 51 28 39.00) (dms 0 0 0) 0 OSGB36),+   ("Edinburgh Castle", Geodetic (dms 55 56 56.30) (dms (-3) (-12) (-2.73)) 0 WGS84,+                        Geodetic (dms 55 56 56.51) (dms (-3) (-11) (-57.61)) 0 OSGB36),+   ("Lands End",        Geodetic (dms 50 03 56.68) (dms (-5) (-42) (-51.20)) 0 WGS84,+                        Geodetic (dms 50 03 54.51) (dms (-5) (-42) (-47.87)) 0 OSGB36),+   ("Gt. Yarmouth Pier",Geodetic (dms 52 36 29.33) (dms 1 44 27.79) 0 WGS84,+                        Geodetic (dms 52 36 27.84) (dms 1 44 34.52) 0 OSGB36),+   ("Stanhope",         Geodetic (dms 54 44 49.08) (dms (-2) 0 (-19.89)) 0 WGS84,+                        Geodetic (dms 54 44 48.71) (dms (-2) 0 (-14.41)) 0 OSGB36) ]   @@ -201,19 +201,34 @@ prop_offset3 :: GridOffset -> Bool prop_offset3 delta = sameOffset delta0                                 (polarOffset (offsetDistance delta0) (offsetBearing delta))-   where delta0 = delta {deltaAltitude = 0 *~ meter}+   where delta0 = delta {deltaAltitude = 0}  -- | Given a grid point and an offset, applying the offset to the point gives a new point which -- is offset from the first point by the argument offset. prop_grid1 :: GridPoint (GridTM LocalEllipsoid) -> GridOffset -> Bool prop_grid1 p d = sameOffset d $ p `gridOffset` applyOffset d p -+-- | Check that using toGrid/fromGrid for TransverseMercator projection are inverses+-- | for negative latitudes near the coordinates 0,0+prop_tmGridInverse :: Bool+prop_tmGridInverse = +   let origin = Geodetic +         { latitude = 0 * degree+         , longitude = 0 * degree+         , geoAlt = 0+         , ellipsoid = WGS84+         }+       g = mkGridTM origin mempty 1+       testPoint = origin { latitude = (-1) * arcminute }+       tp1 = toGrid g testPoint+       tp2 = fromGrid tp1+   in tp2 `closeEnough` testPoint+    -- | Converting a UK grid reference to a GridPoint and back is a null operation. prop_ukGrid1 :: GridRef -> Bool prop_ukGrid1 (GridRef str) =    str ==-   (fromJust $ toUkGridReference ((length str P.- 2) `div` 2) $ fst $ fromJust $ fromUkGridReference str)+   (fromJust $ toUkGridReference ((length str - 2) `div` 2) $ fst $ fromJust $ fromUkGridReference str)  -- | UK Grid Reference points. The oracle for these points was the -- UK Grid Reference Finder (gridreferencefinder.com), retrieved on 26 Jan 2013.@@ -228,11 +243,14 @@    ("ND3804872787", 338048, 972787, 58.638518, -3.0688688,   "John O Groats"),    ("SC3915875189", 239158, 475189, 54.147275, -4.4641148,   "Douglas Harbour"),    ("ST1922474591", 319224, 174591, 51.464505, -3.1641741,   "Torchwood HQ"),-   ("SK3520736502", 435207, 336502, 52.924784, -1.4777486,   "Derby Cathedral")]+   ("SK3520736502", 435207, 336502, 52.924784, -1.4777486,   "Derby Cathedral"),+   ("TG5141013177", 651410, 313177, 52.657979 , 1.7160519,   "Caister Water Tower"),+   ("TG2623802646", 626238, 302646, 52.574548 , 1.3373749,   "Framingham")]+   -- Caister and Framingham are taken from Ordnance Survey worked examples.    where       convert (grid, x, y, lat, long, desc) =-         (grid, GridPoint (x *~ meter) (y *~ meter) (0 *~ meter) UkNationalGrid,-          Geodetic (lat *~ degree) (long *~ degree) (0 *~ meter) WGS84, desc)+         (grid, GridPoint (x) (y) (0) UkNationalGrid,+          Geodetic (lat * degree) (long * degree) (0) WGS84, desc)  type GridPointTest = (String, GridPoint UkNationalGrid, Geodetic WGS84, String) -> Test @@ -254,12 +272,12 @@ -- | Check that WGS84 to grid point works close enough for sample points. ukGridTest5 :: GridPointTest ukGridTest5 (_, gp, geo, testName) = testCase testName $ HU.assertBool ""-   $ offsetDistance (gridOffset gp $ toGrid UkNationalGrid $ toLocal OSGB36 geo) < 1 *~ meter+   $ offsetDistance (gridOffset gp $ toGrid UkNationalGrid $ toLocal OSGB36 geo) < 1   -- | Worked example for UK Geodetic to GridPoint, taken from "A Guide to Coordinate Systems in Great Britain" [1] ukTest :: Geodetic OSGB36-ukTest = Geodetic (dms 52 39 27.2531) (dms 1 43 4.5177) (0 *~ meter) OSGB36+ukTest = Geodetic (dms 52 39 27.2531) (dms 1 43 4.5177) (0) OSGB36  {-    v = 6.3885023333E+06@@ -280,22 +298,22 @@  -- | Standard stereographic grid for point tests in the Northern Hemisphere. stereoGridN :: GridStereo LocalEllipsoid-stereoGridN = mkGridStereo tangent origin (0.9999079 *~ one)+stereoGridN = mkGridStereo tangent origin (0.9999079)    where-      ellipse = LocalEllipsoid "Bessel 1841" (6377397.155 *~ metre) (299.15281 *~ one) mempty-      tangent = Geodetic (dms 52 9 22.178) (dms 5 23 15.500) (0 *~ meter) ellipse-      origin = GridOffset (155000 *~ metre) (463000 *~ metre) (0 *~ meter)+      ellipse = LocalEllipsoid "Bessel 1841" (6377397.155) (299.15281) mempty+      tangent = Geodetic (dms 52 9 22.178) (dms 5 23 15.500) (0) ellipse+      origin = GridOffset (155000) (463000) (0)   -- | Standard steregraphic grid for point tests in the Southern Hemisphere. -- -- This is the same as stereoGridN but with the tangent latitude and the false origin northings negated. stereoGridS :: GridStereo LocalEllipsoid-stereoGridS = mkGridStereo tangent origin (0.9999079 *~ one)+stereoGridS = mkGridStereo tangent origin (0.9999079)    where-      ellipse = LocalEllipsoid "Bessel 1841" (6377397.155 *~ metre) (299.15281 *~ one) mempty-      tangent = Geodetic (negate $ dms 52 9 22.178) (dms 5 23 15.500) (0 *~ meter) ellipse-      origin = GridOffset ((-155000) *~ metre) (463000 *~ metre) (0 *~ meter)+      ellipse = LocalEllipsoid "Bessel 1841" (6377397.155) (299.15281) mempty+      tangent = Geodetic (negate $ dms 52 9 22.178) (dms 5 23 15.500) (0) ellipse+      origin = GridOffset ((-155000)) (463000) (0)   -- | Data for the stereographic tests taken from@@ -303,31 +321,31 @@ stereographicToGridN :: Bool stereographicToGridN = sameGrid g1 g1'    where-      p1 = Geodetic (dms 53 0 0) (dms 6 0 0) (0 *~ meter) $ gridEllipsoid stereoGridN-      g1 = GridPoint (196105.283 *~ meter) (557057.739 *~ meter) (0 *~ meter) stereoGridN+      p1 = Geodetic (dms 53 0 0) (dms 6 0 0) (0) $ gridEllipsoid stereoGridN+      g1 = GridPoint (196105.283) (557057.739) (0) stereoGridN       g1' = toGrid stereoGridN p1  stereographicFromGridN :: Bool stereographicFromGridN = samePlace p1 p1'    where-      p1 = Geodetic (dms 53 0 0) (dms 6 0 0) (0 *~ meter) $ gridEllipsoid stereoGridN-      g1 = GridPoint (196105.283 *~ meter) (557057.739 *~ meter) (0 *~ meter) stereoGridN+      p1 = Geodetic (dms 53 0 0) (dms 6 0 0) (0) $ gridEllipsoid stereoGridN+      g1 = GridPoint (196105.283) (557057.739) (0) stereoGridN       p1' = fromGrid g1   stereographicToGridS :: Bool stereographicToGridS = sameGrid g1 g1'    where-      p1 = Geodetic (negate $ dms 53 0 0) (dms 6 0 0) (0 *~ meter) $ gridEllipsoid stereoGridS-      g1 = GridPoint ((-196105.283) *~ meter) (557057.739 *~ meter) (0 *~ meter) stereoGridS+      p1 = Geodetic (negate $ dms 53 0 0) (dms 6 0 0) (0) $ gridEllipsoid stereoGridS+      g1 = GridPoint ((-196105.283)) (557057.739) (0) stereoGridS       g1' = toGrid stereoGridS p1   stereographicFromGridS :: Bool stereographicFromGridS = samePlace p1 p1'    where-      p1 = Geodetic (negate $ dms 53 0 0) (dms 6 0 0) (0 *~ meter) $ gridEllipsoid stereoGridS-      g1 = GridPoint ((-196105.283) *~ meter) (557057.739 *~ meter) (0 *~ meter) stereoGridS+      p1 = Geodetic (negate $ dms 53 0 0) (dms 6 0 0) (0) $ gridEllipsoid stereoGridS+      g1 = GridPoint ((-196105.283)) (557057.739) (0) stereoGridS       p1' = fromGrid g1  @@ -345,7 +363,7 @@ prop_rayPath1 :: Ray WGS84 -> Bool prop_rayPath1 r@(Ray pt b e) =       samePlace pt pt1 && sameAngle b b1 && sameAngle e e1-   where (pt1,b1,e1) = pathFunc (getRay r) _0+   where (pt1,b1,e1) = pathFunc (getRay r) 0   type ContinuityTest e = Geodetic e -> Bearing -> Azimuth -> Distance -> Distance -> Property@@ -356,7 +374,7 @@ -- and have the property that any (point,bearing,azimuth) triple on -- the path will specify the same path with a distance offset. prop_pathContinuity :: (Ellipsoid e) =>-   (Geodetic e -> Angle Double -> Angle Double -> Path e) -> ContinuityTest e+   (Geodetic e -> Double -> Double -> Path e) -> ContinuityTest e prop_pathContinuity pf pt0 (Bearing b0) (Azimuth a0) (Distance d1) (Distance d2) =    counterexample (show ((pt2, Bearing b2, Azimuth a2), (pt3, Bearing b3, Azimuth a3))) $       pathValidAt path0 d1 && pathValidAt path0 d2 && pathValidAt path0 (d1+d2) ==>@@ -371,7 +389,7 @@  -- | For continuity testing of ground-based paths (azimuth & altitude always zero) -- where lower accuracy is required.-prop_pathContinuity1 :: (Ellipsoid e) => (Geodetic e -> Angle Double -> Path e) -> ContinuityTest1 e+prop_pathContinuity1 :: (Ellipsoid e) => (Geodetic e -> Double -> Path e) -> ContinuityTest1 e prop_pathContinuity1 pf pt0 (Bearing b0) (Distance2 d1) (Distance2 d2) =    counterexample (show ((pt2, Bearing b2), (pt3, Bearing b3))) $       pathValidAt path0 d1 && pathValidAt path0 d2 && pathValidAt path0 (d1+d2) ==>@@ -393,9 +411,9 @@ -- This is a test of bisection rather than rays. prop_rayBisect :: Ray WGS84 -> Altitude -> Bool prop_rayBisect r (Altitude height) =-   case bisect ray0 f (1 *~ centi meter) (0 *~ meter) (1000 *~ kilo meter) of+   case bisect ray0 f (1e-2) (0) (1000 * kilometer) of       Nothing -> False-      Just d -> let (g, _, _) = pathFunc ray0 d in abs (altitude g - height) < 1 *~ centi meter+      Just d -> let (g, _, _) = pathFunc ray0 d in abs (altitude g - height) < 1e-2    where       f g = compare (altitude g) height       ray0 = getRay r@@ -409,7 +427,7 @@ -- | Two rhumb paths intersect at the same place. prop_rhumbIntersect :: RhumbPaths2 -> Property prop_rhumbIntersect rp =-   case intersect _0 _0 (10.0 *~ centi meter) 100 path1 path2 of+   case intersect 0 0 (0.1) 100 path1 path2 of       Just (d1, d2) ->          let (pt1, _, _) = pathFunc path1 d1              (pt2, _, _) = pathFunc path2 d2