{- | The following is based on equations in Section 1.4.7.1 in OGP Surveying and Positioning Guidance Note number 7, part 2 – August 2006 -} {-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-} module Geodetics.Stereographic ( GridStereo (gridTangent, gridOrigin, gridScale), mkGridStereo ) where import Geodetics.Ellipsoids import Geodetics.Geodetic import Geodetics.Grid import Numeric.Units.Dimensional.Prelude import Prelude () -- | 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. -- 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 } 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 tangent origin scale = GridStereo { gridTangent = tangent, gridOrigin = origin, gridScale = scale, gridR = r, gridN = n, gridC = c, gridSin = sinLatC1, gridCos = sqrt $ _1 - sinLatC1 * sinLatC1, gridLatC = asin sinLatC1, gridG = g, gridH = h } where -- 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. 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) w1 = (s1 * s2 ** e) ** n 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 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 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) 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 fromGrid gp = {- trace ( -- Remove comment brackets for debugging. "fromGrid values:\n i = " ++ show i ++ "\n j = " ++ show j ++ "\n longC = " ++ show longC ++ "\n long = " ++ show long ++ "\n latC = " ++ show latC ++ "\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. GridPoint east north height _ = applyOffset (offsetNegate $ gridOrigin grid) gp east' = east north' = north grid = gridBasis gp 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 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) 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 gridEllipsoid = ellipsoid . gridTangent