category.py 11 KB

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