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.pygrass.errors import GrassError
  10. from grass.pygrass.raster.raster_type import TYPE as RTYPE
  11. class Category(list):
  12. """
  13. I would like to add the following functions:
  14. Getting the umber of cats:
  15. Rast_number_of_cats() <- Important for ith access
  16. Getting and setting the title:
  17. Rast_get_cats_title()
  18. Rast_set_cats_title()
  19. Do not use these functions for category access:
  20. Rast_get_cat()
  21. and the specialized types for CELL, FCELL and DCELL.
  22. Since these functions are working on hidden static buffer.
  23. Use the ith-get methods:
  24. Rast_get_ith_c_cat()
  25. Rast_get_ith_f_cat()
  26. Rast_get_ith_d_cat()
  27. This can be implemented using an iterator too. So that the category object
  28. provides the [] access operator to the categories, returning a tuple
  29. (label, min, max).
  30. Using this, the category object must be aware of its raster map type.
  31. Set categories using:
  32. Rast_set_c_cat()
  33. Rast_set_f_cat()
  34. Rast_set_d_cat()
  35. Misc:
  36. Rast_sort_cats()
  37. Rast_copy_cats() <- This should be wrapped so that categories from an
  38. existing Python category class are copied.
  39. """
  40. def __init__(self, name, mapset='', mtype='CELL', *args, **kargs):
  41. self.name = name
  42. self.mapset = mapset
  43. self.c_cats = libraster.Categories()
  44. libraster.Rast_init_cats("", ctypes.byref(self.c_cats))
  45. self._mtype = mtype
  46. self._gtype = None if mtype is None else RTYPE[mtype]['grass type']
  47. super(Category, self).__init__(*args, **kargs)
  48. def _get_mtype(self):
  49. return self._mtype
  50. def _set_mtype(self, mtype):
  51. if mtype.upper() not in ('CELL', 'FCELL', 'DCELL'):
  52. #fatal(_("Raser type: {0} not supported".format(mtype) ) )
  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 = 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. pass
  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. #import pdb; pdb.set_trace()
  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))