abstract_map_dataset.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  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. def update_absolute_time(self, start_time, end_time=None,
  280. timezone=None, dbif=None):
  281. """!Update the absolute time
  282. This functions assures that the timetsamp is written to the
  283. grass file system based database
  284. @param start_time: a datetime object specifying the start time of the map
  285. @param end_time: a datetime object specifying the end time of the map
  286. @param timezone: Thee timezone of the map
  287. """
  288. dbif, connected = init_dbif(dbif)
  289. self.set_absolute_time(start_time, end_time, timezone)
  290. self.absolute_time.update_all(dbif)
  291. self.base.update(dbif)
  292. if connected:
  293. dbif.close()
  294. self.write_timestamp_to_grass()
  295. def set_relative_time(self, start_time, end_time, unit):
  296. """!Set the relative time interval
  297. @param start_time: A double value
  298. @param end_time: A double value
  299. @param unit: The unit of the relative time. Supported units:
  300. years, months, days, hours, minutes, seconds
  301. Return True for success and False otherwise
  302. """
  303. if not self.check_relative_time_unit(unit):
  304. if self.get_layer() is not None:
  305. core.error(_("Unsupported relative time unit type for %s map "
  306. "<%s> with layer %s: %s") % (self.get_type(),
  307. self.get_id(),
  308. self.get_layer(),
  309. unit))
  310. else:
  311. core.error(_("Unsupported relative time unit type for %s map "
  312. "<%s>: %s") % (self.get_type(), self.get_id(),
  313. unit))
  314. return False
  315. if start_time is not None and end_time is not None:
  316. if int(start_time) > int(end_time):
  317. if self.get_layer() is not None:
  318. core.error(_("End time must be greater than start time for"
  319. " %s map <%s> with layer %s") % \
  320. (self.get_type(), self.get_id(),
  321. self.get_layer()))
  322. else:
  323. core.error(_("End time must be greater than start time for"
  324. " %s map <%s>") % (self.get_type(),
  325. self.get_id()))
  326. return False
  327. else:
  328. # Do not create an interval in case start and end time are equal
  329. if start_time == end_time:
  330. end_time = None
  331. self.base.set_ttype("relative")
  332. self.relative_time.set_unit(unit)
  333. self.relative_time.set_start_time(int(start_time))
  334. if end_time is not None:
  335. self.relative_time.set_end_time(int(end_time))
  336. else:
  337. self.relative_time.set_end_time(None)
  338. return True
  339. def update_relative_time(self, start_time, end_time, unit, dbif=None):
  340. """!Update the relative time interval
  341. This functions assures that the timetsamp is written to the
  342. grass file system based database
  343. @param start_time: A double value
  344. @param end_time: A double value
  345. @param dbif: The database interface to be used
  346. """
  347. dbif, connected = init_dbif(dbif)
  348. if self.set_relative_time(start_time, end_time, unit):
  349. self.relative_time.update_all(dbif)
  350. self.base.update(dbif)
  351. if connected:
  352. dbif.close()
  353. self.write_timestamp_to_grass()
  354. def set_spatial_extent(self, north, south, east, west, top=0, bottom=0):
  355. """!Set the spatial extent of the map
  356. @param north: The northern edge
  357. @param south: The southern edge
  358. @param east: The eastern edge
  359. @param west: The western edge
  360. @param top: The top edge
  361. @param bottom: The bottom edge
  362. """
  363. self.spatial_extent.set_spatial_extent(
  364. north, south, east, west, top, bottom)
  365. def check_valid_time(self):
  366. """!Check for correct valid time"""
  367. if self.is_time_absolute():
  368. start, end, tz = self.get_absolute_time()
  369. else:
  370. start, end, unit = self.get_relative_time()
  371. if start is not None:
  372. if end is not None:
  373. if start >= end:
  374. if self.get_layer() is not None:
  375. core.error(_("Map <%s> with layer %s has incorrect "
  376. "time interval, start time is greater "
  377. "than end time") % (self.get_map_id(),
  378. self.get_layer()))
  379. else:
  380. core.error(_("Map <%s> has incorrect time interval, "
  381. "start time is greater than end time") % \
  382. (self.get_map_id()))
  383. return False
  384. else:
  385. core.error(_("Map <%s> has incorrect start time") %
  386. (self.get_map_id()))
  387. return False
  388. return True
  389. def delete(self, dbif=None, update=True, execute=True):
  390. """!Delete a map entry from database if it exists
  391. Remove dependent entries:
  392. * Remove the map entry in each space time dataset in which this map
  393. is registered
  394. * Remove the space time dataset register table
  395. @param dbif: The database interface to be used
  396. @param update: Call for each unregister statement the update from
  397. registered maps of the space time dataset.
  398. This can slow down the un-registration process significantly.
  399. @param execute: If True the SQL DELETE and DROP table statements will
  400. be executed.
  401. If False the prepared SQL statements are
  402. returned and must be executed by the caller.
  403. @return The SQL statements if execute == False, else an empty string,
  404. None in case of a failure
  405. """
  406. dbif, connected = init_dbif(dbif)
  407. statement = ""
  408. if self.is_in_db(dbif):
  409. # SELECT all needed information from the database
  410. self.metadata.select(dbif)
  411. # First we unregister from all dependent space time datasets
  412. statement += self.unregister(
  413. dbif=dbif, update=update, execute=False)
  414. # Remove the strds register table
  415. if self.get_stds_register() is not None:
  416. statement += "DROP TABLE " + self.get_stds_register() + ";\n"
  417. # Commented because of performance issue calling g.message thousend times
  418. #core.verbose(_("Delete %s dataset <%s> from temporal database")
  419. # % (self.get_type(), self.get_id()))
  420. # Delete yourself from the database, trigger functions will
  421. # take care of dependencies
  422. statement += self.base.get_delete_statement()
  423. if execute:
  424. dbif.execute_transaction(statement)
  425. # Remove the timestamp from the file system
  426. self.remove_timestamp_from_grass()
  427. self.reset(None)
  428. if connected:
  429. dbif.close()
  430. if execute:
  431. return ""
  432. return statement
  433. def unregister(self, dbif=None, update=True, execute=True):
  434. """! Remove the map entry in each space time dataset in which this map
  435. is registered
  436. @param dbif: The database interface to be used
  437. @param update: Call for each unregister statement the update from
  438. registered maps of the space time dataset. This can
  439. slow down the un-registration process significantly.
  440. @param execute: If True the SQL DELETE and DROP table statements
  441. will be executed.
  442. If False the prepared SQL statements are
  443. returned and must be executed by the caller.
  444. @return The SQL statements if execute == False, else an empty string
  445. """
  446. # Commented because of performance issue calling g.message thousend times
  447. #if self.get_layer() is not None:
  448. # core.verbose(_("Unregister %(type)s map <%(map)s> with "
  449. # "layer %(layer)s from space time datasets" % \
  450. # {'type':self.get_type(), 'map':self.get_map_id(),
  451. # 'layer':self.get_layer()}))
  452. #else:
  453. # core.verbose(_("Unregister %(type)s map <%(map)s> "
  454. # "from space time datasets"
  455. # % {'type':self.get_type(), 'map':self.get_map_id()}))
  456. statement = ""
  457. dbif, connected = init_dbif(dbif)
  458. # Get all datasets in which this map is registered
  459. rows = self.get_registered_datasets(dbif)
  460. # For each stds in which the map is registered
  461. if rows is not None:
  462. for row in rows:
  463. # Create a space time dataset object to remove the map
  464. # from its register
  465. stds = self.get_new_stds_instance(row["id"])
  466. stds.metadata.select(dbif)
  467. statement += stds.unregister_map(self, dbif, False)
  468. # Take care to update the space time dataset after
  469. # the map has been unregistered
  470. if update == True and execute == True:
  471. stds.update_from_registered_maps(dbif)
  472. if execute:
  473. dbif.execute_transaction(statement)
  474. if connected:
  475. dbif.close()
  476. if execute:
  477. return ""
  478. return statement
  479. def get_registered_datasets(self, dbif=None):
  480. """!Return all space time dataset ids in which this map is registered as
  481. dictionary like rows with column "id" or None if this map is not
  482. registered in any space time dataset.
  483. @param dbif: The database interface to be used
  484. """
  485. dbif, connected = init_dbif(dbif)
  486. rows = None
  487. try:
  488. if self.get_stds_register() is not None:
  489. # Select all stds tables in which this map is registered
  490. sql = "SELECT id FROM " + self.get_stds_register()
  491. dbif.cursor.execute(sql)
  492. rows = dbif.cursor.fetchall()
  493. except:
  494. core.error(_("Unable to select space time dataset register table "
  495. "<%s>") % (self.get_stds_register()))
  496. if connected:
  497. dbif.close()
  498. return rows
  499. ###############################################################################
  500. if __name__ == "__main__":
  501. import doctest
  502. doctest.testmod()