abstract_map_dataset.py 24 KB

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