units.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. """
  2. @package core.units
  3. @brief Units management
  4. .. todo::
  5. Probably will be replaced by Python ctypes fns in the near future(?)
  6. Usage:
  7. from core.units import Units
  8. Classes:
  9. - units::BaseUnits
  10. (C) 2009, 2011 by the GRASS Development Team
  11. This program is free software under the GNU General Public License
  12. (>=v2). Read the file COPYING that comes with GRASS for details.
  13. @author Martin Landa <landa.martin gmail.com>
  14. """
  15. import six
  16. import math
  17. if __name__ == '__main__':
  18. import os
  19. import sys
  20. class BaseUnits:
  21. def __init__(self):
  22. self._units = dict()
  23. self._units['length'] = {0: {'key': 'mu', 'label': _('map units')},
  24. 1: {'key': 'me', 'label': _('meters')},
  25. 2: {'key': 'km', 'label': _('kilometers')},
  26. 3: {'key': 'mi', 'label': _('miles')},
  27. 4: {'key': 'ft', 'label': _('feet')}}
  28. self._units['area'] = {0: {'key': 'mu', 'label': _('sq map units')},
  29. 1: {'key': 'me', 'label': _('sq meters')},
  30. 2: {'key': 'km', 'label': _('sq kilometers')},
  31. 3: {'key': 'ar', 'label': _('acres')},
  32. 4: {'key': 'ht', 'label': _('hectares')}}
  33. def GetUnitsList(self, type):
  34. """Get list of units (their labels)
  35. :param type: units type ('length' or 'area')
  36. :return: list of units labels
  37. """
  38. result = list()
  39. try:
  40. keys = sorted(self._units[type].keys())
  41. for idx in keys:
  42. result.append(self._units[type][idx]['label'])
  43. except KeyError:
  44. pass
  45. return result
  46. def GetUnitsKey(self, type, index):
  47. """Get units key based on index
  48. :param type: units type ('length' or 'area')
  49. :param index: units index
  50. """
  51. return self._units[type][index]['key']
  52. def GetUnitsIndex(self, type, key):
  53. """Get units index based on key
  54. :param type: units type ('length' or 'area')
  55. :param key: units key, e.g. 'me' for meters
  56. :return: index
  57. """
  58. for k, u in six.iteritems(self._units[type]):
  59. if u['key'] == key:
  60. return k
  61. return 0
  62. Units = BaseUnits()
  63. def ConvertValue(value, type, units):
  64. """Convert value from map units to given units
  65. Inspired by vector/v.to.db/units.c
  66. :param value: value to be converted
  67. :param type: units type ('length', 'area')
  68. :param unit: destination units
  69. """
  70. # get map units
  71. # TODO
  72. f = 1
  73. if type == 'length':
  74. if units == 'me':
  75. f = 1.0
  76. elif units == 'km':
  77. f = 1.0e-3
  78. elif units == 'mi':
  79. f = 6.21371192237334e-4
  80. elif units == 'ft':
  81. f = 3.28083989501312
  82. else: # -> area
  83. if units == 'me':
  84. f = 1.0
  85. elif units == 'km':
  86. f = 1.0e-6
  87. elif units == 'mi':
  88. f = 3.86102158542446e-7
  89. elif units == 'ft':
  90. f = 10.7639104167097
  91. elif units == 'ar':
  92. f = 2.47105381467165e-4
  93. elif units == 'ht':
  94. f = 1.0e-4
  95. return f * value
  96. def formatDist(distance, mapunits):
  97. """Formats length numbers and units in a nice way.
  98. Formats length numbers and units as a function of length.
  99. >>> formatDist(20.56915, 'metres')
  100. (20.57, 'm')
  101. >>> formatDist(6983.4591, 'metres')
  102. (6.983, 'km')
  103. >>> formatDist(0.59, 'feet')
  104. (0.59, 'ft')
  105. >>> formatDist(8562, 'feet')
  106. (1.622, 'miles')
  107. >>> formatDist(0.48963, 'degrees')
  108. (29.38, 'min')
  109. >>> formatDist(20.2546, 'degrees')
  110. (20.25, 'deg')
  111. >>> formatDist(82.146, 'unknown')
  112. (82.15, 'units')
  113. Accepted map units are 'meters', 'metres', 'feet', 'degree'.
  114. Returns 'units' instead of unrecognized units.
  115. :param distance: map units
  116. :param mapunits: map units
  117. From code by Hamish Bowman Grass Development Team 2006.
  118. """
  119. if mapunits == 'metres':
  120. mapunits = 'meters'
  121. outunits = mapunits
  122. distance = float(distance)
  123. divisor = 1.0
  124. # figure out which units to use
  125. if mapunits == 'meters':
  126. if distance > 2500.0:
  127. outunits = 'km'
  128. divisor = 1000.0
  129. else:
  130. outunits = 'm'
  131. elif mapunits == 'feet':
  132. # nano-bug: we match any "feet", but US Survey feet is really
  133. # 5279.9894 per statute mile, or 10.6' per 1000 miles. As >1000
  134. # miles the tick markers are rounded to the nearest 10th of a
  135. # mile (528'), the difference in foot flavours is ignored.
  136. if distance > 5280.0:
  137. outunits = 'miles'
  138. divisor = 5280.0
  139. else:
  140. outunits = 'ft'
  141. elif 'degree' in mapunits:
  142. # was: 'degree' in mapunits and not haveCtypes (for unknown reason)
  143. if distance < 1:
  144. outunits = 'min'
  145. divisor = (1 / 60.0)
  146. else:
  147. outunits = 'deg'
  148. else:
  149. return (distance, 'units')
  150. # format numbers in a nice way
  151. if (distance / divisor) >= 2500.0:
  152. outdistance = round(distance / divisor)
  153. elif (distance / divisor) >= 1000.0:
  154. outdistance = round(distance / divisor, 1)
  155. elif (distance / divisor) > 0.0:
  156. outdistance = round(distance / divisor,
  157. int(math.ceil(3 - math.log10(distance / divisor))))
  158. else:
  159. outdistance = float(distance / divisor)
  160. return (outdistance, outunits)
  161. def doc_test():
  162. """Tests the module using doctest
  163. :return: a number of failed tests
  164. """
  165. import doctest
  166. from core.utils import do_doctest_gettext_workaround
  167. do_doctest_gettext_workaround()
  168. return doctest.testmod().failed
  169. if __name__ == '__main__':
  170. sys.exit(doc_test())