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