abstract_map_dataset.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  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. @code
  7. import grass.temporal as tgis
  8. ...
  9. @endcode
  10. (C) 2008-2011 by the GRASS Development Team
  11. This program is free software under the GNU General Public
  12. License (>=v2). Read the file COPYING that comes with GRASS
  13. for details.
  14. @author Soeren Gebbert
  15. """
  16. from abstract_dataset import *
  17. from datetime_math import *
  18. ###############################################################################
  19. class abstract_map_dataset(abstract_dataset):
  20. """!This is the base class for all maps (raster, vector, raster3d)
  21. providing additional function to set the valid time and the spatial extent.
  22. """
  23. def get_new_stds_instance(self, ident):
  24. """!Return a new space time dataset instance in which maps are stored with the type of this class
  25. @param ident: The identifier of the dataset
  26. """
  27. raise IOError("This method must be implemented in the subclasses")
  28. def get_stds_register(self):
  29. """!Return the space time dataset register table name in which stds are listed in which this map is registered"""
  30. raise IOError("This method must be implemented in the subclasses")
  31. def set_stds_register(self, name):
  32. """!Set the space time dataset register table name.
  33. This table stores all space time datasets in which this map is registered.
  34. @param ident: The name of the register table
  35. """
  36. raise IOError("This method must be implemented in the subclasses")
  37. def check_resolution_with_current_region(self):
  38. """!Check if the raster or voxel resolution is finer than the current resolution
  39. Return "finer" in case the raster/voxel resolution is finer than the current region
  40. Return "coarser" in case the raster/voxel resolution is coarser than the current region
  41. Vector maps are alwyas finer than the current region
  42. """
  43. raise IOError("This method must be implemented in the subclasses")
  44. def has_grass_timestamp(self):
  45. """!Check if a grass file bsased time stamp exists for this map.
  46. """
  47. raise IOError("This method must be implemented in the subclasses")
  48. def write_timestamp_to_grass(self):
  49. """!Write the timestamp of this map into the map metadata in the grass file system based spatial
  50. database.
  51. """
  52. raise IOError("This method must be implemented in the subclasses")
  53. def remove_timestamp_from_grass(self):
  54. """!Remove the timestamp from the grass file system based spatial database
  55. """
  56. raise IOError("This method must be implemented in the subclasses")
  57. def map_exists(self):
  58. """!Return True in case the map exists in the grass spatial database
  59. @return True if map exists, False otherwise
  60. """
  61. raise IOError("This method must be implemented in the subclasses")
  62. def read_info(self):
  63. """!Read the map info from the grass file system based database and store the content
  64. into a dictionary
  65. """
  66. raise IOError("This method must be implemented in the subclasses")
  67. def load(self):
  68. """!Load the content of this object from the grass file system based database"""
  69. raise IOError("This method must be implemented in the subclasses")
  70. def _convert_timestamp(self):
  71. """!Convert the valid time into a grass datetime library compatible timestamp string
  72. This methods works for reltaive and absolute time
  73. @return the grass timestamp string
  74. """
  75. start = ""
  76. if self.is_time_absolute():
  77. start_time, end_time, tz = self.get_absolute_time()
  78. start = datetime_to_grass_datetime_string(start_time)
  79. if end_time:
  80. end = datetime_to_grass_datetime_string(end_time)
  81. start += " / %s"%(end)
  82. else:
  83. start_time, end_time, unit = self.get_relative_time()
  84. start = "%i %s"%(int(start_time), unit)
  85. if end_time != None:
  86. end = "%i %s"%(int(end_time), unit)
  87. start += " / %s"%(end)
  88. return start
  89. def get_map_id(self):
  90. """!Return the map id. The map id is the unique map identifier in grass and must not be equal to the
  91. primary key identifier (id) of the map in the database. Since vector maps may have layer information,
  92. the unique id is a combination of name, layer and mapset.
  93. Use get_map_id() every time your need to access the grass map in the file system but not to identify
  94. map information in the temporal database.
  95. """
  96. return self.base.get_map_id()
  97. def build_id(self, name, mapset, layer=None):
  98. """!Convenient method to build the unique identifier
  99. Existing layer and mapset definitions in the name string will be reused
  100. @param return the id of the vector map as name(:layer)@mapset while layer is optional
  101. """
  102. # Check if the name includes any mapset
  103. if name.find("@") >= 0:
  104. name, mapset = name.split("@")[0]
  105. # Check for layer number in map name
  106. if name.find(":") >= 0:
  107. name, layer = name.split(":")
  108. if layer:
  109. return "%s:%s@%s"%(name, layer, mapset)
  110. else:
  111. return "%s@%s"%(name, mapset)
  112. def get_layer(self):
  113. """!Return the layer of the map or None in case no layer is defined"""
  114. return self.base.get_layer()
  115. def print_info(self):
  116. """!Print information about this class in human readable style"""
  117. if self.get_type() == "raster":
  118. # 1 2 3 4 5 6 7
  119. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  120. print ""
  121. print " +-------------------- Raster Dataset ----------------------------------------+"
  122. if self.get_type() == "raster3d":
  123. # 1 2 3 4 5 6 7
  124. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  125. print ""
  126. print " +-------------------- Raster3d Dataset --------------------------------------+"
  127. if self.get_type() == "vector":
  128. # 1 2 3 4 5 6 7
  129. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  130. print ""
  131. print " +-------------------- Vector Dataset ----------------------------------------+"
  132. print " | |"
  133. self.base.print_info()
  134. if self.is_time_absolute():
  135. self.absolute_time.print_info()
  136. if self.is_time_relative():
  137. self.relative_time.print_info()
  138. self.spatial_extent.print_info()
  139. self.metadata.print_info()
  140. datasets = self.get_registered_datasets()
  141. count = 0
  142. string = ""
  143. if datasets:
  144. for ds in datasets:
  145. if count > 0 and count % 3 == 0:
  146. string += "\n | ............................ "
  147. count = 0
  148. if count == 0:
  149. string += ds["id"]
  150. else:
  151. string += ",%s" % ds["id"]
  152. count += 1
  153. print " | Registered datasets ........ " + string
  154. print " +----------------------------------------------------------------------------+"
  155. def print_shell_info(self):
  156. """!Print information about this class in shell style"""
  157. self.base.print_shell_info()
  158. if self.is_time_absolute():
  159. self.absolute_time.print_shell_info()
  160. if self.is_time_relative():
  161. self.relative_time.print_shell_info()
  162. self.spatial_extent.print_shell_info()
  163. self.metadata.print_shell_info()
  164. datasets = self.get_registered_datasets()
  165. count = 0
  166. string = ""
  167. for ds in datasets:
  168. if count == 0:
  169. string += ds["id"]
  170. else:
  171. string += ",%s" % ds["id"]
  172. count += 1
  173. print "registered_datasets=" + string
  174. def insert(self, dbif=None, execute=True):
  175. """!Insert temporal dataset entry into database from the internal structure
  176. This functions assures that the timetsamp is written to the grass file system based database
  177. @param dbif: The database interface to be used
  178. @param execute: If True the SQL statements will be executed.
  179. If False the prepared SQL statements are returned and must be executed by the caller.
  180. """
  181. self.write_timestamp_to_grass()
  182. return abstract_dataset.insert(self, dbif, execute)
  183. def update(self, dbif=None, execute=True):
  184. """!Update temporal dataset entry of database from the internal structure
  185. excluding None variables
  186. This functions assures that the timetsamp is written to the grass file system based database
  187. @param dbif: The database interface to be used
  188. @param execute: If True the SQL statements will be executed.
  189. If False the prepared SQL statements are returned and must be executed by the caller.
  190. """
  191. self.write_timestamp_to_grass()
  192. return abstract_dataset.update(self, dbif, execute)
  193. def update_all(self, dbif=None, execute=True):
  194. """!Update temporal dataset entry of database from the internal structure
  195. and include None varuables.
  196. This functions assures that the timetsamp is written to the grass file system based database
  197. @param dbif: The database interface to be used
  198. @param execute: If True the SQL statements will be executed.
  199. If False the prepared SQL statements are returned and must be executed by the caller.
  200. """
  201. self.write_timestamp_to_grass()
  202. return abstract_dataset.update_all(self, dbif, execute)
  203. def set_absolute_time(self, start_time, end_time=None, timezone=None):
  204. """!Set the absolute time interval with start time and end time
  205. @param start_time: a datetime object specifying the start time of the map
  206. @param end_time: a datetime object specifying the end time of the map
  207. @param timezone: Thee timezone of the map
  208. """
  209. if start_time and not isinstance(start_time, datetime) :
  210. if self.get_layer():
  211. core.fatal(_("Start time must be of type datetime for %s map <%s> with layer: %s") % (self.get_type(), self.get_map_id(), self.get_layer()))
  212. else:
  213. core.fatal(_("Start time must be of type datetime for %s map <%s>") % (self.get_type(), self.get_map_id()))
  214. if end_time and not isinstance(end_time, datetime) :
  215. if self.get_layer():
  216. core.fatal(_("End time must be of type datetime for %s map <%s> with layer: %s") % (self.get_type(), self.get_map_id(), self.get_layer()))
  217. else:
  218. core.fatal(_("End time must be of type datetime for %s map <%s>") % (self.get_type(), self.get_map_id()))
  219. if start_time and end_time:
  220. if start_time > end_time:
  221. if self.get_layer():
  222. core.fatal(_("End time must be greater than start time for %s map <%s> with layer: %s") % (self.get_type(), self.get_map_id(), self.get_layer()))
  223. else:
  224. core.fatal(_("End time must be greater than start time for %s map <%s>") % (self.get_type(), self.get_map_id()))
  225. else:
  226. # Do not create an interval in case start and end time are equal
  227. if start_time == end_time:
  228. end_time = None
  229. self.base.set_ttype("absolute")
  230. self.absolute_time.set_start_time(start_time)
  231. self.absolute_time.set_end_time(end_time)
  232. self.absolute_time.set_timezone(timezone)
  233. def update_absolute_time(self, start_time, end_time=None, timezone=None, dbif = None):
  234. """!Update the absolute time
  235. This functions assures that the timetsamp is written to the grass file system based database
  236. @param start_time: a datetime object specifying the start time of the map
  237. @param end_time: a datetime object specifying the end time of the map
  238. @param timezone: Thee timezone of the map
  239. """
  240. dbif, connect = init_dbif(dbif)
  241. self.set_absolute_time(start_time, end_time, timezone)
  242. self.absolute_time.update_all(dbif)
  243. self.base.update(dbif)
  244. if connect == True:
  245. dbif.close()
  246. self.write_timestamp_to_grass()
  247. def set_relative_time(self, start_time, end_time, unit):
  248. """!Set the relative time interval
  249. @param start_time: A double value
  250. @param end_time: A double value
  251. @param unit: The unit of the relative time. Supported units: years, months, days, hours, minutes, seconds
  252. Return True for success and False otherwise
  253. """
  254. if not self.check_relative_time_unit(unit):
  255. if self.get_layer():
  256. core.error(_("Unsupported relative time unit type for %s map <%s> with layer %s: %s") % (self.get_type(), self.get_id(), self.get_layer(), unit))
  257. else:
  258. core.error(_("Unsupported relative time unit type for %s map <%s>: %s") % (self.get_type(), self.get_id(), unit))
  259. return False
  260. if start_time != None and end_time != None:
  261. if int(start_time) > int(end_time):
  262. if self.get_layer():
  263. core.error(_("End time must be greater than start time for %s map <%s> with layer %s") % (self.get_type(), self.get_id(), self.get_layer()))
  264. else:
  265. core.error(_("End time must be greater than start time for %s map <%s>") % (self.get_type(), self.get_id()))
  266. return False
  267. else:
  268. # Do not create an interval in case start and end time are equal
  269. if start_time == end_time:
  270. end_time = None
  271. self.base.set_ttype("relative")
  272. self.relative_time.set_unit(unit)
  273. self.relative_time.set_start_time(int(start_time))
  274. if end_time != None:
  275. self.relative_time.set_end_time(int(end_time))
  276. else:
  277. self.relative_time.set_end_time(None)
  278. return True
  279. def update_relative_time(self, start_time, end_time, unit, dbif = None):
  280. """!Update the relative time interval
  281. This functions assures that the timetsamp is written to the grass file system based database
  282. @param start_time: A double value
  283. @param end_time: A double value
  284. @param dbif: The database interface to be used
  285. """
  286. dbif, connect = init_dbif(dbif)
  287. if self.set_relative_time(start_time, end_time, unit):
  288. self.relative_time.update_all(dbif)
  289. self.base.update(dbif)
  290. if connect == True:
  291. dbif.close()
  292. self.write_timestamp_to_grass()
  293. def set_spatial_extent(self, north, south, east, west, top=0, bottom=0):
  294. """!Set the spatial extent of the map
  295. @param north: The northern edge
  296. @param south: The southern edge
  297. @param east: The eastern edge
  298. @param west: The western edge
  299. @param top: The top edge
  300. @param bottom: The bottom edge
  301. """
  302. self.spatial_extent.set_spatial_extent(north, south, east, west, top, bottom)
  303. def check_valid_time(self):
  304. """!Check for correct valid time"""
  305. if self.is_time_absolute():
  306. start, end, tz = self.get_absolute_time()
  307. else:
  308. start, end, unit = self.get_relative_time()
  309. if start != None:
  310. if end != None:
  311. if start >= end:
  312. if self.get_layer():
  313. core.error(_("Map <%s> with layer %s has incorrect time interval, start time is greater than end time") % (self.get_map_id(), self.get_layer()))
  314. else:
  315. core.error(_("Map <%s> has incorrect time interval, start time is greater than end time") % (self.get_map_id()))
  316. return False
  317. else:
  318. core.error(_("Map <%s> has incorrect start time") % (self.get_map_id()))
  319. return False
  320. return True
  321. def delete(self, dbif=None, update=True, execute = True):
  322. """!Delete a map entry from database if it exists
  323. Remove dependent entries:
  324. * Remove the map entry in each space time dataset in which this map is registered
  325. * Remove the space time dataset register table
  326. @param dbif: The database interface to be used
  327. @param update: Call for each unregister statement the update from registered maps
  328. of the space time dataset. This can slow down the un-registration process significantly.
  329. @param execute: If True the SQL DELETE and DROP table statements will be executed.
  330. If False the prepared SQL statements are returned and must be executed by the caller.
  331. @return The SQL statements if execute == False, else an empty string, None in case of a failure
  332. """
  333. dbif, connect = init_dbif(dbif)
  334. statement = ""
  335. if self.is_in_db(dbif):
  336. # SELECT all needed information from the database
  337. self.metadata.select(dbif)
  338. # First we unregister from all dependent space time datasets
  339. statement += self.unregister(dbif=dbif, update=update, execute=False)
  340. # Remove the strds register table
  341. if self.get_stds_register():
  342. statement += "DROP TABLE " + self.get_stds_register() + ";\n"
  343. core.verbose(_("Delete %s dataset <%s> from temporal database") % (self.get_type(), self.get_id()))
  344. # Delete yourself from the database, trigger functions will take care of dependencies
  345. statement += self.base.get_delete_statement()
  346. if execute == True:
  347. dbif.execute_transaction(statement)
  348. # Remove the timestamp from the file system
  349. self.remove_timestamp_from_grass()
  350. self.reset(None)
  351. if connect == True:
  352. dbif.close()
  353. if execute:
  354. return ""
  355. return statement
  356. def unregister(self, dbif=None, update=True, execute=True):
  357. """! Remove the map entry in each space time dataset in which this map is registered
  358. @param dbif: The database interface to be used
  359. @param update: Call for each unregister statement the update from registered maps
  360. of the space time dataset. This can slow down the un-registration process significantly.
  361. @param execute: If True the SQL DELETE and DROP table statements will be executed.
  362. If False the prepared SQL statements are returned and must be executed by the caller.
  363. @return The SQL statements if execute == False, else an empty string
  364. """
  365. if self.get_layer():
  366. core.verbose(_("Unregister %s map <%s> with layer %s from space time datasets") % \
  367. (self.get_type(), self.get_map_id(), self.get_layer()))
  368. else:
  369. core.verbose(_("Unregister %s map <%s> from space time datasets") % (self.get_type(), self.get_map_id()))
  370. statement = ""
  371. dbif, connect = init_dbif(dbif)
  372. # Get all datasets in which this map is registered
  373. rows = self.get_registered_datasets(dbif)
  374. # For each stds in which the map is registered
  375. if rows:
  376. count = 0
  377. num_sps = len(rows)
  378. for row in rows:
  379. core.percent(count, num_sps, 1)
  380. count += 1
  381. # Create a space time dataset object to remove the map
  382. # from its register
  383. stds = self.get_new_stds_instance(row["id"])
  384. stds.metadata.select(dbif)
  385. statement += stds.unregister_map(self, dbif, False)
  386. # Take care to update the space time dataset after
  387. # the map has been unregistered
  388. if update == True and execute == True:
  389. stds.update_from_registered_maps(dbif)
  390. core.percent(1, 1, 1)
  391. if execute == True:
  392. dbif.execute_transaction(statement)
  393. if connect == True:
  394. dbif.close()
  395. if execute:
  396. return ""
  397. return statement
  398. def get_registered_datasets(self, dbif=None):
  399. """!Return all space time dataset ids in which this map is registered as
  400. dictionary like rows with column "id" or None if this map is not registered in any
  401. space time dataset.
  402. @param dbif: The database interface to be used
  403. """
  404. dbif, connect = init_dbif(dbif)
  405. rows = None
  406. try:
  407. if self.get_stds_register() != None:
  408. # Select all stds tables in which this map is registered
  409. sql = "SELECT id FROM " + self.get_stds_register()
  410. dbif.cursor.execute(sql)
  411. rows = dbif.cursor.fetchall()
  412. except:
  413. core.error(_("Unable to select space time dataset register table <%s>") % (self.get_stds_register()))
  414. if connect == True:
  415. dbif.close()
  416. return rows