abstract_map_dataset.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  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 get_timestamp_module_name(self):
  38. """Return the name of the C-module to set the time stamp in the file system"""
  39. raise IOError("This method must be implemented in the subclasses")
  40. def load(self):
  41. """Load the content of this object from map files"""
  42. raise IOError("This method must be implemented in the subclasses")
  43. def get_map_id(self):
  44. """Return the map id. The map id is the unique map identifier in grass and must not be equal to the
  45. primary key identifier (id) of the map in the database. Since vector maps may have layer information,
  46. the unique id is a combination of name, layer and mapset.
  47. Use get_map_id() every time your need to access the grass map in the file system but not to identify
  48. map information in the temporal database.
  49. """
  50. return self.base.get_map_id()
  51. def build_id(self, name, mapset, layer=None):
  52. """Convenient method to build the unique identifier
  53. Existing layer and mapset definitions in the name string will be reused
  54. @param return the id of the vector map as name(:layer)@mapset while layer is optional
  55. """
  56. # Check if the name includes any mapset
  57. if name.find("@") >= 0:
  58. name, mapset = name.split("@")[0]
  59. # Check for layer number in map name
  60. if name.find(":") >= 0:
  61. name, layer = name.split(":")[0]
  62. if layer:
  63. return "%s:%s@%s"%(name, layer, mapset)
  64. else:
  65. return "%s@%s"%(name, mapset)
  66. def get_layer(self):
  67. """Return the layer of the map or None in case no layer is defined"""
  68. return self.base.get_layer()
  69. def print_info(self):
  70. """Print information about this class in human readable style"""
  71. if self.get_type() == "raster":
  72. # 1 2 3 4 5 6 7
  73. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  74. print ""
  75. print " +-------------------- Raster Dataset ----------------------------------------+"
  76. if self.get_type() == "raster3d":
  77. # 1 2 3 4 5 6 7
  78. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  79. print ""
  80. print " +-------------------- Raster3d Dataset --------------------------------------+"
  81. if self.get_type() == "vector":
  82. # 1 2 3 4 5 6 7
  83. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  84. print ""
  85. print " +-------------------- Vector Dataset ----------------------------------------+"
  86. print " | |"
  87. self.base.print_info()
  88. if self.is_time_absolute():
  89. self.absolute_time.print_info()
  90. if self.is_time_relative():
  91. self.relative_time.print_info()
  92. self.spatial_extent.print_info()
  93. self.metadata.print_info()
  94. datasets = self.get_registered_datasets()
  95. count = 0
  96. string = ""
  97. if datasets:
  98. for ds in datasets:
  99. if count == 0:
  100. string += ds["id"]
  101. else:
  102. string += ",%s" % ds["id"]
  103. count += 1
  104. if count > 2:
  105. string += " | ............................ "
  106. print " | Registered datasets ........ " + string
  107. print " +----------------------------------------------------------------------------+"
  108. def print_shell_info(self):
  109. """Print information about this class in shell style"""
  110. self.base.print_shell_info()
  111. if self.is_time_absolute():
  112. self.absolute_time.print_shell_info()
  113. if self.is_time_relative():
  114. self.relative_time.print_shell_info()
  115. self.spatial_extent.print_shell_info()
  116. self.metadata.print_shell_info()
  117. datasets = self.get_registered_datasets()
  118. count = 0
  119. string = ""
  120. for ds in datasets:
  121. if count == 0:
  122. string += ds["id"]
  123. else:
  124. string += ",%s" % ds["id"]
  125. count += 1
  126. print "registered_datasets=" + string
  127. def set_absolute_time(self, start_time, end_time=None, timezone=None):
  128. """Set the absolute time interval with start time and end time
  129. @param start_time: a datetime object specifying the start time of the map
  130. @param end_time: a datetime object specifying the end time of the map
  131. @param timezone: Thee timezone of the map
  132. """
  133. if start_time and not isinstance(start_time, datetime) :
  134. if self.get_layer():
  135. 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()))
  136. else:
  137. core.fatal(_("Start time must be of type datetime for %s map <%s>") % (self.get_type(), self.get_map_id()))
  138. if end_time and not isinstance(end_time, datetime) :
  139. if self.get_layer():
  140. 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()))
  141. else:
  142. core.fatal(_("End time must be of type datetime for %s map <%s>") % (self.get_type(), self.get_map_id()))
  143. if start_time and end_time:
  144. if start_time > end_time:
  145. if self.get_layer():
  146. 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()))
  147. else:
  148. core.fatal(_("End time must be greater than start time for %s map <%s>") % (self.get_type(), self.get_map_id()))
  149. else:
  150. # Do not create an interval in case start and end time are equal
  151. if start_time == end_time:
  152. end_time = None
  153. self.base.set_ttype("absolute")
  154. self.absolute_time.set_start_time(start_time)
  155. self.absolute_time.set_end_time(end_time)
  156. self.absolute_time.set_timezone(timezone)
  157. def update_absolute_time(self, start_time, end_time=None, timezone=None, dbif = None):
  158. """Update the absolute time
  159. This method should always be used to set the absolute time. Do not use insert() or update()
  160. to the the time. This update functions assures that the *.timestamp commands are invoked.
  161. @param start_time: a datetime object specifying the start time of the map
  162. @param end_time: a datetime object specifying the end time of the map
  163. @param timezone: Thee timezone of the map
  164. """
  165. connect = False
  166. if dbif == None:
  167. dbif = sql_database_interface()
  168. dbif.connect()
  169. connect = True
  170. self.set_absolute_time(start_time, end_time, timezone)
  171. self.absolute_time.update_all(dbif)
  172. self.base.update(dbif)
  173. if connect == True:
  174. dbif.close()
  175. self.write_absolute_time_to_file()
  176. def write_absolute_time_to_file(self):
  177. """Start the grass timestamp module to set the time in the file system"""
  178. start_time, end_time, unit = self.get_absolute_time()
  179. start = datetime_to_grass_datetime_string(start_time)
  180. if end_time:
  181. end = datetime_to_grass_datetime_string(end_time)
  182. start += " / %s"%(end)
  183. core.run_command(self.get_timestamp_module_name(), map=self.get_map_id(), date=start)
  184. def set_relative_time(self, start_time, end_time, unit):
  185. """Set the relative time interval
  186. @param start_time: A double value
  187. @param end_time: A double value
  188. @param unit: The unit of the relative time. Supported units: years, months, days, hours, minutes, seconds
  189. Return True for success and False otherwise
  190. """
  191. if not self.check_relative_time_unit(unit):
  192. if self.get_layer():
  193. 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))
  194. else:
  195. core.error(_("Unsupported relative time unit type for %s map <%s>: %s") % (self.get_type(), self.get_id(), unit))
  196. return False
  197. if start_time != None and end_time != None:
  198. if int(start_time) > int(end_time):
  199. if self.get_layer():
  200. 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()))
  201. else:
  202. core.error(_("End time must be greater than start time for %s map <%s>") % (self.get_type(), self.get_id()))
  203. return False
  204. else:
  205. # Do not create an interval in case start and end time are equal
  206. if start_time == end_time:
  207. end_time = None
  208. self.base.set_ttype("relative")
  209. self.relative_time.set_unit(unit)
  210. self.relative_time.set_start_time(int(start_time))
  211. if end_time != None:
  212. self.relative_time.set_end_time(int(end_time))
  213. else:
  214. self.relative_time.set_end_time(None)
  215. return True
  216. def update_relative_time(self, start_time, end_time, unit, dbif = None):
  217. """Update the relative time interval
  218. This method should always be used to set the absolute time. Do not use insert() or update()
  219. to the the time. This update functions assures that the *.timestamp commands are invoked.
  220. @param start_time: A double value
  221. @param end_time: A double value
  222. @param dbif: The database interface to be used
  223. """
  224. connect = False
  225. if dbif == None:
  226. dbif = sql_database_interface()
  227. dbif.connect()
  228. connect = True
  229. if self.set_relative_time(start_time, end_time, unit):
  230. self.relative_time.update_all(dbif)
  231. self.base.update(dbif)
  232. dbif.connection.commit()
  233. if connect == True:
  234. dbif.close()
  235. self.write_relative_time_to_file()
  236. def write_relative_time_to_file(self):
  237. """Start the grass timestamp module to set the time in the file system"""
  238. start_time, end_time, unit = self.get_relative_time()
  239. start = "%i %s"%(int(start_time), unit)
  240. if end_time != None:
  241. end = "%i %s"%(int(end_time), unit)
  242. start += " / %s"%(end)
  243. core.run_command(self.get_timestamp_module_name(), map=self.get_map_id(), date=start)
  244. def set_spatial_extent(self, north, south, east, west, top=0, bottom=0):
  245. """Set the spatial extent of the map
  246. @param north: The northern edge
  247. @param south: The southern edge
  248. @param east: The eastern edge
  249. @param west: The western edge
  250. @param top: The top edge
  251. @param bottom: The bottom edge
  252. """
  253. self.spatial_extent.set_spatial_extent(north, south, east, west, top, bottom)
  254. def check_valid_time(self):
  255. """Check for correct valid time"""
  256. if self.is_time_absolute():
  257. start, end, tz = self.get_absolute_time()
  258. else:
  259. start, end, unit = self.get_relative_time()
  260. if start != None:
  261. if end != None:
  262. if start >= end:
  263. if self.get_layer():
  264. 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()))
  265. else:
  266. core.error(_("Map <%s> has incorrect time interval, start time is greater than end time") % (self.get_map_id()))
  267. return False
  268. else:
  269. core.error(_("Map <%s> has incorrect start time") % (self.get_map_id()))
  270. return False
  271. return True
  272. def delete(self, dbif=None):
  273. """Delete a map entry from database if it exists
  274. Remove dependent entries:
  275. * Remove the map entry in each space time dataset in which this map is registered
  276. * Remove the space time dataset register table
  277. @param dbif: The database interface to be used
  278. """
  279. connect = False
  280. if dbif == None:
  281. dbif = sql_database_interface()
  282. dbif.connect()
  283. connect = True
  284. if self.is_in_db(dbif):
  285. # SELECT all needed informations from the database
  286. self.select(dbif)
  287. # First we unregister from all dependent space time datasets
  288. self.unregister(dbif)
  289. # Remove the strds register table
  290. if self.get_stds_register():
  291. sql = "DROP TABLE " + self.get_stds_register()
  292. #print sql
  293. try:
  294. dbif.cursor.execute(sql)
  295. except:
  296. core.error(_("Unable to remove space time dataset register table <%s>") % (self.get_stds_register()))
  297. core.verbose(_("Delete %s dataset <%s> from temporal database") % (self.get_type(), self.get_id()))
  298. # Delete yourself from the database, trigger functions will take care of dependencies
  299. self.base.delete(dbif)
  300. # Remove the timestamp from the file system
  301. if self.get_type() == "vect":
  302. if self.get_layer():
  303. core.run_command(self.get_timestamp_module_name(), map=self.get_map_id(), layer=self.get_layer(), date="none")
  304. else:
  305. core.run_command(self.get_timestamp_module_name(), map=self.get_map_id(), date="none")
  306. else:
  307. core.run_command(self.get_timestamp_module_name(), map=self.get_map_id(), date="none")
  308. self.reset(None)
  309. dbif.connection.commit()
  310. if connect == True:
  311. dbif.close()
  312. def unregister(self, dbif=None, update=True):
  313. """ Remove the map entry in each space time dataset in which this map is registered
  314. @param dbif: The database interface to be used
  315. @param update: Call for each unregister statement the update from registered maps
  316. of the space time dataset. This can slow down the un-registration process significantly.
  317. """
  318. if self.get_layer():
  319. core.verbose(_("Unregister %s map <%s> with layer %s from space time datasets") % \
  320. (self.get_type(), self.get_map_id(), self.get_layer()))
  321. else:
  322. core.verbose(_("Unregister %s map <%s> from space time datasets") % (self.get_type(), self.get_map_id()))
  323. connect = False
  324. if dbif == None:
  325. dbif = sql_database_interface()
  326. dbif.connect()
  327. connect = True
  328. # Get all datasets in which this map is registered
  329. rows = self.get_registered_datasets(dbif)
  330. # For each stds in which the map is registered
  331. if rows:
  332. count = 0
  333. num_sps = len(rows)
  334. for row in rows:
  335. core.percent(count, num_sps, 1)
  336. count += 1
  337. # Create a space time dataset object to remove the map
  338. # from its register
  339. stds = self.get_new_stds_instance(row["id"])
  340. stds.select(dbif)
  341. stds.unregister_map(self, dbif)
  342. # Take care to update the space time dataset after
  343. # the map has been unregistered
  344. if update == True:
  345. stds.update_from_registered_maps(dbif)
  346. core.percent(1, 1, 1)
  347. dbif.connection.commit()
  348. if connect == True:
  349. dbif.close()
  350. def get_registered_datasets(self, dbif=None):
  351. """Return all space time dataset ids in which this map is registered as
  352. dictionary like rows with column "id" or None if this map is not registered in any
  353. space time dataset.
  354. @param dbif: The database interface to be used
  355. """
  356. connect = False
  357. if dbif == None:
  358. dbif = sql_database_interface()
  359. dbif.connect()
  360. connect = True
  361. rows = None
  362. try:
  363. if self.get_stds_register() != None:
  364. # Select all stds tables in which this map is registered
  365. sql = "SELECT id FROM " + self.get_stds_register()
  366. dbif.cursor.execute(sql)
  367. rows = dbif.cursor.fetchall()
  368. except:
  369. core.error(_("Unable to select space time dataset register table <%s>") % (self.get_stds_register()))
  370. if connect == True:
  371. dbif.close()
  372. return rows