units.py 5.6 KB

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