category.py 11 KB

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