category.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  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(
  57. fget=_get_mtype, fset=_set_mtype, doc="Set or obtain raster data type"
  58. )
  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, ctypes.byref(self.c_cats))
  63. title = property(fget=_get_title, fset=_set_title, doc="Set or obtain raster title")
  64. def __str__(self):
  65. return self.__repr__()
  66. def __list__(self):
  67. cats = []
  68. for cat in self.__iter__():
  69. cats.append(cat)
  70. return cats
  71. def __dict__(self):
  72. diz = dict()
  73. for cat in self.__iter__():
  74. label, min_cat, max_cat = cat
  75. diz[(min_cat, max_cat)] = label
  76. return diz
  77. def __repr__(self):
  78. cats = []
  79. for cat in self.__iter__():
  80. cats.append(repr(cat))
  81. return "[{0}]".format(",\n ".join(cats))
  82. def _chk_index(self, index):
  83. if type(index) == str:
  84. try:
  85. index = self.labels().index(index)
  86. except ValueError:
  87. raise KeyError(index)
  88. return index
  89. def _chk_value(self, value):
  90. if type(value) == tuple:
  91. length = len(value)
  92. if length == 2:
  93. label, min_cat = value
  94. value = (label, min_cat, None)
  95. elif length < 2 or length > 3:
  96. raise TypeError("Tuple with a length that is not supported.")
  97. else:
  98. raise TypeError("Only Tuple are supported.")
  99. return value
  100. def __getitem__(self, index):
  101. return super(Category, self).__getitem__(self._chk_index(index))
  102. def __setitem__(self, index, value):
  103. return super(Category, self).__setitem__(
  104. self._chk_index(index), self._chk_value(value)
  105. )
  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(
  120. libraster.Rast_get_ith_cat(
  121. ctypes.byref(self.c_cats),
  122. index,
  123. ctypes.cast(min_cat, ctypes.c_void_p),
  124. ctypes.cast(max_cat, ctypes.c_void_p),
  125. self._gtype,
  126. )
  127. )
  128. # Manage C function Errors
  129. if lab == "":
  130. raise GrassError(_("Error executing: Rast_get_ith_cat"))
  131. if max_cat.contents.value == min_cat.contents.value:
  132. max_cat = None
  133. else:
  134. max_cat = max_cat.contents.value
  135. return lab, min_cat.contents.value, max_cat
  136. def _set_c_cat(self, label, min_cat, max_cat=None):
  137. """Adds the label for range min through max in category structure cats.
  138. int Rast_set_cat(const void * rast1,
  139. const void * rast2,
  140. const char * label,
  141. struct Categories * pcats,
  142. RASTER_MAP_TYPE data_type
  143. )
  144. """
  145. max_cat = min_cat if max_cat is None else max_cat
  146. min_cat = ctypes.pointer(RTYPE[self.mtype]["grass def"](min_cat))
  147. max_cat = ctypes.pointer(RTYPE[self.mtype]["grass def"](max_cat))
  148. err = libraster.Rast_set_cat(
  149. ctypes.cast(min_cat, ctypes.c_void_p),
  150. ctypes.cast(max_cat, ctypes.c_void_p),
  151. label,
  152. ctypes.byref(self.c_cats),
  153. self._gtype,
  154. )
  155. # Manage C function Errors
  156. if err == 1:
  157. return None
  158. elif err == 0:
  159. raise GrassError(_("Null value detected"))
  160. elif err == -1:
  161. raise GrassError(_("Error executing: Rast_set_cat"))
  162. def __del__(self):
  163. libraster.Rast_free_cats(ctypes.byref(self.c_cats))
  164. def get_cat(self, index):
  165. return self.__getitem__(index)
  166. def set_cat(self, index, value):
  167. if index is None:
  168. self.append(value)
  169. elif index < self.__len__():
  170. self.__setitem__(index, value)
  171. else:
  172. raise TypeError("Index outside range.")
  173. def reset(self):
  174. for i in range(len(self) - 1, -1, -1):
  175. del self[i]
  176. libraster.Rast_init_cats("", ctypes.byref(self.c_cats))
  177. def _read_cats(self):
  178. """Copy from the C struct to the list"""
  179. for i in range(self.c_cats.ncats):
  180. self.append(self._get_c_cat(i))
  181. def _write_cats(self):
  182. """Copy from the list data to the C struct"""
  183. # reset only the C struct
  184. libraster.Rast_init_cats("", ctypes.byref(self.c_cats))
  185. # write to the c struct
  186. for cat in self.__iter__():
  187. label, min_cat, max_cat = cat
  188. if max_cat is None:
  189. max_cat = min_cat
  190. self._set_c_cat(label, min_cat, max_cat)
  191. def read(self):
  192. """Read categories from a raster map
  193. The category file for raster map name in mapset is read into the
  194. cats structure. If there is an error reading the category file,
  195. a diagnostic message is printed.
  196. int Rast_read_cats(const char * name,
  197. const char * mapset,
  198. struct Categories * pcats
  199. )
  200. """
  201. self.reset()
  202. err = libraster.Rast_read_cats(
  203. self.name, self.mapset, ctypes.byref(self.c_cats)
  204. )
  205. if err == -1:
  206. raise GrassError("Can not read the categories.")
  207. # copy from C struct to list
  208. self._read_cats()
  209. def write(self):
  210. """Writes the category file for the raster map name in the current
  211. mapset from the cats structure.
  212. void Rast_write_cats(const char * name,
  213. struct Categories * cats
  214. )
  215. """
  216. # copy from list to C struct
  217. self._write_cats()
  218. # write to the map
  219. libraster.Rast_write_cats(self.name, ctypes.byref(self.c_cats))
  220. def copy(self, category):
  221. """Copy from another Category class
  222. :param category: Category class to be copied
  223. :type category: Category object
  224. """
  225. libraster.Rast_copy_cats(
  226. ctypes.byref(self.c_cats), ctypes.byref(category.c_cats) # to
  227. ) # from
  228. self._read_cats()
  229. def ncats(self):
  230. return self.__len__()
  231. def set_cats_fmt(self, fmt, m1, a1, m2, a2):
  232. """Not implemented yet.
  233. void Rast_set_cats_fmt()
  234. """
  235. # TODO: add
  236. raise ImplementationError("set_cats_fmt() is not implemented yet.")
  237. def read_rules(self, filename, sep=":"):
  238. """Copy categories from a rules file, default separetor is ':', the
  239. columns must be: min and/or max and label. ::
  240. 1:forest
  241. 2:road
  242. 3:urban
  243. 0.:0.5:forest
  244. 0.5:1.0:road
  245. 1.0:1.5:urban
  246. :param str filename: the name of file with categories rules
  247. :param str sep: the separator used to divide values and category
  248. """
  249. self.reset()
  250. with open(filename, "r") as f:
  251. for row in f.readlines():
  252. cat = row.strip().split(sep)
  253. if len(cat) == 2:
  254. label, min_cat = cat
  255. max_cat = None
  256. elif len(cat) == 3:
  257. label, min_cat, max_cat = cat
  258. else:
  259. raise TypeError("Row length is greater than 3")
  260. self.append((label, min_cat, max_cat))
  261. def write_rules(self, filename, sep=":"):
  262. """Copy categories from a rules file, default separetor is ':', the
  263. columns must be: min and/or max and label. ::
  264. 1:forest
  265. 2:road
  266. 3:urban
  267. 0.:0.5:forest
  268. 0.5:1.0:road
  269. 1.0:1.5:urban
  270. :param str filename: the name of file with categories rules
  271. :param str sep: the separator used to divide values and category
  272. """
  273. with open(filename, "w") as f:
  274. cats = []
  275. for cat in self.__iter__():
  276. if cat[-1] is None:
  277. cat = cat[:-1]
  278. cats.append(sep.join([str(i) for i in cat]))
  279. f.write("\n".join(cats))
  280. def sort(self):
  281. libraster.Rast_sort_cats(ctypes.byref(self.c_cats))
  282. def labels(self):
  283. return list(map(itemgetter(0), self))