category.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on Thu Jun 28 17:44:14 2012
  4. @author: pietro
  5. """
  6. import ctypes
  7. from operator import itemgetter
  8. import grass.lib.raster as libraster
  9. from grass.exceptions import ImplementationError
  10. from grass.pygrass.errors import GrassError
  11. from grass.pygrass.utils import decode
  12. from grass.pygrass.raster.raster_type import TYPE as RTYPE
  13. class Category(list):
  14. """
  15. I would like to add the following functions:
  16. Getting the umber of cats:
  17. Rast_number_of_cats() <- Important for ith access
  18. Getting and setting the title:
  19. Rast_get_cats_title()
  20. Rast_set_cats_title()
  21. Do not use these functions for category access:
  22. Rast_get_cat()
  23. and the specialized types for CELL, FCELL and DCELL.
  24. Since these functions are working on hidden static buffer.
  25. Use the ith-get methods:
  26. Rast_get_ith_c_cat()
  27. Rast_get_ith_f_cat()
  28. Rast_get_ith_d_cat()
  29. This can be implemented using an iterator too. So that the category object
  30. provides the [] access operator to the categories, returning a tuple
  31. (label, min, max).
  32. Using this, the category object must be aware of its raster map type.
  33. Set categories using:
  34. Rast_set_c_cat()
  35. Rast_set_f_cat()
  36. Rast_set_d_cat()
  37. Misc:
  38. Rast_sort_cats()
  39. Rast_copy_cats() <- This should be wrapped so that categories from an
  40. existing Python category class are copied.
  41. """
  42. def __init__(self, name, mapset='', mtype='CELL', *args, **kargs):
  43. self.name = name
  44. self.mapset = mapset
  45. self.c_cats = libraster.Categories()
  46. libraster.Rast_init_cats("", ctypes.byref(self.c_cats))
  47. self._mtype = mtype
  48. self._gtype = None if mtype is None else RTYPE[mtype]['grass type']
  49. super(Category, self).__init__(*args, **kargs)
  50. def _get_mtype(self):
  51. return self._mtype
  52. def _set_mtype(self, mtype):
  53. if mtype.upper() not in ('CELL', 'FCELL', 'DCELL'):
  54. raise ValueError(_("Raster type: {0} not supported".format(mtype)))
  55. self._mtype = mtype
  56. self._gtype = RTYPE[self.mtype]['grass type']
  57. mtype = property(fget=_get_mtype, fset=_set_mtype,
  58. doc="Set or obtain raster data type")
  59. def _get_title(self):
  60. return libraster.Rast_get_cats_title(ctypes.byref(self.c_cats))
  61. def _set_title(self, newtitle):
  62. return libraster.Rast_set_cats_title(newtitle,
  63. ctypes.byref(self.c_cats))
  64. title = property(fget=_get_title, fset=_set_title,
  65. doc="Set or obtain raster title")
  66. def __str__(self):
  67. return self.__repr__()
  68. def __list__(self):
  69. cats = []
  70. for cat in self.__iter__():
  71. cats.append(cat)
  72. return cats
  73. def __dict__(self):
  74. diz = dict()
  75. for cat in self.__iter__():
  76. label, min_cat, max_cat = cat
  77. diz[(min_cat, max_cat)] = label
  78. return diz
  79. def __repr__(self):
  80. cats = []
  81. for cat in self.__iter__():
  82. cats.append(repr(cat))
  83. return "[{0}]".format(',\n '.join(cats))
  84. def _chk_index(self, index):
  85. if type(index) == str:
  86. try:
  87. index = self.labels().index(index)
  88. except ValueError:
  89. raise KeyError(index)
  90. return index
  91. def _chk_value(self, value):
  92. if type(value) == tuple:
  93. length = len(value)
  94. if length == 2:
  95. label, min_cat = value
  96. value = (label, min_cat, None)
  97. elif length < 2 or length > 3:
  98. raise TypeError('Tuple with a length that is not supported.')
  99. else:
  100. raise TypeError('Only Tuple are supported.')
  101. return value
  102. def __getitem__(self, index):
  103. return super(Category, self).__getitem__(self._chk_index(index))
  104. def __setitem__(self, index, value):
  105. return super(Category, self).__setitem__(self._chk_index(index),
  106. self._chk_value(value))
  107. def _get_c_cat(self, index):
  108. """Returns i-th description and i-th data range from the list of
  109. category descriptions with corresponding data ranges. end points of
  110. data interval.
  111. Rast_get_ith_cat(const struct Categories * pcats,
  112. int i,
  113. void * rast1,
  114. void * rast2,
  115. RASTER_MAP_TYPE data_type
  116. )
  117. """
  118. min_cat = ctypes.pointer(RTYPE[self.mtype]['grass def']())
  119. max_cat = ctypes.pointer(RTYPE[self.mtype]['grass def']())
  120. lab = decode(libraster.Rast_get_ith_cat(ctypes.byref(self.c_cats),
  121. index,
  122. ctypes.cast(min_cat, ctypes.c_void_p),
  123. ctypes.cast(max_cat, ctypes.c_void_p),
  124. self._gtype))
  125. # Manage C function Errors
  126. if lab == '':
  127. raise GrassError(_("Error executing: Rast_get_ith_cat"))
  128. if max_cat.contents.value == min_cat.contents.value:
  129. max_cat = None
  130. else:
  131. max_cat = max_cat.contents.value
  132. return lab, min_cat.contents.value, max_cat
  133. def _set_c_cat(self, label, min_cat, max_cat=None):
  134. """Adds the label for range min through max in category structure cats.
  135. int Rast_set_cat(const void * rast1,
  136. const void * rast2,
  137. const char * label,
  138. struct Categories * pcats,
  139. RASTER_MAP_TYPE data_type
  140. )
  141. """
  142. max_cat = min_cat if max_cat is None else max_cat
  143. min_cat = ctypes.pointer(RTYPE[self.mtype]['grass def'](min_cat))
  144. max_cat = ctypes.pointer(RTYPE[self.mtype]['grass def'](max_cat))
  145. err = libraster.Rast_set_cat(ctypes.cast(min_cat, ctypes.c_void_p),
  146. ctypes.cast(max_cat, ctypes.c_void_p),
  147. label,
  148. ctypes.byref(self.c_cats), self._gtype)
  149. # Manage C function Errors
  150. if err == 1:
  151. return None
  152. elif err == 0:
  153. raise GrassError(_("Null value detected"))
  154. elif err == -1:
  155. raise GrassError(_("Error executing: Rast_set_cat"))
  156. def __del__(self):
  157. libraster.Rast_free_cats(ctypes.byref(self.c_cats))
  158. def get_cat(self, index):
  159. return self.__getitem__(index)
  160. def set_cat(self, index, value):
  161. if index is None:
  162. self.append(value)
  163. elif index < self.__len__():
  164. self.__setitem__(index, value)
  165. else:
  166. raise TypeError("Index outside range.")
  167. def reset(self):
  168. for i in range(len(self) - 1, -1, -1):
  169. del(self[i])
  170. libraster.Rast_init_cats("", ctypes.byref(self.c_cats))
  171. def _read_cats(self):
  172. """Copy from the C struct to the list"""
  173. for i in range(self.c_cats.ncats):
  174. self.append(self._get_c_cat(i))
  175. def _write_cats(self):
  176. """Copy from the list data to the C struct"""
  177. # reset only the C struct
  178. libraster.Rast_init_cats("", ctypes.byref(self.c_cats))
  179. # write to the c struct
  180. for cat in self.__iter__():
  181. label, min_cat, max_cat = cat
  182. if max_cat is None:
  183. max_cat = min_cat
  184. self._set_c_cat(label, min_cat, max_cat)
  185. def read(self):
  186. """Read categories from a raster map
  187. The category file for raster map name in mapset is read into the
  188. cats structure. If there is an error reading the category file,
  189. a diagnostic message is printed.
  190. int Rast_read_cats(const char * name,
  191. const char * mapset,
  192. struct Categories * pcats
  193. )
  194. """
  195. self.reset()
  196. err = libraster.Rast_read_cats(self.name, self.mapset,
  197. ctypes.byref(self.c_cats))
  198. if err == -1:
  199. raise GrassError("Can not read the categories.")
  200. # copy from C struct to list
  201. self._read_cats()
  202. def write(self):
  203. """Writes the category file for the raster map name in the current
  204. mapset from the cats structure.
  205. void Rast_write_cats(const char * name,
  206. struct Categories * cats
  207. )
  208. """
  209. # copy from list to C struct
  210. self._write_cats()
  211. # write to the map
  212. libraster.Rast_write_cats(self.name, ctypes.byref(self.c_cats))
  213. def copy(self, category):
  214. """Copy from another Category class
  215. :param category: Category class to be copied
  216. :type category: Category object
  217. """
  218. libraster.Rast_copy_cats(ctypes.byref(self.c_cats), # to
  219. ctypes.byref(category.c_cats)) # from
  220. self._read_cats()
  221. def ncats(self):
  222. return self.__len__()
  223. def set_cats_fmt(self, fmt, m1, a1, m2, a2):
  224. """Not implemented yet.
  225. void Rast_set_cats_fmt()
  226. """
  227. # TODO: add
  228. raise ImplementationError("set_cats_fmt() is not implemented yet.")
  229. def read_rules(self, filename, sep=':'):
  230. """Copy categories from a rules file, default separetor is ':', the
  231. columns must be: min and/or max and label. ::
  232. 1:forest
  233. 2:road
  234. 3:urban
  235. 0.:0.5:forest
  236. 0.5:1.0:road
  237. 1.0:1.5:urban
  238. :param str filename: the name of file with categories rules
  239. :param str sep: the separator used to divide values and category
  240. """
  241. self.reset()
  242. with open(filename, 'r') as f:
  243. for row in f.readlines():
  244. cat = row.strip().split(sep)
  245. if len(cat) == 2:
  246. label, min_cat = cat
  247. max_cat = None
  248. elif len(cat) == 3:
  249. label, min_cat, max_cat = cat
  250. else:
  251. raise TypeError("Row length is greater than 3")
  252. self.append((label, min_cat, max_cat))
  253. def write_rules(self, filename, sep=':'):
  254. """Copy categories from a rules file, default separetor is ':', the
  255. columns must be: min and/or max and label. ::
  256. 1:forest
  257. 2:road
  258. 3:urban
  259. 0.:0.5:forest
  260. 0.5:1.0:road
  261. 1.0:1.5:urban
  262. :param str filename: the name of file with categories rules
  263. :param str sep: the separator used to divide values and category
  264. """
  265. with open(filename, 'w') as f:
  266. cats = []
  267. for cat in self.__iter__():
  268. if cat[-1] is None:
  269. cat = cat[:-1]
  270. cats.append(sep.join([str(i) for i in cat]))
  271. f.write('\n'.join(cats))
  272. def sort(self):
  273. libraster.Rast_sort_cats(ctypes.byref(self.c_cats))
  274. def labels(self):
  275. return list(map(itemgetter(0), self))