abstract_map_dataset.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981
  1. # -*- coding: utf-8 -*-
  2. """!@package grass.temporal
  3. @brief GRASS Python scripting module (temporal GIS functions)
  4. Temporal GIS related functions to be used in temporal GIS Python library package.
  5. Usage:
  6. >>> import grass.temporal as tgis
  7. >>> tmr = tgis.TemporalMapRelations()
  8. >>> amd = tgis.AbstractMapDataset()
  9. (C) 2008-2011 by the GRASS Development Team
  10. This program is free software under the GNU General Public
  11. License (>=v2). Read the file COPYING that comes with GRASS
  12. for details.
  13. @author Soeren Gebbert
  14. """
  15. from abstract_dataset import *
  16. from datetime_math import *
  17. class TemporalMapRelations(AbstractDataset):
  18. """!This class implements a temporal topology access structure
  19. This object will be set up by temporal topology creation methods.
  20. If correctly initialize the calls next() and prev()
  21. let the user walk temporally forward and backward in time.
  22. The following temporal relations with access methods are supported:
  23. * equal
  24. * follows
  25. * precedes
  26. * overlaps
  27. * overlapped
  28. * during (including starts, finishes)
  29. * contains (including started, finished)
  30. @code:
  31. # We have build the temporal topology and we know the first map
  32. start = first
  33. while start:
  34. # Print all maps this map temporally contains
  35. dlist = start.get_contains()
  36. for map in dlist:
  37. map.print_info()
  38. start = start.next()
  39. @endcode
  40. Usage:
  41. @code
  42. >>> tmr = TemporalMapRelations()
  43. >>> tmr.print_temporal_topology_info()
  44. +-------------------- Temporal Topology -------------------------------------+
  45. >>> tmr.print_temporal_topology_shell_info()
  46. @endcode
  47. """
  48. def __init__(self):
  49. AbstractDataset.__init__(self)
  50. self.reset_temporal_topology()
  51. def reset_temporal_topology(self):
  52. """!Reset any information about temporal topology"""
  53. self._temporal_topology = {}
  54. self._has_temporal_topology = False
  55. def set_temporal_topology_build_true(self):
  56. """!Same as name"""
  57. self._has_temporal_topology = True
  58. def set_temporal_topology_build_false(self):
  59. """!Same as name"""
  60. self._has_temporal_topology = False
  61. def is_temporal_topology_build(self):
  62. """!Check if the temporal topology was build"""
  63. return self._has_temporal_topology
  64. def set_temporal_next(self, map_):
  65. """!Set the map that is temporally as closest located after this map.
  66. Temporally located means that the start time of the "next" map is
  67. temporally located AFTER the start time of this map, but temporally
  68. near than other maps of the same dataset.
  69. @param map_: This object should be of type AbstractMapDataset
  70. or derived classes
  71. """
  72. self._temporal_topology["NEXT"] = map_
  73. def set_temporal_prev(self, map_):
  74. """!Set the map that is temporally as closest located before this map.
  75. Temporally located means that the start time of the "previous" map is
  76. temporally located BEFORE the start time of this map, but temporally
  77. near than other maps of the same dataset.
  78. @param map_: This object should be of type AbstractMapDataset
  79. or derived classes
  80. """
  81. self._temporal_topology["PREV"] = map_
  82. def temporal_next(self):
  83. """!Return the map with a start time temporally located after
  84. the start time of this map, but temporal closer than other maps
  85. @return A map object or None
  86. """
  87. if "NEXT" not in self._temporal_topology:
  88. return None
  89. return self._temporal_topology["NEXT"]
  90. def temporal_prev(self):
  91. """!Return the map with a start time temporally located before
  92. the start time of this map, but temporal closer than other maps
  93. @return A map object or None
  94. """
  95. if "PREV" not in self._temporal_topology:
  96. return None
  97. return self._temporal_topology["PREV"]
  98. def append_temporal_equivalent(self, map_):
  99. """!Append a map with equivalent temporal extent as this map
  100. @param map_: This object should be of type AbstractMapDataset
  101. or derived classes
  102. """
  103. if "EQUAL" not in self._temporal_topology:
  104. self._temporal_topology["EQUAL"] = []
  105. self._temporal_topology["EQUAL"].append(map_)
  106. def get_temporal_equivalent(self):
  107. """!Return a list of map objects with equivalent temporal extent as this map
  108. @return A list of map objects or None
  109. """
  110. if "EQUAL" not in self._temporal_topology:
  111. return None
  112. return self._temporal_topology["EQUAL"]
  113. def append_temporal_overlaps(self, map_):
  114. """!Append a map that this map temporally overlaps
  115. @param map_: This object should be of type AbstractMapDataset
  116. or derived classes
  117. """
  118. if "OVERLAPS" not in self._temporal_topology:
  119. self._temporal_topology["OVERLAPS"] = []
  120. self._temporal_topology["OVERLAPS"].append(map_)
  121. def get_temporal_overlaps(self):
  122. """!Return a list of map objects that this map temporally overlaps
  123. @return A list of map objects or None
  124. """
  125. if "OVERLAPS" not in self._temporal_topology:
  126. return None
  127. return self._temporal_topology["OVERLAPS"]
  128. def append_temporal_overlapped(self, map_):
  129. """!Append a map that this map temporally overlapped
  130. @param map_: This object should be of type AbstractMapDataset
  131. or derived classes
  132. """
  133. if "OVERLAPPED" not in self._temporal_topology:
  134. self._temporal_topology["OVERLAPPED"] = []
  135. self._temporal_topology["OVERLAPPED"].append(map_)
  136. def get_temporal_overlapped(self):
  137. """!Return a list of map objects that this map temporally overlapped
  138. @return A list of map objects or None
  139. """
  140. if "OVERLAPPED" not in self._temporal_topology:
  141. return None
  142. return self._temporal_topology["OVERLAPPED"]
  143. def append_temporal_follows(self, map_):
  144. """!Append a map that this map temporally follows
  145. @param map_: This object should be of type AbstractMapDataset
  146. or derived classes
  147. """
  148. if "FOLLOWS" not in self._temporal_topology:
  149. self._temporal_topology["FOLLOWS"] = []
  150. self._temporal_topology["FOLLOWS"].append(map_)
  151. def get_temporal_follows(self):
  152. """!Return a list of map objects that this map temporally follows
  153. @return A list of map objects or None
  154. """
  155. if "FOLLOWS" not in self._temporal_topology:
  156. return None
  157. return self._temporal_topology["FOLLOWS"]
  158. def append_temporal_precedes(self, map_):
  159. """!Append a map that this map temporally precedes
  160. @param map_: This object should be of type AbstractMapDataset
  161. or derived classes
  162. """
  163. if "PRECEDES" not in self._temporal_topology:
  164. self._temporal_topology["PRECEDES"] = []
  165. self._temporal_topology["PRECEDES"].append(map_)
  166. def get_temporal_precedes(self):
  167. """!Return a list of map objects that this map temporally precedes
  168. @return A list of map objects or None
  169. """
  170. if "PRECEDES" not in self._temporal_topology:
  171. return None
  172. return self._temporal_topology["PRECEDES"]
  173. def append_temporal_during(self, map_):
  174. """!Append a map that this map is temporally located during
  175. This includes temporal relationships starts and finishes
  176. @param map_: This object should be of type
  177. AbstractMapDataset or derived classes
  178. """
  179. if "DURING" not in self._temporal_topology:
  180. self._temporal_topology["DURING"] = []
  181. self._temporal_topology["DURING"].append(map_)
  182. def get_temporal_during(self):
  183. """!Return a list of map objects that this map is temporally located during
  184. This includes temporally relationships starts and finishes
  185. @return A list of map objects or None
  186. """
  187. if "DURING" not in self._temporal_topology:
  188. return None
  189. return self._temporal_topology["DURING"]
  190. def append_temporal_contains(self, map_):
  191. """!Append a map that this map temporally contains
  192. This includes temporal relationships started and finished
  193. @param map_: This object should be of type AbstractMapDataset
  194. or derived classes
  195. """
  196. if "CONTAINS" not in self._temporal_topology:
  197. self._temporal_topology["CONTAINS"] = []
  198. self._temporal_topology["CONTAINS"].append(map_)
  199. def get_temporal_contains(self):
  200. """!Return a list of map objects that this map temporally contains
  201. This includes temporal relationships started and finished
  202. @return A list of map objects or None
  203. """
  204. if "CONTAINS" not in self._temporal_topology:
  205. return None
  206. return self._temporal_topology["CONTAINS"]
  207. def _generate_map_list_string(self, map_list, line_wrap=True):
  208. count = 0
  209. string = ""
  210. for map_ in map_list:
  211. if line_wrap and count > 0 and count % 3 == 0:
  212. string += "\n | ............................ "
  213. count = 0
  214. if count == 0:
  215. string += map_.get_id()
  216. else:
  217. string += ",%s" % map_.get_id()
  218. count += 1
  219. return string
  220. # Set the properties
  221. temporal_equivalent = property(fget=get_temporal_equivalent,
  222. fset=append_temporal_equivalent)
  223. temporal_follows = property(fget=get_temporal_follows,
  224. fset=append_temporal_follows)
  225. temporal_precedes = property(fget=get_temporal_precedes,
  226. fset=append_temporal_precedes)
  227. temporal_overlaps = property(fget=get_temporal_overlaps,
  228. fset=append_temporal_overlaps)
  229. temporal_overlapped = property(fget=get_temporal_overlapped,
  230. fset=append_temporal_overlapped)
  231. temporal_during = property(fget=get_temporal_during,
  232. fset=append_temporal_during)
  233. temporal_contains = property(fget=get_temporal_contains,
  234. fset=append_temporal_contains)
  235. def print_temporal_topology_info(self):
  236. """!Print information about this class in human readable style"""
  237. _next = self.temporal_next()
  238. _prev = self.temporal_prev()
  239. _equal = self.get_temporal_equivalent()
  240. _follows = self.get_temporal_follows()
  241. _precedes = self.get_temporal_precedes()
  242. _overlaps = self.get_temporal_overlaps()
  243. _overlapped = self.get_temporal_overlapped()
  244. _during = self.get_temporal_during()
  245. _contains = self.get_temporal_contains()
  246. print " +-------------------- Temporal Topology -------------------------------------+"
  247. # 0123456789012345678901234567890
  248. if _next is not None:
  249. print " | Next: ...................... " + str(_next.get_id())
  250. if _prev is not None:
  251. print " | Previous: .................. " + str(_prev.get_id())
  252. if _equal is not None:
  253. print " | Equivalent: ................ " + \
  254. self._generate_map_list_string(_equal)
  255. if _follows is not None:
  256. print " | Follows: ................... " + \
  257. self._generate_map_list_string(_follows)
  258. if _precedes is not None:
  259. print " | Precedes: .................. " + \
  260. self._generate_map_list_string(_precedes)
  261. if _overlaps is not None:
  262. print " | Overlaps: .................. " + \
  263. self._generate_map_list_string(_overlaps)
  264. if _overlapped is not None:
  265. print " | Overlapped: ................ " + \
  266. self._generate_map_list_string(_overlapped)
  267. if _during is not None:
  268. print " | During: .................... " + \
  269. self._generate_map_list_string(_during)
  270. if _contains is not None:
  271. print " | Contains: .................. " + \
  272. self._generate_map_list_string(_contains)
  273. def print_temporal_topology_shell_info(self):
  274. """!Print information about this class in shell style"""
  275. _next = self.temporal_next()
  276. _prev = self.temporal_prev()
  277. _equal = self.get_temporal_equivalent()
  278. _follows = self.get_temporal_follows()
  279. _precedes = self.get_temporal_precedes()
  280. _overlaps = self.get_temporal_overlaps()
  281. _overlapped = self.get_temporal_overlapped()
  282. _during = self.get_temporal_during()
  283. _contains = self.get_temporal_contains()
  284. if _next is not None:
  285. print "next=" + _next.get_id()
  286. if _prev is not None:
  287. print "prev=" + _prev.get_id()
  288. if _equal is not None:
  289. print "equivalent=" + self._generate_map_list_string(_equal, False)
  290. if _follows is not None:
  291. print "follows=" + self._generate_map_list_string(_follows, False)
  292. if _precedes is not None:
  293. print "precedes=" + self._generate_map_list_string(
  294. _precedes, False)
  295. if _overlaps is not None:
  296. print "overlaps=" + self._generate_map_list_string(
  297. _overlaps, False)
  298. if _overlapped is not None:
  299. print "overlapped=" + \
  300. self._generate_map_list_string(_overlapped, False)
  301. if _during is not None:
  302. print "during=" + self._generate_map_list_string(_during, False)
  303. if _contains is not None:
  304. print "contains=" + self._generate_map_list_string(
  305. _contains, False)
  306. ###############################################################################
  307. class AbstractMapDataset(TemporalMapRelations):
  308. """!This is the base class for all maps (raster, vector, raster3d)
  309. providing additional function to set the valid time and the spatial extent.
  310. """
  311. def __init__(self):
  312. TemporalMapRelations.__init__(self)
  313. def get_new_stds_instance(self, ident):
  314. """!Return a new space time dataset instance in which maps
  315. are stored with the type of this class
  316. @param ident: The identifier of the dataset
  317. """
  318. raise ImplementationError(
  319. "This method must be implemented in the subclasses")
  320. def get_stds_register(self):
  321. """!Return the space time dataset register table name in which stds
  322. are listed in which this map is registered"""
  323. raise ImplementationError(
  324. "This method must be implemented in the subclasses")
  325. def set_stds_register(self, name):
  326. """!Set the space time dataset register table name.
  327. This table stores all space time datasets in
  328. which this map is registered.
  329. @param ident: The name of the register table
  330. """
  331. raise ImplementationError(
  332. "This method must be implemented in the subclasses")
  333. def check_resolution_with_current_region(self):
  334. """!Check if the raster or voxel resolution is
  335. finer than the current resolution
  336. * Return "finer" in case the raster/voxel resolution is finer
  337. than the current region
  338. * Return "coarser" in case the raster/voxel resolution is coarser
  339. than the current region
  340. Vector maps are alwyas finer than the current region
  341. """
  342. raise ImplementationError(
  343. "This method must be implemented in the subclasses")
  344. def has_grass_timestamp(self):
  345. """!Check if a grass file bsased time stamp exists for this map.
  346. """
  347. raise ImplementationError(
  348. "This method must be implemented in the subclasses")
  349. def write_timestamp_to_grass(self):
  350. """!Write the timestamp of this map into the map metadata
  351. in the grass file system based spatial database.
  352. """
  353. raise ImplementationError(
  354. "This method must be implemented in the subclasses")
  355. def remove_timestamp_from_grass(self):
  356. """!Remove the timestamp from the grass file
  357. system based spatial database
  358. """
  359. raise ImplementationError(
  360. "This method must be implemented in the subclasses")
  361. def map_exists(self):
  362. """!Return True in case the map exists in the grass spatial database
  363. @return True if map exists, False otherwise
  364. """
  365. raise ImplementationError(
  366. "This method must be implemented in the subclasses")
  367. def read_info(self):
  368. """!Read the map info from the grass file system based database and
  369. store the content into a dictionary
  370. """
  371. raise ImplementationError(
  372. "This method must be implemented in the subclasses")
  373. def load(self):
  374. """!Load the content of this object from the grass
  375. file system based database"""
  376. raise ImplementationError(
  377. "This method must be implemented in the subclasses")
  378. def _convert_timestamp(self):
  379. """!Convert the valid time into a grass datetime library
  380. compatible timestamp string
  381. This methods works for reltaive and absolute time
  382. @return the grass timestamp string
  383. """
  384. start = ""
  385. if self.is_time_absolute():
  386. start_time, end_time, tz = self.get_absolute_time()
  387. start = datetime_to_grass_datetime_string(start_time)
  388. if end_time is not None:
  389. end = datetime_to_grass_datetime_string(end_time)
  390. start += " / %s" % (end)
  391. else:
  392. start_time, end_time, unit = self.get_relative_time()
  393. start = "%i %s" % (int(start_time), unit)
  394. if end_time is not None:
  395. end = "%i %s" % (int(end_time), unit)
  396. start += " / %s" % (end)
  397. return start
  398. def get_map_id(self):
  399. """!Return the map id. The map id is the unique map identifier
  400. in grass and must not be equal to the
  401. primary key identifier (id) of the map in the database.
  402. Since vector maps may have layer information,
  403. the unique id is a combination of name, layer and mapset.
  404. Use get_map_id() every time your need to access the grass map
  405. in the file system but not to identify
  406. map information in the temporal database.
  407. """
  408. return self.base.get_map_id()
  409. def build_id(self, name, mapset, layer=None):
  410. """!Convenient method to build the unique identifier
  411. Existing layer and mapset definitions in the name
  412. string will be reused
  413. @param return the id of the vector map as name(:layer)@mapset
  414. while layer is optional
  415. """
  416. # Check if the name includes any mapset
  417. if name.find("@") >= 0:
  418. name, mapset = name.split("@")
  419. # Check for layer number in map name
  420. if name.find(":") >= 0:
  421. name, layer = name.split(":")
  422. if layer is not None:
  423. return "%s:%s@%s" % (name, layer, mapset)
  424. else:
  425. return "%s@%s" % (name, mapset)
  426. def get_layer(self):
  427. """!Return the layer of the map or None in case no layer is defined"""
  428. return self.base.get_layer()
  429. def print_info(self):
  430. """!Print information about this class in human readable style"""
  431. if self.get_type() == "raster":
  432. # 1 2 3 4 5 6 7
  433. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  434. print " +-------------------- Raster Dataset ----------------------------------------+"
  435. if self.get_type() == "raster3d":
  436. # 1 2 3 4 5 6 7
  437. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  438. print " +-------------------- 3D Raster Dataset -------------------------------------+"
  439. if self.get_type() == "vector":
  440. # 1 2 3 4 5 6 7
  441. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  442. print " +-------------------- Vector Dataset ----------------------------------------+"
  443. print " | |"
  444. self.base.print_info()
  445. if self.is_time_absolute():
  446. self.absolute_time.print_info()
  447. if self.is_time_relative():
  448. self.relative_time.print_info()
  449. self.spatial_extent.print_info()
  450. self.metadata.print_info()
  451. datasets = self.get_registered_datasets()
  452. count = 0
  453. string = ""
  454. if datasets is not None:
  455. for ds in datasets:
  456. if count > 0 and count % 3 == 0:
  457. string += "\n | ............................ "
  458. count = 0
  459. if count == 0:
  460. string += ds["id"]
  461. else:
  462. string += ",%s" % ds["id"]
  463. count += 1
  464. print " | Registered datasets ........ " + string
  465. if self.is_temporal_topology_build():
  466. self.print_temporal_topology_info()
  467. print " +----------------------------------------------------------------------------+"
  468. def print_shell_info(self):
  469. """!Print information about this class in shell style"""
  470. self.base.print_shell_info()
  471. if self.is_time_absolute():
  472. self.absolute_time.print_shell_info()
  473. if self.is_time_relative():
  474. self.relative_time.print_shell_info()
  475. self.spatial_extent.print_shell_info()
  476. self.metadata.print_shell_info()
  477. datasets = self.get_registered_datasets()
  478. count = 0
  479. string = ""
  480. if datasets is not None:
  481. for ds in datasets:
  482. if count == 0:
  483. string += ds["id"]
  484. else:
  485. string += ",%s" % ds["id"]
  486. count += 1
  487. print "registered_datasets=" + string
  488. if self.is_temporal_topology_build():
  489. self.print_temporal_topology_shell_info()
  490. def insert(self, dbif=None, execute=True):
  491. """!Insert temporal dataset entry into database from the internal structure
  492. This functions assures that the timetsamp is written to the
  493. grass file system based database
  494. @param dbif: The database interface to be used
  495. @param execute: If True the SQL statements will be executed.
  496. If False the prepared SQL statements are
  497. returned and must be executed by the caller.
  498. """
  499. self.write_timestamp_to_grass()
  500. return AbstractDataset.insert(self, dbif, execute)
  501. def update(self, dbif=None, execute=True):
  502. """!Update temporal dataset entry of database from the internal structure
  503. excluding None variables
  504. This functions assures that the timetsamp is written to the
  505. grass file system based database
  506. @param dbif: The database interface to be used
  507. @param execute: If True the SQL statements will be executed.
  508. If False the prepared SQL statements are
  509. returned and must be executed by the caller.
  510. """
  511. self.write_timestamp_to_grass()
  512. return AbstractDataset.update(self, dbif, execute)
  513. def update_all(self, dbif=None, execute=True):
  514. """!Update temporal dataset entry of database from the internal structure
  515. and include None varuables.
  516. This functions assures that the timetsamp is written to the
  517. grass file system based database
  518. @param dbif: The database interface to be used
  519. @param execute: If True the SQL statements will be executed.
  520. If False the prepared SQL statements are
  521. returned and must be executed by the caller.
  522. """
  523. self.write_timestamp_to_grass()
  524. return AbstractDataset.update_all(self, dbif, execute)
  525. def set_absolute_time(self, start_time, end_time=None, timezone=None):
  526. """!Set the absolute time interval with start time and end time
  527. @param start_time: a datetime object specifying the start time of the map
  528. @param end_time: a datetime object specifying the end time of the map
  529. @param timezone: Thee timezone of the map
  530. """
  531. if start_time and not isinstance(start_time, datetime):
  532. if self.get_layer() is not None:
  533. core.fatal(_("Start time must be of type datetime "
  534. "for %s map <%s> with layer: %s") % \
  535. (self.get_type(), self.get_map_id(),
  536. self.get_layer()))
  537. else:
  538. core.fatal(_("Start time must be of type "
  539. "datetime ""for %s map <%s>") % \
  540. (self.get_type(), self.get_map_id()))
  541. if end_time and not isinstance(end_time, datetime):
  542. if self.get_layer():
  543. core.fatal(_("End time must be of type datetime "
  544. "for %s map <%s> with layer: %s") % \
  545. (self.get_type(), self.get_map_id(),
  546. self.get_layer()))
  547. else:
  548. core.fatal(_("End time must be of type datetime "
  549. "for %s map <%s>") % (self.get_type(),
  550. self.get_map_id()))
  551. if start_time is not None and end_time is not None:
  552. if start_time > end_time:
  553. if self.get_layer():
  554. core.fatal(_("End time must be greater than "
  555. "start time for %s map <%s> with layer: %s") %\
  556. (self.get_type(), self.get_map_id(),
  557. self.get_layer()))
  558. else:
  559. core.fatal(_("End time must be greater than "
  560. "start time for %s map <%s>") % \
  561. (self.get_type(), self.get_map_id()))
  562. else:
  563. # Do not create an interval in case start and end time are equal
  564. if start_time == end_time:
  565. end_time = None
  566. self.base.set_ttype("absolute")
  567. self.absolute_time.set_start_time(start_time)
  568. self.absolute_time.set_end_time(end_time)
  569. self.absolute_time.set_timezone(timezone)
  570. def update_absolute_time(self, start_time, end_time=None,
  571. timezone=None, dbif=None):
  572. """!Update the absolute time
  573. This functions assures that the timetsamp is written to the
  574. grass file system based database
  575. @param start_time: a datetime object specifying the start time of the map
  576. @param end_time: a datetime object specifying the end time of the map
  577. @param timezone: Thee timezone of the map
  578. """
  579. dbif, connect = init_dbif(dbif)
  580. self.set_absolute_time(start_time, end_time, timezone)
  581. self.absolute_time.update_all(dbif)
  582. self.base.update(dbif)
  583. if connect:
  584. dbif.close()
  585. self.write_timestamp_to_grass()
  586. def set_relative_time(self, start_time, end_time, unit):
  587. """!Set the relative time interval
  588. @param start_time: A double value
  589. @param end_time: A double value
  590. @param unit: The unit of the relative time. Supported units:
  591. years, months, days, hours, minutes, seconds
  592. Return True for success and False otherwise
  593. """
  594. if not self.check_relative_time_unit(unit):
  595. if self.get_layer() is not None:
  596. core.error(_("Unsupported relative time unit type for %s map "
  597. "<%s> with layer %s: %s") % (self.get_type(),
  598. self.get_id(),
  599. self.get_layer(),
  600. unit))
  601. else:
  602. core.error(_("Unsupported relative time unit type for %s map "
  603. "<%s>: %s") % (self.get_type(), self.get_id(),
  604. unit))
  605. return False
  606. if start_time is not None and end_time is not None:
  607. if int(start_time) > int(end_time):
  608. if self.get_layer() is not None:
  609. core.error(_("End time must be greater than start time for"
  610. " %s map <%s> with layer %s") % \
  611. (self.get_type(), self.get_id(),
  612. self.get_layer()))
  613. else:
  614. core.error(_("End time must be greater than start time for"
  615. " %s map <%s>") % (self.get_type(),
  616. self.get_id()))
  617. return False
  618. else:
  619. # Do not create an interval in case start and end time are equal
  620. if start_time == end_time:
  621. end_time = None
  622. self.base.set_ttype("relative")
  623. self.relative_time.set_unit(unit)
  624. self.relative_time.set_start_time(int(start_time))
  625. if end_time is not None:
  626. self.relative_time.set_end_time(int(end_time))
  627. else:
  628. self.relative_time.set_end_time(None)
  629. return True
  630. def update_relative_time(self, start_time, end_time, unit, dbif=None):
  631. """!Update the relative time interval
  632. This functions assures that the timetsamp is written to the
  633. grass file system based database
  634. @param start_time: A double value
  635. @param end_time: A double value
  636. @param dbif: The database interface to be used
  637. """
  638. dbif, connect = init_dbif(dbif)
  639. if self.set_relative_time(start_time, end_time, unit):
  640. self.relative_time.update_all(dbif)
  641. self.base.update(dbif)
  642. if connect:
  643. dbif.close()
  644. self.write_timestamp_to_grass()
  645. def set_spatial_extent(self, north, south, east, west, top=0, bottom=0):
  646. """!Set the spatial extent of the map
  647. @param north: The northern edge
  648. @param south: The southern edge
  649. @param east: The eastern edge
  650. @param west: The western edge
  651. @param top: The top edge
  652. @param bottom: The bottom edge
  653. """
  654. self.spatial_extent.set_spatial_extent(
  655. north, south, east, west, top, bottom)
  656. def check_valid_time(self):
  657. """!Check for correct valid time"""
  658. if self.is_time_absolute():
  659. start, end, tz = self.get_absolute_time()
  660. else:
  661. start, end, unit = self.get_relative_time()
  662. if start is not None:
  663. if end is not None:
  664. if start >= end:
  665. if self.get_layer() is not None:
  666. core.error(_("Map <%s> with layer %s has incorrect "
  667. "time interval, start time is greater "
  668. "than end time") % (self.get_map_id(),
  669. self.get_layer()))
  670. else:
  671. core.error(_("Map <%s> has incorrect time interval, "
  672. "start time is greater than end time") % \
  673. (self.get_map_id()))
  674. return False
  675. else:
  676. core.error(_("Map <%s> has incorrect start time") %
  677. (self.get_map_id()))
  678. return False
  679. return True
  680. def delete(self, dbif=None, update=True, execute=True):
  681. """!Delete a map entry from database if it exists
  682. Remove dependent entries:
  683. * Remove the map entry in each space time dataset in which this map
  684. is registered
  685. * Remove the space time dataset register table
  686. @param dbif: The database interface to be used
  687. @param update: Call for each unregister statement the update from
  688. registered maps of the space time dataset.
  689. This can slow down the un-registration process significantly.
  690. @param execute: If True the SQL DELETE and DROP table statements will
  691. 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. dbif, connect = init_dbif(dbif)
  698. statement = ""
  699. if self.is_in_db(dbif):
  700. # SELECT all needed information from the database
  701. self.metadata.select(dbif)
  702. # First we unregister from all dependent space time datasets
  703. statement += self.unregister(
  704. dbif=dbif, update=update, execute=False)
  705. # Remove the strds register table
  706. if self.get_stds_register() is not None:
  707. statement += "DROP TABLE " + self.get_stds_register() + ";\n"
  708. core.verbose(_("Delete %s dataset <%s> from temporal database")
  709. % (self.get_type(), self.get_id()))
  710. # Delete yourself from the database, trigger functions will
  711. # take care of dependencies
  712. statement += self.base.get_delete_statement()
  713. if execute:
  714. dbif.execute_transaction(statement)
  715. # Remove the timestamp from the file system
  716. self.remove_timestamp_from_grass()
  717. self.reset(None)
  718. if connect:
  719. dbif.close()
  720. if execute:
  721. return ""
  722. return statement
  723. def unregister(self, dbif=None, update=True, execute=True):
  724. """! Remove the map entry in each space time dataset in which this map
  725. is registered
  726. @param dbif: The database interface to be used
  727. @param update: Call for each unregister statement the update from
  728. registered maps of the space time dataset. This can
  729. slow down the un-registration process significantly.
  730. @param execute: If True the SQL DELETE and DROP table statements
  731. will be executed.
  732. If False the prepared SQL statements are
  733. returned and must be executed by the caller.
  734. @return The SQL statements if execute == False, else an empty string
  735. """
  736. if self.get_layer() is not None:
  737. core.verbose(_("Unregister %(type)s map <%(map)s> with "
  738. "layer %(layer)s from space time datasets" % \
  739. {'type':self.get_type(), 'map':self.get_map_id(),
  740. 'layer':self.get_layer()}))
  741. else:
  742. core.verbose(_("Unregister %(type)s map <%(map)s> "
  743. "from space time datasets"
  744. % {'type':self.get_type(), 'map':self.get_map_id()}))
  745. statement = ""
  746. dbif, connect = init_dbif(dbif)
  747. # Get all datasets in which this map is registered
  748. rows = self.get_registered_datasets(dbif)
  749. # For each stds in which the map is registered
  750. if rows is not None:
  751. count = 0
  752. num_sps = len(rows)
  753. for row in rows:
  754. core.percent(count, num_sps, 1)
  755. count += 1
  756. # Create a space time dataset object to remove the map
  757. # from its register
  758. stds = self.get_new_stds_instance(row["id"])
  759. stds.metadata.select(dbif)
  760. statement += stds.unregister_map(self, dbif, False)
  761. # Take care to update the space time dataset after
  762. # the map has been unregistered
  763. if update == True and execute == True:
  764. stds.update_from_registered_maps(dbif)
  765. core.percent(1, 1, 1)
  766. if execute:
  767. dbif.execute_transaction(statement)
  768. if connect:
  769. dbif.close()
  770. if execute:
  771. return ""
  772. return statement
  773. def get_registered_datasets(self, dbif=None):
  774. """!Return all space time dataset ids in which this map is registered as
  775. dictionary like rows with column "id" or None if this map is not
  776. registered in any space time dataset.
  777. @param dbif: The database interface to be used
  778. """
  779. dbif, connect = init_dbif(dbif)
  780. rows = None
  781. try:
  782. if self.get_stds_register() is not None:
  783. # Select all stds tables in which this map is registered
  784. sql = "SELECT id FROM " + self.get_stds_register()
  785. dbif.cursor.execute(sql)
  786. rows = dbif.cursor.fetchall()
  787. except:
  788. core.error(_("Unable to select space time dataset register table "
  789. "<%s>") % (self.get_stds_register()))
  790. if connect:
  791. dbif.close()
  792. return rows
  793. ###############################################################################
  794. if __name__ == "__main__":
  795. import doctest
  796. doctest.testmod()