abstract_map_dataset.py 43 KB

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