abstract_map_dataset.py 42 KB

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