abstract_map_dataset.py 44 KB

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