__init__.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. #
  4. # Project: Plankton Toolbox. http://plankton-toolbox.org
  5. # Author: Arnold Andreasson, info@mellifica.se
  6. # Copyright (c) 2010-2012 SMHI, Swedish Meteorological and Hydrological Institute
  7. # License: MIT License as follows:
  8. #
  9. # Permission is hereby granted, free of charge, to any person obtaining a copy
  10. # of this software and associated documentation files (the "Software"), to deal
  11. # in the Software without restriction, including without limitation the rights
  12. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. # copies of the Software, and to permit persons to whom the Software is
  14. # furnished to do so, subject to the following conditions:
  15. #
  16. # The above copyright notice and this permission notice shall be included in
  17. # all copies or substantial portions of the Software.
  18. #
  19. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. # THE SOFTWARE.
  26. """
  27. This module contains an implementation of the "Gauss Conformal Projection
  28. (Transverse Mercator), Krügers Formulas" and a set of parameters for the most
  29. commonly used map projections in Sweden. The Gauss-Krüger formula transforms
  30. between geodetic coordinates and grid coordinates.
  31. Standard transformations are implemented as pure functions. For transformations
  32. to and from local projections the SwedishGeoPositionConverter class must be
  33. used. Note that WGS 84 is used when naming some of the standard transformation
  34. functions. A more correct name is SWEREF 99 (without TM or ddmm), but WGS 84 is
  35. one of many modern reference systems and well known by GPS users.
  36. Supported map projections are:
  37. - RT 90: rt90_5.0_gon_v, rt90_2.5_gon_v, rt90_0.0_gon_v, rt90_2.5_gon_o,
  38. rt90_5.0_gon_o.
  39. - RT90 Bessel 1841: bessel_rt90_7.5_gon_v, bessel_rt90_5.0_gon_v,
  40. bessel_rt90_2.5_gon_v, bessel_rt90_0.0_gon_v, bessel_rt90_2.5_gon_o,
  41. bessel_rt90_5.0_gon_o.
  42. - SWEREF99: sweref_99_tm, sweref_99_1200, sweref_99_1330, sweref_99_1500,
  43. sweref_99_1630, sweref_99_1800, sweref_99_1415, sweref_99_1545,
  44. sweref_99_1715, sweref_99_1845, sweref_99_2015, sweref_99_2145,
  45. sweref_99_2315.
  46. Used formula and parameters can be found here (in Swedish):
  47. http://www.lantmateriet.se/geodesi/
  48. """
  49. import math
  50. # Globals used to reuse converter objects.
  51. rt90_converter = None
  52. sweref99tm_converter = None
  53. def rt90_to_sweref99tm(x, y):
  54. """ Converts from RT 90 2.5 gon V to SWEREF 99 TM. """
  55. global rt90_converter
  56. global sweref99tm_converter
  57. if (rt90_converter == None):
  58. rt90_converter = SwedishGeoPositionConverter("rt90_2.5_gon_v")
  59. if (sweref99tm_converter == None):
  60. sweref99tm_converter = SwedishGeoPositionConverter("sweref_99_tm")
  61. lat, long = rt90_converter.gridToGeodetic(x, y)
  62. return sweref99tm_converter.geodeticToGrid(lat, long)
  63. def sweref99tm_to_rt90(n, e):
  64. """ Converts from SWEREF 99 TM to RT 90 2.5 gon V. """
  65. global rt90_converter
  66. global sweref99tm_converter
  67. if (rt90_converter == None):
  68. rt90_converter = SwedishGeoPositionConverter("rt90_2.5_gon_v")
  69. if (sweref99tm_converter == None):
  70. sweref99tm_converter = SwedishGeoPositionConverter("sweref_99_tm")
  71. lat, long = sweref99tm_converter.gridToGeodetic(n, e)
  72. return rt90_converter.geodeticToGrid(lat, long)
  73. def wgs84_to_rt90(lat, long):
  74. """ Converts from WGS 84 to RT 90 2.5 gon V. """
  75. global rt90_converter
  76. if (rt90_converter == None):
  77. rt90_converter = SwedishGeoPositionConverter("rt90_2.5_gon_v")
  78. return rt90_converter.geodeticToGrid(lat, long)
  79. def rt90_to_wgs84(x, y):
  80. """ Converts from RT 90 2.5 gon V to WGS 84. """
  81. global rt90_converter
  82. if (rt90_converter == None):
  83. rt90_converter = SwedishGeoPositionConverter("rt90_2.5_gon_v")
  84. return rt90_converter.gridToGeodetic(x, y)
  85. def wgs84_to_sweref99tm(lat, long):
  86. """ Converts from WGS 84 to SWEREF 99 TM. """
  87. global sweref99tm_converter
  88. if (sweref99tm_converter == None):
  89. sweref99tm_converter = SwedishGeoPositionConverter("sweref_99_tm")
  90. return sweref99tm_converter.geodeticToGrid(lat, long)
  91. def sweref99tm_to_wgs84(n, e):
  92. """ Converts from SWEREF 99 TM to WGS 84. """
  93. global sweref99tm_converter
  94. if (sweref99tm_converter == None):
  95. sweref99tm_converter = SwedishGeoPositionConverter("sweref_99_tm")
  96. return sweref99tm_converter.gridToGeodetic(n, e)
  97. class SwedishGeoPositionConverter(object):
  98. """
  99. Implementation of the Gauss-Kr�ger formula for transformations between
  100. geodetic and grid coordinates. Contains parameters for the most commonly
  101. used map projections in Sweden. These projections are based on the
  102. Bessel 1841, GRS 80 and the SWEREF 99 ellipsoids.
  103. """
  104. def __init__(self, projection = "sweref_99_tm"):
  105. """ """
  106. # Local variables.
  107. self._initialized = False
  108. self._axis = 0.0 # Semi-major axis of the ellipsoid.
  109. self._flattening = 0.0 # Flattening of the ellipsoid.
  110. self._central_meridian = 0.0 # Central meridian for the projection.
  111. self._lat_of_origin = 0.0 # Latitude of origin (not used).
  112. self._scale = 0.0 # Scale on central meridian.
  113. self._false_northing = 0.0 # Offset for origo.
  114. self._false_easting = 0.0 # Offset for origo.
  115. self._a_roof = 0.0
  116. self._A = 0.0
  117. self._B = 0.0
  118. self._C = 0.0
  119. self._D = 0.0
  120. self._beta1 = 0.0
  121. self._beta2 = 0.0
  122. self._beta3 = 0.0
  123. self._beta4 = 0.0
  124. self._delta1 = 0.0
  125. self._delta2 = 0.0
  126. self._delta3 = 0.0
  127. self._delta4 = 0.0
  128. self._Astar = 0.0
  129. self._Bstar = 0.0
  130. self._Cstar = 0.0
  131. self._Dstar = 0.0
  132. # Initial calculations.
  133. self._setProjection(projection)
  134. self._prepareEllipsoid()
  135. def geodeticToGrid(self, latitude, longitude):
  136. """
  137. Transformation from geodetic coordinates to grid coordinates.
  138. @param latitude
  139. @param longitude
  140. @return (north, east)
  141. (North corresponds to X in RT 90 and N in SWEREF 99.)
  142. (East corresponds to Y in RT 90 and E in SWEREF 99.)
  143. """
  144. if (self._initialized == False):
  145. return None
  146. deg_to_rad = math.pi / 180.0
  147. phi = latitude * deg_to_rad
  148. lambda_long = longitude * deg_to_rad
  149. lambda_zero = self._central_meridian * deg_to_rad
  150. phi_star = phi - math.sin(phi) * math.cos(phi) * (self._A + \
  151. self._B*math.pow(math.sin(phi), 2) + \
  152. self._C*math.pow(math.sin(phi), 4) + \
  153. self._D*math.pow(math.sin(phi), 6))
  154. delta_lambda = lambda_long - lambda_zero
  155. xi_prim = math.atan(math.tan(phi_star) / math.cos(delta_lambda))
  156. eta_prim = math.atanh(math.cos(phi_star) * math.sin(delta_lambda))
  157. north = self._scale * self._a_roof * (xi_prim + \
  158. self._beta1 * math.sin(2.0*xi_prim) * math.cosh(2.0*eta_prim) + \
  159. self._beta2 * math.sin(4.0*xi_prim) * math.cosh(4.0*eta_prim) + \
  160. self._beta3 * math.sin(6.0*xi_prim) * math.cosh(6.0*eta_prim) + \
  161. self._beta4 * math.sin(8.0*xi_prim) * math.cosh(8.0*eta_prim)) + \
  162. self._false_northing
  163. east = self._scale * self._a_roof * (eta_prim + \
  164. self._beta1 * math.cos(2.0*xi_prim) * math.sinh(2.0*eta_prim) + \
  165. self._beta2 * math.cos(4.0*xi_prim) * math.sinh(4.0*eta_prim) + \
  166. self._beta3 * math.cos(6.0*xi_prim) * math.sinh(6.0*eta_prim) + \
  167. self._beta4 * math.cos(8.0*xi_prim) * math.sinh(8.0*eta_prim)) + \
  168. self._false_easting
  169. # north = round(north * 1000.0) / 1000.0
  170. # east = round(east * 1000.0) / 1000.0
  171. return (north, east)
  172. def gridToGeodetic(self, north, east):
  173. """
  174. Transformation from grid coordinates to geodetic coordinates.
  175. @param north (corresponds to X in RT 90 and N in SWEREF 99.)
  176. @param east (corresponds to Y in RT 90 and E in SWEREF 99.)
  177. @return (latitude, longitude)
  178. """
  179. if (self._initialized == False):
  180. return None
  181. deg_to_rad = math.pi / 180
  182. lambda_zero = self._central_meridian * deg_to_rad
  183. xi = (north - self._false_northing) / (self._scale * self._a_roof)
  184. eta = (east - self._false_easting) / (self._scale * self._a_roof)
  185. xi_prim = xi - \
  186. self._delta1*math.sin(2.0*xi) * math.cosh(2.0*eta) - \
  187. self._delta2*math.sin(4.0*xi) * math.cosh(4.0*eta) - \
  188. self._delta3*math.sin(6.0*xi) * math.cosh(6.0*eta) - \
  189. self._delta4*math.sin(8.0*xi) * math.cosh(8.0*eta)
  190. eta_prim = eta - \
  191. self._delta1*math.cos(2.0*xi) * math.sinh(2.0*eta) - \
  192. self._delta2*math.cos(4.0*xi) * math.sinh(4.0*eta) - \
  193. self._delta3*math.cos(6.0*xi) * math.sinh(6.0*eta) - \
  194. self._delta4*math.cos(8.0*xi) * math.sinh(8.0*eta)
  195. phi_star = math.asin(math.sin(xi_prim) / math.cosh(eta_prim))
  196. delta_lambda = math.atan(math.sinh(eta_prim) / math.cos(xi_prim))
  197. lon_radian = lambda_zero + delta_lambda
  198. lat_radian = phi_star + math.sin(phi_star) * math.cos(phi_star) * \
  199. (self._Astar + \
  200. self._Bstar*math.pow(math.sin(phi_star), 2) + \
  201. self._Cstar*math.pow(math.sin(phi_star), 4) + \
  202. self._Dstar*math.pow(math.sin(phi_star), 6))
  203. lat = lat_radian * 180.0 / math.pi
  204. lon = lon_radian * 180.0 / math.pi
  205. return (lat, lon)
  206. def _prepareEllipsoid(self):
  207. """ Prepare calculations only related to the choosen ellipsoid. """
  208. if (self._initialized == False):
  209. return None
  210. e2 = self._flattening * (2.0 - self._flattening)
  211. n = self._flattening / (2.0 - self._flattening)
  212. self._a_roof = self._axis / (1.0 + n) * (1.0 + n*n/4.0 + n*n*n*n/64.0)
  213. # Prepare ellipsoid-based stuff for geodetic_to_grid.
  214. self._A = e2
  215. self._B = (5.0*e2*e2 - e2*e2*e2) / 6.0
  216. self._C = (104.0*e2*e2*e2 - 45.0*e2*e2*e2*e2) / 120.0
  217. self._D = (1237.0*e2*e2*e2*e2) / 1260.0
  218. self._beta1 = n/2.0 - 2.0*n*n/3.0 + 5.0*n*n*n/16.0 + 41.0*n*n*n*n/180.0
  219. self._beta2 = 13.0*n*n/48.0 - 3.0*n*n*n/5.0 + 557.0*n*n*n*n/1440.0
  220. self._beta3 = 61.0*n*n*n/240.0 - 103.0*n*n*n*n/140.0
  221. self._beta4 = 49561.0*n*n*n*n/161280.0
  222. # Prepare ellipsoid-based stuff for grid_to_geodetic.
  223. self._delta1 = n/2.0 - 2.0*n*n/3.0 + 37.0*n*n*n/96.0 - n*n*n*n/360.0
  224. self._delta2 = n*n/48.0 + n*n*n/15.0 - 437.0*n*n*n*n/1440.0
  225. self._delta3 = 17.0*n*n*n/480.0 - 37*n*n*n*n/840.0
  226. self._delta4 = 4397.0*n*n*n*n/161280.0
  227. self._Astar = e2 + e2*e2 + e2*e2*e2 + e2*e2*e2*e2
  228. self._Bstar = -(7.0*e2*e2 + 17.0*e2*e2*e2 + 30.0*e2*e2*e2*e2) / 6.0
  229. self._Cstar = (224.0*e2*e2*e2 + 889.0*e2*e2*e2*e2) / 120.0
  230. self._Dstar = -(4279.0*e2*e2*e2*e2) / 1260.0
  231. def _setProjection(self, projection):
  232. """
  233. Parameters for RT90 and SWEREF99TM.
  234. Parameters for RT90 are choosen to eliminate the differences between
  235. Bessel and GRS80-ellipsoids.
  236. Note: Bessel-variants should only be used if lat/long are given as
  237. RT90-lat/long based on the old Bessel 1841 ellipsoid.
  238. Parameter: projection (string). Must match if-statement.
  239. """
  240. # RT90 parameters, GRS 80 ellipsoid.
  241. if (projection == "rt90_7.5_gon_v"):
  242. self._grs80()
  243. self._central_meridian = 11.0 + 18.375/60.0
  244. self._scale = 1.000006000000
  245. self._false_northing = -667.282
  246. self._false_easting = 1500025.141
  247. self._initialized = True
  248. elif (projection == "rt90_5.0_gon_v"):
  249. self._grs80()
  250. self._central_meridian = 13.0 + 33.376/60.0
  251. self._scale = 1.000005800000
  252. self._false_northing = -667.130
  253. self._false_easting = 1500044.695
  254. self._initialized = True
  255. elif (projection == "rt90_2.5_gon_v"):
  256. self._grs80()
  257. self._central_meridian = 15.0 + 48.0/60.0 + 22.624306/3600.0
  258. self._scale = 1.00000561024
  259. self._false_northing = -667.711
  260. self._false_easting = 1500064.274
  261. self._initialized = True
  262. elif (projection == "rt90_0.0_gon_v"):
  263. self._grs80()
  264. self._central_meridian = 18.0 + 3.378/60.0
  265. self._scale = 1.000005400000
  266. self._false_northing = -668.844
  267. self._false_easting = 1500083.521
  268. self._initialized = True
  269. elif (projection == "rt90_2.5_gon_o"):
  270. self._grs80()
  271. self._central_meridian = 20.0 + 18.379/60.0
  272. self._scale = 1.000005200000
  273. self._false_northing = -670.706
  274. self._false_easting = 1500102.765
  275. self._initialized = True
  276. elif (projection == "rt90_5.0_gon_o"):
  277. self._grs80()
  278. self._central_meridian = 22.0 + 33.380/60.0
  279. self._scale = 1.000004900000
  280. self._false_northing = -672.557
  281. self._false_easting = 1500121.846
  282. self._initialized = True
  283. # RT90 parameters, Bessel 1841 ellipsoid.
  284. elif (projection == "bessel_rt90_7.5_gon_v"):
  285. self._bessel()
  286. self._central_meridian = 11.0 + 18.0/60.0 + 29.8/3600.0
  287. self._initialized = True
  288. elif (projection == "bessel_rt90_5.0_gon_v"):
  289. self._bessel()
  290. self._central_meridian = 13.0 + 33.0/60.0 + 29.8/3600.0
  291. self._initialized = True
  292. elif (projection == "bessel_rt90_2.5_gon_v"):
  293. self._bessel()
  294. self._central_meridian = 15.0 + 48.0/60.0 + 29.8/3600.0
  295. self._initialized = True
  296. elif (projection == "bessel_rt90_0.0_gon_v"):
  297. self._bessel()
  298. self._central_meridian = 18.0 + 3.0/60.0 + 29.8/3600.0
  299. self._initialized = True
  300. elif (projection == "bessel_rt90_2.5_gon_o"):
  301. self._bessel()
  302. self._central_meridian = 20.0 + 18.0/60.0 + 29.8/3600.0
  303. self._initialized = True
  304. elif (projection == "bessel_rt90_5.0_gon_o"):
  305. self._bessel()
  306. self._central_meridian = 22.0 + 33.0/60.0 + 29.8/3600.0
  307. self._initialized = True
  308. # SWEREF99TM and SWEREF99ddmm parameters.
  309. elif (projection == "sweref_99_tm"):
  310. self._sweref99()
  311. self._central_meridian = 15.00
  312. self._lat_of_origin = 0.0
  313. self._scale = 0.9996
  314. self._false_northing = 0.0
  315. self._false_easting = 500000.0
  316. self._initialized = True
  317. elif (projection == "sweref_99_1200"):
  318. self._sweref99()
  319. self._central_meridian = 12.00
  320. self._initialized = True
  321. elif (projection == "sweref_99_1330"):
  322. self._sweref99()
  323. self._central_meridian = 13.50
  324. self._initialized = True
  325. elif (projection == "sweref_99_1500"):
  326. self._sweref99()
  327. self._central_meridian = 15.00
  328. self._initialized = True
  329. elif (projection == "sweref_99_1630"):
  330. self._sweref99()
  331. self._central_meridian = 16.50
  332. self._initialized = True
  333. elif (projection == "sweref_99_1800"):
  334. self._sweref99()
  335. self._central_meridian = 18.00
  336. self._initialized = True
  337. elif (projection == "sweref_99_1415"):
  338. self._sweref99()
  339. self._central_meridian = 14.25
  340. self._initialized = True
  341. elif (projection == "sweref_99_1545"):
  342. self._sweref99()
  343. self._central_meridian = 15.75
  344. self._initialized = True
  345. elif (projection == "sweref_99_1715"):
  346. self._sweref99()
  347. self._central_meridian = 17.25
  348. self._initialized = True
  349. elif (projection == "sweref_99_1845"):
  350. self._sweref99()
  351. self._central_meridian = 18.75
  352. self._initialized = True
  353. elif (projection == "sweref_99_2015"):
  354. self._sweref99()
  355. self._central_meridian = 20.25
  356. self._initialized = True
  357. elif (projection == "sweref_99_2145"):
  358. self._sweref99()
  359. self._central_meridian = 21.75
  360. self._initialized = True
  361. elif (projection == "sweref_99_2315"):
  362. self._sweref99()
  363. self._central_meridian = 23.25
  364. self._initialized = True
  365. # For testing.
  366. elif (projection == "test_case"):
  367. # Test-case:
  368. # Lat: 66 0'0", long: 24 0'0".
  369. # X:1135809.413803 Y:555304.016555.
  370. self._axis = 6378137.0
  371. self._flattening = 1.0 / 298.257222101
  372. self._central_meridian = 13.0 + 35.0/60.0 + 7.692000/3600.0
  373. self._lat_of_origin = 0.0
  374. self._scale = 1.000002540000
  375. self._false_northing = -6226307.8640
  376. self._false_easting = 84182.8790
  377. self._initialized = True
  378. else:
  379. self._initialized = False
  380. def _grs80(self):
  381. """ Default parameters for the GRS80 ellipsoid. """
  382. self._axis = 6378137.0
  383. self._flattening = 1.0 / 298.257222101
  384. self._lat_of_origin = 0.0
  385. def _bessel(self):
  386. """ Default parameters for the Bessel 1841 ellipsoid. """
  387. self._axis = 6377397.155
  388. self._flattening = 1.0 / 299.1528128
  389. self._lat_of_origin = 0.0
  390. self._scale = 1.0
  391. self._false_northing = 0.0
  392. self._false_easting = 1500000.0
  393. def _sweref99(self):
  394. """ Default parameters for the SWEREF 99 ellipsoid. """
  395. self._axis = 6378137.0
  396. self._flattening = 1.0 / 298.257222101
  397. self._lat_of_origin = 0.0
  398. self._scale = 1.0
  399. self._false_northing = 0.0
  400. self._false_easting = 150000.0