1# -*- coding: utf-8 -*-
2"""
3Created on Fri Aug 17 16:05:25 2012
4
5@author: pietro
6"""
7from __future__ import (nested_scopes, generators, division, absolute_import,
8                        with_statement, print_function, unicode_literals)
9import ctypes
10
11#
12# import GRASS modules
13#
14from grass.script import fatal, gisenv
15import grass.lib.gis as libgis
16import grass.lib.raster as libraster
17
18#
19# import pygrass modules
20#
21from grass.pygrass import utils
22from grass.pygrass.gis.region import Region
23from grass.pygrass.errors import must_be_open
24from grass.pygrass.shell.conversion import dict2html
25from grass.pygrass.shell.show import raw_figure
26
27#
28# import raster classes
29#
30from grass.pygrass.raster.raster_type import TYPE as RTYPE, RTYPE_STR
31from grass.pygrass.raster.category import Category
32from grass.pygrass.raster.history import History
33
34test_raster_name = "abstract_test_map"
35
36# Define global variables to not exceed the 80 columns
37INDXOUTRANGE = "The index (%d) is out of range, have you open the map?."
38INFO = """{name}@{mapset}
39rows: {rows}
40cols: {cols}
41north: {north} south: {south} nsres:{nsres}
42east:  {east} west: {west} ewres:{ewres}
43range: {min}, {max}
44proj: {proj}
45"""
46
47
48class Info(object):
49    def __init__(self, name, mapset=''):
50        """Read the information for a raster map. ::
51
52            >>> info = Info(test_raster_name)
53            >>> info.read()
54            >>> info          # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
55            abstract_test_map@
56            rows: 4
57            cols: 4
58            north: 40.0 south: 0.0 nsres:10.0
59            east:  40.0 west: 0.0 ewres:10.0
60            range: 11, 44
61            ...
62            <BLANKLINE>
63
64        """
65        self.name = name
66        self.mapset = mapset
67        self.c_region = ctypes.pointer(libraster.struct_Cell_head())
68        self.c_range = None
69
70    def _get_range(self):
71        if self.mtype == 'CELL':
72            self.c_range = ctypes.pointer(libraster.Range())
73            libraster.Rast_read_range(self.name, self.mapset, self.c_range)
74        else:
75            self.c_range = ctypes.pointer(libraster.FPRange())
76            libraster.Rast_read_fp_range(self.name, self.mapset, self.c_range)
77
78    def _get_raster_region(self):
79        libraster.Rast_get_cellhd(self.name, self.mapset, self.c_region)
80
81    def read(self):
82        self._get_range()
83        self._get_raster_region()
84
85    @property
86    def north(self):
87        return self.c_region.contents.north
88
89    @property
90    def south(self):
91        return self.c_region.contents.south
92
93    @property
94    def east(self):
95        return self.c_region.contents.east
96
97    @property
98    def west(self):
99        return self.c_region.contents.west
100
101    @property
102    def top(self):
103        return self.c_region.contents.top
104
105    @property
106    def bottom(self):
107        return self.c_region.contents.bottom
108
109    @property
110    def rows(self):
111        return self.c_region.contents.rows
112
113    @property
114    def cols(self):
115        return self.c_region.contents.cols
116
117    @property
118    def nsres(self):
119        return self.c_region.contents.ns_res
120
121    @property
122    def ewres(self):
123        return self.c_region.contents.ew_res
124
125    @property
126    def tbres(self):
127        return self.c_region.contents.tb_res
128
129    @property
130    def zone(self):
131        return self.c_region.contents.zone
132
133    @property
134    def proj(self):
135        return self.c_region.contents.proj
136
137    @property
138    def min(self):
139        if self.c_range is None:
140            return None
141        return self.c_range.contents.min
142
143    @property
144    def max(self):
145        if self.c_range is None:
146            return None
147        return self.c_range.contents.max
148
149    @property
150    def range(self):
151        if self.c_range is None:
152            return None, None
153        return self.c_range.contents.min, self.c_range.contents.max
154
155    @property
156    def mtype(self):
157        return RTYPE_STR[libraster.Rast_map_type(self.name, self.mapset)]
158
159    def _get_units(self):
160        return libraster.Rast_read_units(self.name, self.mapset)
161
162    def _set_units(self, units):
163        libraster.Rast_write_units(self.name, units)
164
165    units = property(_get_units, _set_units)
166
167    def _get_vdatum(self):
168        return libraster.Rast_read_vdatum(self.name, self.mapset)
169
170    def _set_vdatum(self, vdatum):
171        libraster.Rast_write_vdatum(self.name, vdatum)
172
173    vdatum = property(_get_vdatum, _set_vdatum)
174
175    def __repr__(self):
176        return INFO.format(name=self.name, mapset=self.mapset,
177                           rows=self.rows, cols=self.cols,
178                           north=self.north, south=self.south,
179                           east=self.east, west=self.west,
180                           top=self.top, bottom=self.bottom,
181                           nsres=self.nsres, ewres=self.ewres,
182                           tbres=self.tbres, zone=self.zone,
183                           proj=self.proj, min=self.min, max=self.max)
184
185    def keys(self):
186        return ['name', 'mapset', 'rows', 'cols', 'north', 'south',
187                'east', 'west', 'top', 'bottom', 'nsres', 'ewres', 'tbres',
188                'zone', 'proj', 'min', 'max']
189
190    def items(self):
191        return [(k, self.__getattribute__(k)) for k in self.keys()]
192
193    def __iter__(self):
194        return ((k, self.__getattribute__(k)) for k in self.keys())
195
196    def _repr_html_(self):
197        return dict2html(dict(self.items()), keys=self.keys(),
198                         border='1', kdec='b')
199
200
201class RasterAbstractBase(object):
202    """Raster_abstract_base: The base class from which all sub-classes
203    inherit. It does not implement any row or map access methods:
204
205    * Implements raster metadata information access (Type, ...)
206    * Implements an open method that will be overwritten by the sub-classes
207    * Implements the close method that might be overwritten by sub-classes
208      (should work for simple row access)
209    * Implements get and set region methods
210    * Implements color, history and category handling
211    * Renaming, deletion, ...
212
213    """
214    def __init__(self, name, mapset="", *aopen, **kwopen):
215        """The constructor need at least the name of the map
216        *optional* field is the `mapset`.
217
218        >>> ele = RasterAbstractBase(test_raster_name)
219        >>> ele.name
220        'abstract_test_map'
221        >>> ele.exist()
222        True
223
224        ..
225        """
226        self.mapset = mapset
227        self._name = name
228        # Private attribute `_fd` that return the file descriptor of the map
229        self._fd = None
230        # Private attribute `_rows` that return the number of rows
231        # in active window, When the class is instanced is empty and it is set
232        # when you open the file, using Rast_window_rows()
233        self._rows = None
234        # Private attribute `_cols` that return the number of rows
235        # in active window, When the class is instanced is empty and it is set
236        # when you open the file, using Rast_window_cols()
237        self._cols = None
238        # self.region = Region()
239        self.hist = History(self.name, self.mapset)
240        self.cats = Category(self.name, self.mapset)
241        self.info = Info(self.name, self.mapset)
242        self._aopen = aopen
243        self._kwopen = kwopen
244        self._mtype = 'CELL'
245        self._mode = 'r'
246        self._overwrite = False
247
248    def __enter__(self):
249        self.open(*self._aopen, **self._kwopen)
250        return self
251
252    def __exit__(self, exc_type, exc_value, traceback):
253        self.close()
254
255    def _get_mtype(self):
256        """Private method to get the Raster type"""
257        return self._mtype
258
259    def _set_mtype(self, mtype):
260        """Private method to change the Raster type"""
261        if mtype.upper() not in ('CELL', 'FCELL', 'DCELL'):
262            str_err = "Raster type: {0} not supported ('CELL','FCELL','DCELL')"
263            raise ValueError(_(str_err).format(mtype))
264        self._mtype = mtype
265        self._gtype = RTYPE[self.mtype]['grass type']
266
267    mtype = property(fget=_get_mtype, fset=_set_mtype)
268
269    def _get_mode(self):
270        return self._mode
271
272    def _set_mode(self, mode):
273        if mode.upper() not in ('R', 'W'):
274            str_err = _("Mode type: {0} not supported ('r', 'w')")
275            raise ValueError(str_err.format(mode))
276        self._mode = mode
277
278    mode = property(fget=_get_mode, fset=_set_mode)
279
280    def _get_overwrite(self):
281        return self._overwrite
282
283    def _set_overwrite(self, overwrite):
284        if overwrite not in (True, False):
285            str_err = _("Overwrite type: {0} not supported (True/False)")
286            raise ValueError(str_err.format(overwrite))
287        self._overwrite = overwrite
288
289    overwrite = property(fget=_get_overwrite, fset=_set_overwrite)
290
291    def _get_name(self):
292        """Private method to return the Raster name"""
293        return self._name
294
295    def _set_name(self, newname):
296        """Private method to change the Raster name"""
297        if not utils.is_clean_name(newname):
298            str_err = _("Map name {0} not valid")
299            raise ValueError(str_err.format(newname))
300        if self.exist():
301            self.rename(newname)
302        self._name = newname
303
304    name = property(fget=_get_name, fset=_set_name)
305
306    @must_be_open
307    def _get_cats_title(self):
308        return self.cats.title
309
310    @must_be_open
311    def _set_cats_title(self, newtitle):
312        self.cats.title = newtitle
313
314    cats_title = property(fget=_get_cats_title, fset=_set_cats_title)
315
316    def __unicode__(self):
317        return self.name_mapset()
318
319    def __str__(self):
320        """Return the string of the object"""
321        return self.__unicode__()
322
323    def __len__(self):
324        return self._rows
325
326    def __getitem__(self, key):
327        """Return the row of Raster object, slice allowed."""
328        if isinstance(key, slice):
329            # Get the start, stop, and step from the slice
330            return (self.get_row(ii) for ii in range(*key.indices(len(self))))
331        elif isinstance(key, tuple):
332            x, y = key
333            return self.get(x, y)
334        elif isinstance(key, int):
335            if not self.is_open():
336                raise IndexError("Can not operate on a closed map. Call open() first.")
337            if key < 0:  # Handle negative indices
338                key += self._rows
339            if key >= self._rows:
340                raise IndexError("The row index {0} is out of range [0, {1}).".format(key, self._rows))
341            return self.get_row(key)
342        else:
343            fatal("Invalid argument type.")
344
345    def __iter__(self):
346        """Return a constructor of the class"""
347        return (self.__getitem__(irow) for irow in range(self._rows))
348
349    def _repr_png_(self):
350        return raw_figure(utils.r_export(self))
351
352    def exist(self):
353        """Return True if the map already exist, and
354        set the mapset if were not set.
355
356        call the C function `G_find_raster`.
357
358        >>> ele = RasterAbstractBase(test_raster_name)
359        >>> ele.exist()
360        True
361        """
362        if self.name:
363            if self.mapset == '':
364                mapset = utils.get_mapset_raster(self.name, self.mapset)
365                self.mapset = mapset if mapset else ''
366                return True if mapset else False
367            return bool(utils.get_mapset_raster(self.name, self.mapset))
368        else:
369            return False
370
371    def is_open(self):
372        """Return True if the map is open False otherwise.
373
374        >>> ele = RasterAbstractBase(test_raster_name)
375        >>> ele.is_open()
376        False
377
378        """
379        return True if self._fd is not None and self._fd >= 0 else False
380
381    @must_be_open
382    def close(self):
383        """Close the map"""
384        libraster.Rast_close(self._fd)
385        # update rows and cols attributes
386        self._rows = None
387        self._cols = None
388        self._fd = None
389
390    def remove(self):
391        """Remove the map"""
392        if self.is_open():
393            self.close()
394        utils.remove(self.name, 'rast')
395
396    def fullname(self):
397        """Return the full name of a raster map: name@mapset"""
398        return "{name}@{mapset}".format(name=self.name, mapset=self.mapset)
399
400    def name_mapset(self, name=None, mapset=None):
401        """Return the full name of the Raster.
402
403        >>> ele = RasterAbstractBase(test_raster_name)
404        >>> name = ele.name_mapset().split("@")
405        >>> name
406        ['abstract_test_map']
407
408        """
409        if name is None:
410            name = self.name
411        if mapset is None:
412            self.exist()
413            mapset = self.mapset
414
415        gis_env = gisenv()
416
417        if mapset and mapset != gis_env['MAPSET']:
418            return "{name}@{mapset}".format(name=name, mapset=mapset)
419        else:
420            return name
421
422    def rename(self, newname):
423        """Rename the map"""
424        if self.exist():
425            utils.rename(self.name, newname, 'rast')
426        self._name = newname
427
428    def set_region_from_rast(self, rastname='', mapset=''):
429        """Set the computational region from a map,
430           if rastername and mapset is not specify, use itself.
431           This region will be used by all
432           raster map layers that are opened in the same process.
433
434           The GRASS region settings will not be modified.
435
436           call C function `Rast_get_cellhd`, `Rast_set_window`
437
438           """
439        if self.is_open():
440            fatal("You cannot change the region if map is open")
441            raise
442        region = Region()
443        if rastname == '':
444            rastname = self.name
445        if mapset == '':
446            mapset = self.mapset
447
448        libraster.Rast_get_cellhd(rastname, mapset,
449                                  region.byref())
450        self._set_raster_window(region)
451
452    def set_region(self, region):
453        """Set the computational region that can be different from the
454           current region settings. This region will be used by all
455           raster map layers that are opened in the same process.
456
457           The GRASS region settings will not be modified.
458        """
459        if self.is_open():
460            fatal("You cannot change the region if map is open")
461            raise
462        self._set_raster_window(region)
463
464    def _set_raster_window(self, region):
465        libraster.Rast_set_window(region.byref())
466        # update rows and cols attributes
467        self._rows = libraster.Rast_window_rows()
468        self._cols = libraster.Rast_window_cols()
469
470    @must_be_open
471    def get_value(self, point, region=None):
472        """This method returns the pixel value of a given pair of coordinates:
473
474        :param point: pair of coordinates in tuple object or class object with coords() method
475        """
476        # Check for tuple
477        if type(point) != type([]) and type(point) != type(()):
478            point = point.coords()
479
480        if not region:
481            region = Region()
482        row, col = utils.coor2pixel(point, region)
483        if col < 0 or col > region.cols or row < 0 or row > region.rows:
484            return None
485        line = self.get_row(int(row))
486        return line[int(col)]
487
488    @must_be_open
489    def has_cats(self):
490        """Return True if the raster map has categories"""
491        if self.exist():
492            self.cats.read()
493            if len(self.cats) != 0:
494                return True
495        return False
496
497    @must_be_open
498    def num_cats(self):
499        """Return the number of categories"""
500        return len(self.cats)
501
502    @must_be_open
503    def copy_cats(self, raster):
504        """Copy categories from another raster map object"""
505        self.cats.copy(raster.cats)
506
507    @must_be_open
508    def sort_cats(self):
509        """Sort categories order by range"""
510        self.cats.sort()
511
512    @must_be_open
513    def read_cats(self):
514        """Read category from the raster map file"""
515        self.cats.read(self)
516
517    @must_be_open
518    def write_cats(self):
519        """Write category to the raster map file"""
520        self.cats.write(self)
521
522    @must_be_open
523    def read_cats_rules(self, filename, sep=':'):
524        """Read category from the raster map file"""
525        self.cats.read_rules(filename, sep)
526
527    @must_be_open
528    def write_cats_rules(self, filename, sep=':'):
529        """Write category to the raster map file"""
530        self.cats.write_rules(filename, sep)
531
532    @must_be_open
533    def get_cats(self):
534        """Return a category object"""
535        cat = Category(name=self.name, mapset=self.mapset)
536        cat.read()
537        return cat
538
539    @must_be_open
540    def set_cats(self, category):
541        """The internal categories are copied from this object."""
542        self.cats.copy(category)
543
544    @must_be_open
545    def get_cat(self, label):
546        """Return a category given an index or a label"""
547        return self.cats[label]
548
549    @must_be_open
550    def set_cat(self, label, min_cat, max_cat=None, index=None):
551        """Set or update a category"""
552        self.cats.set_cat(index, (label, min_cat, max_cat))
553
554if __name__ == "__main__":
555
556    import doctest
557    from grass.pygrass.modules import Module
558    Module("g.region", n=40, s=0, e=40, w=0, res=10)
559    Module("r.mapcalc", expression="%s = row() + (10 * col())" % (test_raster_name),
560        overwrite=True)
561
562    doctest.testmod()
563
564    """Remove the generated vector map, if exist"""
565    mset = utils.get_mapset_raster(test_raster_name, mapset='')
566    if mset:
567        Module("g.remove", flags='f', type='raster', name=test_raster_name)
568