abstract_map_dataset.py 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081
  1. # -*- coding: utf-8 -*-
  2. """
  3. The abstract_map_dataset module provides the AbstractMapDataset class
  4. that is the base class for all map layer.
  5. (C) 2011-2013 by the GRASS Development Team
  6. This program is free software under the GNU General Public
  7. License (>=v2). Read the file COPYING that comes with GRASS
  8. for details.
  9. :authors: Soeren Gebbert
  10. """
  11. from __future__ import print_function
  12. from grass.exceptions import ImplementationError
  13. from datetime import datetime
  14. from abc import ABCMeta, abstractmethod
  15. from .core import get_tgis_c_library_interface, get_enable_timestamp_write, \
  16. get_enable_mapset_check, get_current_mapset, init_dbif
  17. from .abstract_dataset import AbstractDataset
  18. from .temporal_extent import RelativeTemporalExtent, AbsoluteTemporalExtent
  19. from .datetime_math import datetime_to_grass_datetime_string, \
  20. increment_datetime_by_string, decrement_datetime_by_string
  21. class AbstractMapDataset(AbstractDataset):
  22. """This is the base class for all maps (raster, vector, raster3d).
  23. The temporal extent, the spatial extent and the metadata of maps
  24. are stored in the temporal database. Maps can be registered in the
  25. temporal database, updated and deleted.
  26. This class provides all functionalities that are needed to manage maps
  27. in the temporal database. That are:
  28. - insert() to register the map and therefore its spatio-temporal extent
  29. and metadata in the temporal database
  30. - update() to update the map spatio-temporal extent and metadata in the
  31. temporal database
  32. - unregister() to unregister the map from each space time dataset in
  33. which this map is registered
  34. - delete() to remove the map from the temporal database
  35. - Methods to set relative and absolute time stamps
  36. - Abstract methods that must be implemented in the map specific
  37. subclasses
  38. """
  39. __metaclass__ = ABCMeta
  40. def __init__(self):
  41. AbstractDataset.__init__(self)
  42. self.ciface = get_tgis_c_library_interface()
  43. @abstractmethod
  44. def get_new_stds_instance(self, ident):
  45. """Return a new space time dataset instance that store maps with the
  46. type of this map object (raster, raster_3d or vector)
  47. :param ident The identifier of the space time dataset
  48. :return: The new space time dataset instance
  49. """
  50. def check_resolution_with_current_region(self):
  51. """Check if the raster or voxel resolution is
  52. finer than the current resolution
  53. - Return "finer" in case the raster/voxel resolution is finer
  54. than the current region
  55. - Return "coarser" in case the raster/voxel resolution is coarser
  56. than the current region
  57. Vector maps have no resolution, since they store the coordinates
  58. directly.
  59. :return: "finer" or "coarser"
  60. """
  61. raise ImplementationError(
  62. "This method must be implemented in the subclasses")
  63. @abstractmethod
  64. def has_grass_timestamp(self):
  65. """Check if a grass file based time stamp exists for this map.
  66. :return: True is the grass file based time stamped exists for this map
  67. """
  68. @abstractmethod
  69. def write_timestamp_to_grass(self):
  70. """Write the timestamp of this map into the map metadata
  71. in the grass file system based spatial database.
  72. """
  73. @abstractmethod
  74. def read_timestamp_from_grass(self):
  75. """Read the timestamp of this map from the map metadata
  76. in the grass file system based spatial database and
  77. set the internal time stamp that should be insert/updated
  78. in the temporal database.
  79. """
  80. @abstractmethod
  81. def remove_timestamp_from_grass(self):
  82. """Remove the timestamp from the grass file
  83. system based spatial database
  84. """
  85. @abstractmethod
  86. def map_exists(self):
  87. """Return True in case the map exists in the grass spatial database
  88. :return: True if map exists, False otherwise
  89. """
  90. @abstractmethod
  91. def load(self):
  92. """Load the content of this object from the grass
  93. file system based database"""
  94. def _convert_timestamp(self):
  95. """Convert the valid time into a grass datetime library
  96. compatible timestamp string
  97. This methods works for relative and absolute time
  98. :return: the grass timestamp string
  99. """
  100. start = ""
  101. if self.is_time_absolute():
  102. start_time, end_time = self.get_absolute_time()
  103. start = datetime_to_grass_datetime_string(start_time)
  104. if end_time is not None:
  105. end = datetime_to_grass_datetime_string(end_time)
  106. start += " / %s" % (end)
  107. else:
  108. start_time, end_time, unit = self.get_relative_time()
  109. start = "%i %s" % (int(start_time), unit)
  110. if end_time is not None:
  111. end = "%i %s" % (int(end_time), unit)
  112. start += " / %s" % (end)
  113. return start
  114. def get_map_id(self):
  115. """Return the map id. The map id is the unique identifier
  116. in grass and must not be equal to the
  117. primary key identifier (id) of the map in the database.
  118. Since vector maps may have layer information,
  119. the unique id is a combination of name, layer and mapset.
  120. Use get_map_id() every time your need to access the grass map
  121. in the file system but not to identify
  122. map information in the temporal database.
  123. :return: The map id "name@mapset"
  124. """
  125. return self.base.get_map_id()
  126. @staticmethod
  127. def build_id(name, mapset, layer=None):
  128. """Convenient method to build the unique identifier
  129. Existing layer and mapset definitions in the name
  130. string will be reused
  131. :param name: The name of the map
  132. :param mapset: The mapset in which the map is located
  133. :param layer: The layer of the vector map, use None in case no
  134. layer exists
  135. :return: the id of the map as "name(:layer)@mapset" while layer is
  136. optional
  137. """
  138. # Check if the name includes any mapset
  139. if name.find("@") >= 0:
  140. name, mapset = name.split("@")
  141. # Check for layer number in map name
  142. if name.find(":") >= 0:
  143. name, layer = name.split(":")
  144. if layer is not None:
  145. return "%s:%s@%s" % (name, layer, mapset)
  146. else:
  147. return "%s@%s" % (name, mapset)
  148. def get_layer(self):
  149. """Return the layer of the map
  150. :return: the layer of the map or None in case no layer is defined
  151. """
  152. return self.base.get_layer()
  153. def print_self(self):
  154. """Print the content of the internal structure to stdout"""
  155. self.base.print_self()
  156. self.temporal_extent.print_self()
  157. self.spatial_extent.print_self()
  158. self.metadata.print_self()
  159. self.stds_register.print_self()
  160. def print_info(self):
  161. """Print information about this object in human readable style"""
  162. if self.get_type() == "raster":
  163. # 1 2 3 4 5 6 7
  164. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  165. print(" +-------------------- Raster Dataset ----------------------------------------+")
  166. if self.get_type() == "raster3d":
  167. # 1 2 3 4 5 6 7
  168. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  169. print(" +-------------------- 3D Raster Dataset -------------------------------------+")
  170. if self.get_type() == "vector":
  171. # 1 2 3 4 5 6 7
  172. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  173. print(" +-------------------- Vector Dataset ----------------------------------------+")
  174. print(" | |")
  175. self.base.print_info()
  176. self.temporal_extent.print_info()
  177. if self.is_topology_build():
  178. self.print_topology_info()
  179. self.spatial_extent.print_info()
  180. self.metadata.print_info()
  181. datasets = self.get_registered_stds()
  182. count = 0
  183. string = ""
  184. if datasets is not None:
  185. for ds in datasets:
  186. if count > 0 and count % 3 == 0:
  187. string += "\n | ............................ "
  188. count = 0
  189. if count == 0:
  190. string += ds
  191. else:
  192. string += ",%s" % ds
  193. count += 1
  194. print(" | Registered datasets ........ " + string)
  195. print(" +----------------------------------------------------------------------------+")
  196. def print_shell_info(self):
  197. """Print information about this object in shell style"""
  198. self.base.print_shell_info()
  199. self.temporal_extent.print_shell_info()
  200. self.spatial_extent.print_shell_info()
  201. self.metadata.print_shell_info()
  202. datasets = self.get_registered_stds()
  203. count = 0
  204. string = ""
  205. if datasets is not None:
  206. for ds in datasets:
  207. if count == 0:
  208. string += ds
  209. else:
  210. string += ",%s" % ds
  211. count += 1
  212. print("registered_datasets=" + string)
  213. if self.is_topology_build():
  214. self.print_topology_shell_info()
  215. def insert(self, dbif=None, execute=True):
  216. """Insert the map content into the database from the internal
  217. structure
  218. This functions assures that the timestamp is written to the
  219. grass file system based database in addition to the temporal
  220. database entry. The stds register table will be created as well.
  221. Hence maps can only be registered in a space time dataset, when
  222. they were inserted in the temporal database beforehand.
  223. :param dbif: The database interface to be used
  224. :param execute: If True the SQL statements will be executed.
  225. If False the prepared SQL statements are
  226. returned and must be executed by the caller.
  227. :return: The SQL insert statement in case execute=False, or an
  228. empty string otherwise
  229. """
  230. if get_enable_timestamp_write():
  231. self.write_timestamp_to_grass()
  232. return AbstractDataset.insert(self, dbif=dbif, execute=execute)
  233. def update(self, dbif=None, execute=True):
  234. """Update the map content in the database from the internal structure
  235. excluding None variables
  236. This functions assures that the timestamp is written to the
  237. grass file system based database in addition to the temporal
  238. database entry.
  239. :param dbif: The database interface to be used
  240. :param execute: If True the SQL statements will be executed.
  241. If False the prepared SQL statements are
  242. returned and must be executed by the caller.
  243. :return: The SQL insert statement in case execute=False, or an
  244. empty string otherwise
  245. """
  246. if get_enable_timestamp_write():
  247. self.write_timestamp_to_grass()
  248. return AbstractDataset.update(self, dbif, execute)
  249. def update_all(self, dbif=None, execute=True):
  250. """Update the map content in the database from the internal structure
  251. including None variables
  252. This functions assures that the timestamp is written to the
  253. grass file system based database in addition to the temporal
  254. database entry.
  255. :param dbif: The database interface to be used
  256. :param execute: If True the SQL statements will be executed.
  257. If False the prepared SQL statements are
  258. returned and must be executed by the caller.
  259. :return: The SQL insert statement in case execute=False, or an
  260. empty string otherwise
  261. """
  262. if get_enable_timestamp_write():
  263. self.write_timestamp_to_grass()
  264. return AbstractDataset.update_all(self, dbif, execute)
  265. def set_time_to_absolute(self):
  266. """Set the temporal type to absolute"""
  267. self.base.set_ttype("absolute")
  268. def set_time_to_relative(self):
  269. """Set the temporal type to relative"""
  270. self.base.set_ttype("relative")
  271. def set_absolute_time(self, start_time, end_time=None):
  272. """Set the absolute time with start time and end time
  273. The end time is optional and must be set to None in case of time
  274. instance.
  275. This method only modifies this object and does not commit
  276. the modifications to the temporal database.
  277. :param start_time: A datetime object specifying the start time of
  278. the map
  279. :param end_time: A datetime object specifying the end time of the
  280. map, None in case or time instance
  281. :return: True for success and False otherwise
  282. """
  283. if start_time and not isinstance(start_time, datetime):
  284. if self.get_layer() is not None:
  285. self.msgr.error(_("Start time must be of type datetime for "
  286. "%(type)s map <%(id)s> with layer: %(l)s") %
  287. {'type': self.get_type(),
  288. 'id': self.get_map_id(),
  289. 'l': self.get_layer()})
  290. return False
  291. else:
  292. self.msgr.error(_("Start time must be of type datetime for "
  293. "%(type)s map <%(id)s>") %
  294. {'type': self.get_type(),
  295. 'id': self.get_map_id()})
  296. return False
  297. if end_time and not isinstance(end_time, datetime):
  298. if self.get_layer():
  299. self.msgr.error(_("End time must be of type datetime for "
  300. "%(type)s map <%(id)s> with layer: %(l)s") %
  301. {'type': self.get_type(),
  302. 'id': self.get_map_id(),
  303. 'l': self.get_layer()})
  304. return False
  305. else:
  306. self.msgr.error(_("End time must be of type datetime for "
  307. "%(type)s map <%(id)s>") %
  308. {'type': self.get_type(),
  309. 'id': self.get_map_id()})
  310. return False
  311. if start_time is not None and end_time is not None:
  312. if start_time > end_time:
  313. if self.get_layer():
  314. self.msgr.error(_("End time must be greater than start "
  315. "time for %(type)s map <%(id)s> with "
  316. "layer: %(l)s") %
  317. {'type': self.get_type(),
  318. 'id': self.get_map_id(),
  319. 'l': self.get_layer()})
  320. return False
  321. else:
  322. self.msgr.error(_("End time must be greater than start "
  323. "time for %(type)s map <%(id)s>") %
  324. {'type': self.get_type(),
  325. 'id': self.get_map_id()})
  326. return False
  327. else:
  328. # Do not create an interval in case start and end time are
  329. # equal
  330. if start_time == end_time:
  331. end_time = None
  332. self.base.set_ttype("absolute")
  333. self.absolute_time.set_start_time(start_time)
  334. self.absolute_time.set_end_time(end_time)
  335. return True
  336. def update_absolute_time(self, start_time, end_time=None, dbif=None):
  337. """Update the absolute time
  338. The end time is optional and must be set to None in case of time
  339. instance.
  340. This functions assures that the timestamp is written to the
  341. grass file system based database in addition to the temporal
  342. database entry.
  343. :param start_time: A datetime object specifying the start time of
  344. the map
  345. :param end_time: A datetime object specifying the end time of the
  346. map, None in case or time instance
  347. :param dbif: The database interface to be used
  348. """
  349. if get_enable_mapset_check() is True and self.get_mapset() != get_current_mapset():
  350. self.msgr.fatal(_("Unable to update dataset <%(ds)s> of type "
  351. "%(type)s in the temporal database. The mapset "
  352. "of the dataset does not match the current "
  353. "mapset") % {"ds": self.get_id(),
  354. "type": self.get_type()})
  355. if self.set_absolute_time(start_time, end_time):
  356. dbif, connected = init_dbif(dbif)
  357. self.absolute_time.update_all(dbif)
  358. self.base.update(dbif)
  359. if connected:
  360. dbif.close()
  361. if get_enable_timestamp_write():
  362. self.write_timestamp_to_grass()
  363. def set_relative_time(self, start_time, end_time, unit):
  364. """Set the relative time interval
  365. The end time is optional and must be set to None in case of time
  366. instance.
  367. This method only modifies this object and does not commit
  368. the modifications to the temporal database.
  369. :param start_time: An integer value
  370. :param end_time: An integer value, None in case or time instance
  371. :param unit: The unit of the relative time. Supported units:
  372. year(s), month(s), day(s), hour(s), minute(s), second(s)
  373. :return: True for success and False otherwise
  374. """
  375. if not self.check_relative_time_unit(unit):
  376. if self.get_layer() is not None:
  377. self.msgr.error(_("Unsupported relative time unit type for "
  378. "%(type)s map <%(id)s> with layer %(l)s: "
  379. "%(u)s") % {'type': self.get_type(),
  380. 'id': self.get_id(),
  381. 'l': self.get_layer(),
  382. 'u': unit})
  383. else:
  384. self.msgr.error(_("Unsupported relative time unit type for "
  385. "%(type)s map <%(id)s>: %(u)s") %
  386. {'type': self.get_type(), 'id': self.get_id(),
  387. 'u': unit})
  388. return False
  389. if start_time is not None and end_time is not None:
  390. if int(start_time) > int(end_time):
  391. if self.get_layer() is not None:
  392. self.msgr.error(_("End time must be greater than start "
  393. "time for %(typ)s map <%(id)s> with "
  394. "layer %(l)s") % {'typ': self.get_type(),
  395. 'id': self.get_id(),
  396. 'l': self.get_layer()})
  397. else:
  398. self.msgr.error(_("End time must be greater than start "
  399. "time for %(type)s map <%(id)s>") %
  400. {'type': self.get_type(),
  401. 'id': self.get_id()})
  402. return False
  403. else:
  404. # Do not create an interval in case start and end time are
  405. # equal
  406. if start_time == end_time:
  407. end_time = None
  408. self.base.set_ttype("relative")
  409. self.relative_time.set_unit(unit)
  410. self.relative_time.set_start_time(int(start_time))
  411. if end_time is not None:
  412. self.relative_time.set_end_time(int(end_time))
  413. else:
  414. self.relative_time.set_end_time(None)
  415. return True
  416. def update_relative_time(self, start_time, end_time, unit, dbif=None):
  417. """Update the relative time interval
  418. The end time is optional and must be set to None in case of time
  419. instance.
  420. This functions assures that the timestamp is written to the
  421. grass file system based database in addition to the temporal
  422. database entry.
  423. :param start_time: An integer value
  424. :param end_time: An integer value, None in case or time instance
  425. :param unit: The relative time unit
  426. :param dbif: The database interface to be used
  427. """
  428. if get_enable_mapset_check() is True and self.get_mapset() != get_current_mapset():
  429. self.msgr.fatal(_("Unable to update dataset <%(ds)s> of type "
  430. "%(type)s in the temporal database. The mapset "
  431. "of the dataset does not match the current "
  432. "mapset") % {"ds": self.get_id(),
  433. "type": self.get_type()})
  434. if self.set_relative_time(start_time, end_time, unit):
  435. dbif, connected = init_dbif(dbif)
  436. self.relative_time.update_all(dbif)
  437. self.base.update(dbif)
  438. if connected:
  439. dbif.close()
  440. if get_enable_timestamp_write():
  441. self.write_timestamp_to_grass()
  442. def set_temporal_extent(self, extent):
  443. """Convenient method to set the temporal extent from a temporal extent
  444. object
  445. :param extent: The temporal extent that should be set for
  446. this object
  447. .. code-block: : python
  448. >>> import datetime
  449. >>> import grass.temporal as tgis
  450. >>> map = tgis.RasterDataset(None)
  451. >>> temp_ext = tgis.RasterRelativeTime(start_time=1, end_time=2, unit="years")
  452. >>> map.set_temporal_extent(temp_ext)
  453. >>> print(map.get_temporal_extent_as_tuple())
  454. (1, 2)
  455. >>> map = tgis.VectorDataset(None)
  456. >>> temp_ext = tgis.VectorAbsoluteTime(start_time=datetime.datetime(2000, 1, 1),
  457. ... end_time=datetime.datetime(2001, 1, 1))
  458. >>> map.set_temporal_extent(temp_ext)
  459. >>> print(map.get_temporal_extent_as_tuple())
  460. (datetime.datetime(2000, 1, 1, 0, 0), datetime.datetime(2001, 1, 1, 0, 0))
  461. >>> map1 = tgis.VectorDataset("A@P")
  462. >>> check = map1.set_absolute_time(datetime.datetime(2000,5,5), datetime.datetime(2005,6,6))
  463. >>> print(map1.get_temporal_extent_as_tuple())
  464. (datetime.datetime(2000, 5, 5, 0, 0), datetime.datetime(2005, 6, 6, 0, 0))
  465. >>> map2 = tgis.RasterDataset("B@P")
  466. >>> check = map2.set_absolute_time(datetime.datetime(1990,1,1), datetime.datetime(1999,8,1))
  467. >>> print(map2.get_temporal_extent_as_tuple())
  468. (datetime.datetime(1990, 1, 1, 0, 0), datetime.datetime(1999, 8, 1, 0, 0))
  469. >>> map2.set_temporal_extent(map1.get_temporal_extent())
  470. >>> print(map2.get_temporal_extent_as_tuple())
  471. (datetime.datetime(2000, 5, 5, 0, 0), datetime.datetime(2005, 6, 6, 0, 0))
  472. """
  473. if issubclass(type(extent), RelativeTemporalExtent):
  474. start = extent.get_start_time()
  475. end = extent.get_end_time()
  476. unit = extent.get_unit()
  477. self.set_relative_time(start, end, unit)
  478. elif issubclass(type(extent), AbsoluteTemporalExtent):
  479. start = extent.get_start_time()
  480. end = extent.get_end_time()
  481. self.set_absolute_time(start, end)
  482. def temporal_buffer(self, increment, update=False, dbif=None):
  483. """Create a temporal buffer based on an increment
  484. For absolute time the increment must be a string of type "integer
  485. unit"
  486. Unit can be year, years, month, months, day, days, hour, hours,
  487. minute, minutes, day or days.
  488. :param increment: This is the increment, a string in case of
  489. absolute time or an integer in case of relative
  490. time
  491. :param update: Perform an immediate database update to store the
  492. modified temporal extent, otherwise only this object
  493. will be modified
  494. Usage:
  495. .. code-block: : python
  496. >>> import grass.temporal as tgis
  497. >>> maps = []
  498. >>> for i in range(5):
  499. ... map = tgis.RasterDataset(None)
  500. ... if i%2 == 0:
  501. ... check = map.set_relative_time(i, i + 1, 'years')
  502. ... else:
  503. ... check = map.set_relative_time(i, None, 'years')
  504. ... map.temporal_buffer(3)
  505. ... maps.append(map)
  506. >>> for map in maps:
  507. ... map.temporal_extent.print_info()
  508. +-------------------- Relative time -----------------------------------------+
  509. | Start time:................. -3
  510. | End time:................... 4
  511. | Relative time unit:......... years
  512. +-------------------- Relative time -----------------------------------------+
  513. | Start time:................. -2
  514. | End time:................... 4
  515. | Relative time unit:......... years
  516. +-------------------- Relative time -----------------------------------------+
  517. | Start time:................. -1
  518. | End time:................... 6
  519. | Relative time unit:......... years
  520. +-------------------- Relative time -----------------------------------------+
  521. | Start time:................. 0
  522. | End time:................... 6
  523. | Relative time unit:......... years
  524. +-------------------- Relative time -----------------------------------------+
  525. | Start time:................. 1
  526. | End time:................... 8
  527. | Relative time unit:......... years
  528. >>> maps = []
  529. >>> for i in range(1,5):
  530. ... map = tgis.RasterDataset(None)
  531. ... if i%2 == 0:
  532. ... check = map.set_absolute_time(datetime(2001,i,1), datetime(2001, i + 1, 1))
  533. ... else:
  534. ... check = map.set_absolute_time(datetime(2001,i,1), None)
  535. ... map.temporal_buffer("7 days")
  536. ... maps.append(map)
  537. >>> for map in maps:
  538. ... map.temporal_extent.print_info()
  539. +-------------------- Absolute time -----------------------------------------+
  540. | Start time:................. 2000-12-25 00:00:00
  541. | End time:................... 2001-01-08 00:00:00
  542. +-------------------- Absolute time -----------------------------------------+
  543. | Start time:................. 2001-01-25 00:00:00
  544. | End time:................... 2001-03-08 00:00:00
  545. +-------------------- Absolute time -----------------------------------------+
  546. | Start time:................. 2001-02-22 00:00:00
  547. | End time:................... 2001-03-08 00:00:00
  548. +-------------------- Absolute time -----------------------------------------+
  549. | Start time:................. 2001-03-25 00:00:00
  550. | End time:................... 2001-05-08 00:00:00
  551. """
  552. if self.is_time_absolute():
  553. start, end = self.get_absolute_time()
  554. new_start = decrement_datetime_by_string(start, increment)
  555. if end is None:
  556. new_end = increment_datetime_by_string(start, increment)
  557. else:
  558. new_end = increment_datetime_by_string(end, increment)
  559. if update:
  560. self.update_absolute_time(new_start, new_end, dbif=dbif)
  561. else:
  562. self.set_absolute_time(new_start, new_end)
  563. else:
  564. start, end, unit = self.get_relative_time()
  565. new_start = start - increment
  566. if end is None:
  567. new_end = start + increment
  568. else:
  569. new_end = end + increment
  570. if update:
  571. self.update_relative_time(new_start, new_end, unit, dbif=dbif)
  572. else:
  573. self.set_relative_time(new_start, new_end, unit)
  574. def set_spatial_extent_from_values(self, north, south, east, west, top=0,
  575. bottom=0):
  576. """Set the spatial extent of the map from values
  577. This method only modifies this object and does not commit
  578. the modifications to the temporal database.
  579. :param north: The northern edge
  580. :param south: The southern edge
  581. :param east: The eastern edge
  582. :param west: The western edge
  583. :param top: The top edge
  584. :param bottom: The bottom edge
  585. """
  586. self.spatial_extent.set_spatial_extent_from_values(
  587. north, south, east, west, top, bottom)
  588. def set_spatial_extent(self, spatial_extent):
  589. """Set the spatial extent of the map
  590. This method only modifies this object and does not commit
  591. the modifications to the temporal database.
  592. :param spatial_extent: An object of type SpatialExtent or its
  593. subclasses
  594. .. code-block: : python
  595. >>> import datetime
  596. >>> import grass.temporal as tgis
  597. >>> map = tgis.RasterDataset(None)
  598. >>> spat_ext = tgis.SpatialExtent(north=10, south=-10, east=20, west=-20, top=5, bottom=-5)
  599. >>> map.set_spatial_extent(spat_ext)
  600. >>> print(map.get_spatial_extent_as_tuple())
  601. (10.0, -10.0, 20.0, -20.0, 5.0, -5.0)
  602. """
  603. self.spatial_extent.set_spatial_extent(spatial_extent)
  604. def spatial_buffer(self, size, update=False, dbif=None):
  605. """Buffer the spatial extent by a given size in all
  606. spatial directions.
  607. :param size: The buffer size, using the unit of the grass region
  608. :param update: If True perform an immediate database update, otherwise only the
  609. internal variables are set
  610. :param dbif: The database interface to be used
  611. .. code-block: : python
  612. >>> import grass.temporal as tgis
  613. >>> map = tgis.RasterDataset(None)
  614. >>> spat_ext = tgis.SpatialExtent(north=10, south=-10, east=20, west=-20, top=5, bottom=-5)
  615. >>> map.set_spatial_extent(spat_ext)
  616. >>> map.spatial_buffer(10)
  617. >>> print(map.get_spatial_extent_as_tuple())
  618. (20.0, -20.0, 30.0, -30.0, 15.0, -15.0)
  619. """
  620. self.spatial_extent.north += size
  621. self.spatial_extent.south -= size
  622. self.spatial_extent.east += size
  623. self.spatial_extent.west -= size
  624. self.spatial_extent.top += size
  625. self.spatial_extent.bottom -= size
  626. if update:
  627. self.spatial_extent.update(dbif)
  628. def spatial_buffer_2d(self, size, update=False, dbif=None):
  629. """Buffer the spatial extent by a given size in 2d
  630. spatial directions.
  631. :param size: The buffer size, using the unit of the grass region
  632. :param update: If True perform an immediate database update, otherwise only the
  633. internal variables are set
  634. :param dbif: The database interface to be used
  635. .. code-block: : python
  636. >>> import grass.temporal as tgis
  637. >>> map = tgis.RasterDataset(None)
  638. >>> spat_ext = tgis.SpatialExtent(north=10, south=-10, east=20, west=-20, top=5, bottom=-5)
  639. >>> map.set_spatial_extent(spat_ext)
  640. >>> map.spatial_buffer_2d(10)
  641. >>> print(map.get_spatial_extent_as_tuple())
  642. (20.0, -20.0, 30.0, -30.0, 5.0, -5.0)
  643. """
  644. self.spatial_extent.north += size
  645. self.spatial_extent.south -= size
  646. self.spatial_extent.east += size
  647. self.spatial_extent.west -= size
  648. if update:
  649. self.spatial_extent.update(dbif)
  650. def check_for_correct_time(self):
  651. """Check for correct time
  652. :return: True in case of success, False otherwise
  653. """
  654. if self.is_time_absolute():
  655. start, end = self.get_absolute_time()
  656. else:
  657. start, end, unit = self.get_relative_time()
  658. if start is not None:
  659. if end is not None:
  660. if start >= end:
  661. if self.get_layer() is not None:
  662. self.msgr.error(_("Map <%(id)s> with layer %(layer)s "
  663. "has incorrect time interval, start "
  664. "time is greater than end time") %
  665. {'id': self.get_map_id(),
  666. 'layer': self.get_layer()})
  667. else:
  668. self.msgr.error(_("Map <%s> has incorrect time "
  669. "interval, start time is greater "
  670. "than end time") %
  671. (self.get_map_id()))
  672. return False
  673. else:
  674. self.msgr.error(_("Map <%s> has incorrect start time") %
  675. (self.get_map_id()))
  676. return False
  677. return True
  678. def delete(self, dbif=None, update=True, execute=True):
  679. """Delete a map entry from database if it exists
  680. Remove dependent entries:
  681. - Remove the map entry in each space time dataset in which this map
  682. is registered
  683. - Remove the space time dataset register table
  684. :param dbif: The database interface to be used
  685. :param update: Call for each unregister statement the update from
  686. registered maps of the space time dataset.
  687. This can slow down the un-registration process
  688. significantly.
  689. :param execute: If True the SQL DELETE and DROP table statements
  690. will be executed.
  691. If False the prepared SQL statements are
  692. returned and must be executed by the caller.
  693. :return: The SQL statements if execute=False, else an empty string,
  694. None in case of a failure
  695. """
  696. if get_enable_mapset_check() is True and self.get_mapset() != get_current_mapset():
  697. self.msgr.fatal(_("Unable to delete dataset <%(ds)s> of type "
  698. "%(type)s from the temporal database. The mapset"
  699. " of the dataset does not match the current "
  700. "mapset") % {"ds": self.get_id(),
  701. "type": self.get_type()})
  702. dbif, connected = init_dbif(dbif)
  703. statement = ""
  704. if self.is_in_db(dbif):
  705. # SELECT all needed information from the database
  706. self.metadata.select(dbif)
  707. # First we unregister from all dependent space time datasets
  708. statement += self.unregister(
  709. dbif=dbif, update=update, execute=False)
  710. self.msgr.verbose(_("Delete %s dataset <%s> from temporal "
  711. "database") % (self.get_type(), self.get_id()))
  712. # Delete yourself from the database, trigger functions will
  713. # take care of dependencies
  714. statement += self.base.get_delete_statement()
  715. if execute:
  716. dbif.execute_transaction(statement)
  717. statement = ""
  718. # Remove the timestamp from the file system
  719. self.remove_timestamp_from_grass()
  720. self.reset(None)
  721. if connected:
  722. dbif.close()
  723. return statement
  724. def unregister(self, dbif=None, update=True, execute=True):
  725. """ Remove the map entry in each space time dataset in which this map
  726. is registered
  727. :param dbif: The database interface to be used
  728. :param update: Call for each unregister statement the update from
  729. registered maps of the space time dataset. This can
  730. slow down the un-registration process significantly.
  731. :param execute: If True the SQL DELETE and DROP table statements
  732. will be executed.
  733. If False the prepared SQL statements are
  734. returned and must be executed by the caller.
  735. :return: The SQL statements if execute=False, else an empty string
  736. """
  737. if self.get_layer() is not None:
  738. self.msgr.debug(1, "Unregister %(type)s map <%(map)s> with "
  739. "layer %(layer)s from space time datasets" %
  740. {'type': self.get_type(),
  741. 'map': self.get_map_id(),
  742. 'layer': self.get_layer()})
  743. else:
  744. self.msgr.debug(1, "Unregister %(type)s map <%(map)s> "
  745. "from space time datasets" % {
  746. 'type': self.get_type(),
  747. 'map': self.get_map_id()})
  748. if get_enable_mapset_check() is True and self.get_mapset() != get_current_mapset():
  749. self.msgr.fatal(_("Unable to unregister dataset <%(ds)s> of type "
  750. "%(type)s from the temporal database. The mapset"
  751. " of the dataset does not match the current "
  752. "mapset") % {"ds": self.get_id(),
  753. "type": self.get_type()})
  754. statement = ""
  755. dbif, connected = init_dbif(dbif)
  756. # Get all datasets in which this map is registered
  757. datasets = self.get_registered_stds(dbif)
  758. # For each stds in which the map is registered
  759. if datasets is not None:
  760. for dataset in datasets:
  761. # Create a space time dataset object to remove the map
  762. # from its register
  763. stds = self.get_new_stds_instance(dataset)
  764. stds.metadata.select(dbif)
  765. statement += stds.unregister_map(self, dbif, False)
  766. # Take care to update the space time dataset after
  767. # the map has been unregistered
  768. if update is True and execute is True:
  769. stds.update_from_registered_maps(dbif)
  770. if execute:
  771. dbif.execute_transaction(statement)
  772. statement = ""
  773. if connected:
  774. dbif.close()
  775. return statement
  776. def get_registered_stds(self, dbif=None):
  777. """Return all space time dataset ids in which this map is registered
  778. as as a list of strings, or None if this map is not
  779. registered in any space time dataset.
  780. :param dbif: The database interface to be used
  781. :return: A list of ids of all space time datasets in
  782. which this map is registered
  783. """
  784. dbif, connected = init_dbif(dbif)
  785. self.stds_register.select(dbif)
  786. datasets = self.stds_register.get_registered_stds()
  787. if datasets is not None and datasets != "" and datasets.find("@") >= 0:
  788. datasets = datasets.split(",")
  789. else:
  790. datasets = None
  791. if connected:
  792. dbif.close
  793. return datasets
  794. def add_stds_to_register(self, stds_id, dbif=None, execute=True):
  795. """Add a new space time dataset to the register
  796. :param stds_id: The id of the space time dataset to be registered
  797. :param dbif: The database interface to be used
  798. :param execute: If True the SQL INSERT table statements
  799. will be executed.
  800. If False the prepared SQL statements are
  801. returned and must be executed by the caller.
  802. :return: The SQL statements if execute=False, else an empty string
  803. """
  804. dbif, connected = init_dbif(dbif=dbif)
  805. datasets = self.get_registered_stds(dbif=dbif)
  806. if stds_id is None or stds_id == "":
  807. return ""
  808. # Check if no datasets are present
  809. if datasets is None:
  810. datasets = []
  811. # Check if the dataset is already present
  812. if stds_id in datasets:
  813. if connected:
  814. dbif.close
  815. return ""
  816. datasets.append(stds_id)
  817. self.stds_register.set_registered_stds(",".join(datasets))
  818. statement = ""
  819. if execute is True:
  820. self.stds_register.update(dbif=dbif)
  821. else:
  822. statement = self.stds_register.get_update_statement_mogrified(dbif=dbif)
  823. if connected:
  824. dbif.close
  825. return statement
  826. def remove_stds_from_register(self, stds_id, dbif=None, execute=True):
  827. """Remove a space time dataset from the register
  828. :param stds_id: The id of the space time dataset to removed from
  829. the registered
  830. :param dbif: The database interface to be used
  831. :param execute: If True the SQL INSERT table statements
  832. will be executed.
  833. If False the prepared SQL statements are
  834. returned and must be executed by the caller.
  835. :return: The SQL statements if execute=False, else an empty string
  836. """
  837. dbif, connected = init_dbif(dbif)
  838. datasets = self.get_registered_stds(dbif=dbif)
  839. # Check if no datasets are present
  840. if datasets is None:
  841. if connected:
  842. dbif.close
  843. return ""
  844. # Check if the dataset is already present
  845. if stds_id not in datasets:
  846. if connected:
  847. dbif.close
  848. return ""
  849. datasets.remove(stds_id)
  850. self.stds_register.set_registered_stds(",".join(datasets))
  851. statement = ""
  852. if execute is True:
  853. self.stds_register.update(dbif=dbif)
  854. else:
  855. statement = self.stds_register.get_update_statement_mogrified(dbif=dbif)
  856. if connected:
  857. dbif.close
  858. return statement
  859. def read_band_reference_from_grass(self):
  860. """Read the band identifier of this map from the map metadata
  861. in the GRASS file system based spatial database and
  862. set the internal band identifier that should be insert/updated
  863. in the temporal database.
  864. Currently only implemented in RasterDataset. Otherwise
  865. silently pass.
  866. """
  867. pass
  868. def set_band_reference(self, band_reference):
  869. """Set band reference identifier
  870. Currently only implemented in RasterDataset. Otherwise
  871. report a warning.
  872. """
  873. self.msgr.warning(_("Band references can only be assigned to raster maps"))
  874. ###############################################################################
  875. if __name__ == "__main__":
  876. import doctest
  877. doctest.testmod()