"""
Created on Thu Jun 28 17:44:14 2012
@author: pietro
"""
import ctypes
from operator import itemgetter
from pathlib import Path
import grass.lib.raster as libraster
from grass.exceptions import ImplementationError
from grass.pygrass.errors import GrassError
from grass.pygrass.utils import decode
from grass.pygrass.raster.raster_type import TYPE as RTYPE
[docs]class Category(list):
"""
I would like to add the following functions:
Getting the umber of cats:
Rast_number_of_cats() <- Important for ith access
Getting and setting the title:
Rast_get_cats_title()
Rast_set_cats_title()
Do not use these functions for category access:
Rast_get_cat()
and the specialized types for CELL, FCELL and DCELL.
Since these functions are working on hidden static buffer.
Use the ith-get methods:
Rast_get_ith_c_cat()
Rast_get_ith_f_cat()
Rast_get_ith_d_cat()
This can be implemented using an iterator too. So that the category object
provides the [] access operator to the categories, returning a tuple
(label, min, max).
Using this, the category object must be aware of its raster map type.
Set categories using:
Rast_set_c_cat()
Rast_set_f_cat()
Rast_set_d_cat()
Misc:
Rast_sort_cats()
Rast_copy_cats() <- This should be wrapped so that categories from an
existing Python category class are copied.
"""
def __init__(self, name, mapset="", mtype="CELL", *args, **kargs):
self.name = name
self.mapset = mapset
self.c_cats = libraster.Categories()
libraster.Rast_init_cats("", ctypes.byref(self.c_cats))
self._mtype = mtype
self._gtype = None if mtype is None else RTYPE[mtype]["grass type"]
super().__init__(*args, **kargs)
def _get_mtype(self):
return self._mtype
def _set_mtype(self, mtype):
if mtype.upper() not in {"CELL", "FCELL", "DCELL"}:
raise ValueError(_("Raster type: {0} not supported").format(mtype))
self._mtype = mtype
self._gtype = RTYPE[self.mtype]["grass type"]
mtype = property(
fget=_get_mtype, fset=_set_mtype, doc="Set or obtain raster data type"
)
def _get_title(self):
return libraster.Rast_get_cats_title(ctypes.byref(self.c_cats))
def _set_title(self, newtitle):
return libraster.Rast_set_cats_title(newtitle, ctypes.byref(self.c_cats))
title = property(fget=_get_title, fset=_set_title, doc="Set or obtain raster title")
def __str__(self):
return self.__repr__()
def __list__(self):
cats = []
for cat in self.__iter__():
cats.append(cat)
return cats
def __dict__(self):
diz = {}
for cat in self.__iter__():
label, min_cat, max_cat = cat
diz[min_cat, max_cat] = label
return diz
def __repr__(self):
cats = []
for cat in self.__iter__():
cats.append(repr(cat))
return "[{0}]".format(",\n ".join(cats))
def _chk_index(self, index):
if isinstance(index, str):
try:
index = self.labels().index(index)
except ValueError:
raise KeyError(index)
return index
def _chk_value(self, value):
if isinstance(value, tuple):
length = len(value)
if length == 2:
label, min_cat = value
value = (label, min_cat, None)
elif length < 2 or length > 3:
msg = "Tuple with a length that is not supported."
raise TypeError(msg)
else:
msg = "Only tuples are supported."
raise TypeError(msg)
return value
def __getitem__(self, index):
return super().__getitem__(self._chk_index(index))
def __setitem__(self, index, value):
return super().__setitem__(self._chk_index(index), self._chk_value(value))
def _get_c_cat(self, index):
"""Returns i-th description and i-th data range from the list of
category descriptions with corresponding data ranges. end points of
data interval.
Rast_get_ith_cat(const struct Categories * pcats,
int i,
void * rast1,
void * rast2,
RASTER_MAP_TYPE data_type
)
"""
min_cat = ctypes.pointer(RTYPE[self.mtype]["grass def"]())
max_cat = ctypes.pointer(RTYPE[self.mtype]["grass def"]())
lab = decode(
libraster.Rast_get_ith_cat(
ctypes.byref(self.c_cats),
index,
ctypes.cast(min_cat, ctypes.c_void_p),
ctypes.cast(max_cat, ctypes.c_void_p),
self._gtype,
)
)
# Manage C function Errors
if lab == "":
raise GrassError(_("Error executing: Rast_get_ith_cat"))
if max_cat.contents.value == min_cat.contents.value:
max_cat = None
else:
max_cat = max_cat.contents.value
return lab, min_cat.contents.value, max_cat
def _set_c_cat(self, label, min_cat, max_cat=None):
"""Adds the label for range min through max in category structure cats.
int Rast_set_cat(const void * rast1,
const void * rast2,
const char * label,
struct Categories * pcats,
RASTER_MAP_TYPE data_type
)
"""
max_cat = min_cat if max_cat is None else max_cat
min_cat = ctypes.pointer(RTYPE[self.mtype]["grass def"](min_cat))
max_cat = ctypes.pointer(RTYPE[self.mtype]["grass def"](max_cat))
err = libraster.Rast_set_cat(
ctypes.cast(min_cat, ctypes.c_void_p),
ctypes.cast(max_cat, ctypes.c_void_p),
label,
ctypes.byref(self.c_cats),
self._gtype,
)
# Manage C function Errors
if err == 1:
return None
if err == 0:
raise GrassError(_("Null value detected"))
if err == -1:
raise GrassError(_("Error executing: Rast_set_cat"))
def __del__(self):
libraster.Rast_free_cats(ctypes.byref(self.c_cats))
[docs] def get_cat(self, index):
return self[index]
[docs] def set_cat(self, index, value):
if index is None:
self.append(value)
elif index < (len(self)):
self[index] = value
else:
msg = "Index outside range."
raise TypeError(msg)
[docs] def reset(self):
for i in range(len(self) - 1, -1, -1):
del self[i]
libraster.Rast_init_cats("", ctypes.byref(self.c_cats))
def _read_cats(self):
"""Copy from the C struct to the list"""
for i in range(self.c_cats.ncats):
self.append(self._get_c_cat(i))
def _write_cats(self):
"""Copy from the list data to the C struct"""
# reset only the C struct
libraster.Rast_init_cats("", ctypes.byref(self.c_cats))
# write to the c struct
for cat in iter(self):
label, min_cat, max_cat = cat
if max_cat is None:
max_cat = min_cat
self._set_c_cat(label, min_cat, max_cat)
[docs] def read(self):
"""Read categories from a raster map
The category file for raster map name in mapset is read into the
cats structure. If there is an error reading the category file,
a diagnostic message is printed.
int Rast_read_cats(const char * name,
const char * mapset,
struct Categories * pcats
)
"""
self.reset()
err = libraster.Rast_read_cats(
self.name, self.mapset, ctypes.byref(self.c_cats)
)
if err == -1:
msg = "Can not read the categories."
raise GrassError(msg)
# copy from C struct to list
self._read_cats()
[docs] def write(self):
"""Writes the category file for the raster map name in the current
mapset from the cats structure.
void Rast_write_cats(const char * name,
struct Categories * cats
)
"""
# copy from list to C struct
self._write_cats()
# write to the map
libraster.Rast_write_cats(self.name, ctypes.byref(self.c_cats))
[docs] def copy(self, category):
"""Copy from another Category class
:param category: Category class to be copied
:type category: Category object
"""
libraster.Rast_copy_cats(
ctypes.byref(self.c_cats), ctypes.byref(category.c_cats) # to
) # from
self._read_cats()
[docs] def ncats(self):
return len(self)
[docs] def set_cats_fmt(self, fmt, m1, a1, m2, a2):
"""Not implemented yet.
void Rast_set_cats_fmt()
"""
# TODO: add
msg = f"{self.set_cats_fmt.__name__}() is not implemented yet."
raise ImplementationError(msg)
[docs] def read_rules(self, filename, sep=":"):
"""Copy categories from a rules file, default separator is ':', the
columns must be: min and/or max and label. ::
1:forest
2:road
3:urban
0.:0.5:forest
0.5:1.0:road
1.0:1.5:urban
:param str filename: the name of file with categories rules
:param str sep: the separator used to divide values and category
"""
self.reset()
with open(filename) as f:
for row in f:
cat = row.strip().split(sep)
if len(cat) == 2:
label, min_cat = cat
max_cat = None
elif len(cat) == 3:
label, min_cat, max_cat = cat
else:
msg = "Row length is greater than 3"
raise TypeError(msg)
self.append((label, min_cat, max_cat))
[docs] def write_rules(self, filename, sep=":"):
"""Copy categories from a rules file, default separator is ':', the
columns must be: min and/or max and label. ::
1:forest
2:road
3:urban
0.:0.5:forest
0.5:1.0:road
1.0:1.5:urban
:param str filename: the name of file with categories rules
:param str sep: the separator used to divide values and category
"""
cats = []
for cat in iter(self):
if cat[-1] is None:
cat = cat[:-1]
cats.append(sep.join([str(i) for i in cat]))
Path(filename).write_text("\n".join(cats))
[docs] def sort(self):
libraster.Rast_sort_cats(ctypes.byref(self.c_cats))
[docs] def labels(self):
return list(map(itemgetter(0), self))