abstract.py 19 KB

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