abstract_datasets.py 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123
  1. """!@package grass.temporal
  2. @brief GRASS Python scripting module (temporal GIS functions)
  3. Temporal GIS related functions to be used in temporal GIS Python library package.
  4. Usage:
  5. @code
  6. import grass.temporal as tgis
  7. ...
  8. @endcode
  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. import uuid
  16. import copy
  17. from temporal_extent import *
  18. from spatial_extent import *
  19. from metadata import *
  20. class abstract_dataset(object):
  21. """This is the base class for all datasets (raster, vector, raster3d, strds, stvds, str3ds)"""
  22. def reset(self, ident):
  23. """Reset the internal structure and set the identifier
  24. @param ident: The identifier of the dataset
  25. """
  26. raise IOError("This method must be implemented in the subclasses")
  27. def get_type(self):
  28. """Return the type of this class"""
  29. raise IOError("This method must be implemented in the subclasses")
  30. def get_new_instance(self, ident):
  31. """Return a new instance with the type of this class
  32. @param ident: The identifier of the dataset
  33. """
  34. raise IOError("This method must be implemented in the subclasses")
  35. def get_id(self):
  36. return self.base.get_id()
  37. def get_absolute_time(self):
  38. """Returns a tuple of the start, the end valid time and the timezone of the map
  39. @return A tuple of (start_time, end_time, timezone)
  40. """
  41. start = self.absolute_time.get_start_time()
  42. end = self.absolute_time.get_end_time()
  43. tz = self.absolute_time.get_timezone()
  44. return (start, end, tz)
  45. def get_relative_time(self):
  46. """Returns the relative time interval (start_time, end_time) or None if not present"""
  47. start = self.relative_time.get_start_time()
  48. end = self.relative_time.get_end_time()
  49. return (start, end)
  50. def get_temporal_type(self):
  51. """Return the temporal type of this dataset"""
  52. return self.base.get_ttype()
  53. def get_spatial_extent(self):
  54. """Return a tuple of spatial extent (north, south, east, west, top, bottom) """
  55. north = self.spatial_extent.get_north()
  56. south = self.spatial_extent.get_south()
  57. east = self.spatial_extent.get_east()
  58. west = self.spatial_extent.get_west()
  59. top = self.spatial_extent.get_top()
  60. bottom = self.spatial_extent.get_bottom()
  61. return (north, south, east, west, top, bottom)
  62. def select(self, dbif=None):
  63. """Select temporal dataset entry from database and fill up the internal structure"""
  64. self.base.select(dbif)
  65. if self.is_time_absolute():
  66. self.absolute_time.select(dbif)
  67. if self.is_time_relative():
  68. self.relative_time.select(dbif)
  69. self.spatial_extent.select(dbif)
  70. self.metadata.select(dbif)
  71. def is_in_db(self, dbif=None):
  72. """Check if the temporal dataset entry is in the database"""
  73. return self.base.is_in_db(dbif)
  74. def delete(self):
  75. """Delete temporal dataset entry from database if it exists"""
  76. raise IOError("This method must be implemented in the subclasses")
  77. def insert(self, dbif=None):
  78. """Insert temporal dataset entry into database from the internal structure"""
  79. self.base.insert(dbif)
  80. if self.is_time_absolute():
  81. self.absolute_time.insert(dbif)
  82. if self.is_time_relative():
  83. self.relative_time.insert(dbif)
  84. self.spatial_extent.insert(dbif)
  85. self.metadata.insert(dbif)
  86. def update(self, dbif=None):
  87. """Update temporal dataset entry of database from the internal structure
  88. excluding None variables
  89. """
  90. self.base.update(dbif)
  91. if self.is_time_absolute():
  92. self.absolute_time.update(dbif)
  93. if self.is_time_relative():
  94. self.relative_time.update(dbif)
  95. self.spatial_extent.update(dbif)
  96. self.metadata.update(dbif)
  97. def update_all(self, dbif=None):
  98. """Update temporal dataset entry of database from the internal structure
  99. and include None varuables.
  100. @param dbif: The database interface to be used
  101. """
  102. self.base.update_all(dbif)
  103. if self.is_time_absolute():
  104. self.absolute_time.update_all(dbif)
  105. if self.is_time_relative():
  106. self.relative_time.update_all(dbif)
  107. self.spatial_extent.update_all(dbif)
  108. self.metadata.update_all(dbif)
  109. def print_self(self):
  110. """Print the content of the internal structure to stdout"""
  111. self.base.print_self()
  112. if self.is_time_absolute():
  113. self.absolute_time.print_self()
  114. if self.is_time_relative():
  115. self.relative_time.print_self()
  116. self.spatial_extent.print_self()
  117. self.metadata.print_self()
  118. def print_info(self):
  119. """Print information about this class in human readable style"""
  120. if self.get_type() == "raster":
  121. # 1 2 3 4 5 6 7
  122. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  123. print ""
  124. print " +-------------------- Raster Dataset ----------------------------------------+"
  125. if self.get_type() == "raster3d":
  126. # 1 2 3 4 5 6 7
  127. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  128. print ""
  129. print " +-------------------- Raster3d Dataset --------------------------------------+"
  130. if self.get_type() == "vector":
  131. # 1 2 3 4 5 6 7
  132. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  133. print ""
  134. print " +-------------------- Vector Dataset ----------------------------------------+"
  135. if self.get_type() == "strds":
  136. # 1 2 3 4 5 6 7
  137. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  138. print ""
  139. print " +-------------------- Space Time Raster Dataset -----------------------------+"
  140. if self.get_type() == "str3ds":
  141. # 1 2 3 4 5 6 7
  142. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  143. print ""
  144. print " +-------------------- Space Time Raster3d Dataset ---------------------------+"
  145. if self.get_type() == "stvds":
  146. # 1 2 3 4 5 6 7
  147. # 0123456789012345678901234567890123456789012345678901234567890123456789012345678
  148. print ""
  149. print " +-------------------- Space Time Vector Dataset -----------------------------+"
  150. print " | |"
  151. self.base.print_info()
  152. if self.is_time_absolute():
  153. self.absolute_time.print_info()
  154. if self.is_time_relative():
  155. self.relative_time.print_info()
  156. self.spatial_extent.print_info()
  157. self.metadata.print_info()
  158. print " +----------------------------------------------------------------------------+"
  159. def print_shell_info(self):
  160. """Print information about this class in shell style"""
  161. self.base.print_shell_info()
  162. if self.is_time_absolute():
  163. self.absolute_time.print_shell_info()
  164. if self.is_time_relative():
  165. self.relative_time.print_shell_info()
  166. self.spatial_extent.print_shell_info()
  167. self.metadata.print_shell_info()
  168. def set_time_to_absolute(self):
  169. self.base.set_ttype("absolute")
  170. def set_time_to_relative(self):
  171. self.base.set_ttype("relative")
  172. def is_time_absolute(self):
  173. if self.base.D.has_key("temporal_type"):
  174. return self.base.get_ttype() == "absolute"
  175. else:
  176. return None
  177. def is_time_relative(self):
  178. if self.base.D.has_key("temporal_type"):
  179. return self.base.get_ttype() == "relative"
  180. else:
  181. return None
  182. def temporal_relation(self, map):
  183. """Return the temporal relation of this and the provided temporal map"""
  184. if self.is_time_absolute() and map.is_time_absolute():
  185. return self.absolute_time.temporal_relation(map.absolute_time)
  186. if self.is_time_relative() and map.is_time_relative():
  187. return self.relative_time.temporal_relation(map.relative_time)
  188. return None
  189. ###############################################################################
  190. class abstract_map_dataset(abstract_dataset):
  191. """This is the base class for all maps (raster, vector, raster3d)
  192. providing additional function to set the valid time and the spatial extent.
  193. """
  194. def get_new_stds_instance(self, ident):
  195. """Return a new space time dataset instance in which maps are stored with the type of this class
  196. @param ident: The identifier of the dataset
  197. """
  198. raise IOError("This method must be implemented in the subclasses")
  199. def get_stds_register(self):
  200. """Return the space time dataset register table name in which stds are listed in which this map is registered"""
  201. raise IOError("This method must be implemented in the subclasses")
  202. def set_stds_register(self, name):
  203. """Set the space time dataset register table name.
  204. This table stores all space time datasets in which this map is registered.
  205. @param ident: The name of the register table
  206. """
  207. raise IOError("This method must be implemented in the subclasses")
  208. def set_absolute_time(self, start_time, end_time=None, timezone=None):
  209. """Set the absolute time interval with start time and end time
  210. @param start_time: a datetime object specifying the start time of the map
  211. @param end_time: a datetime object specifying the end time of the map
  212. @param timezone: Thee timezone of the map
  213. """
  214. if start_time != None and not isinstance(start_time, datetime) :
  215. core.fatal(_("Start time must be of type datetime"))
  216. if end_time != None and not isinstance(end_time, datetime) :
  217. core.fatal(_("End time must be of type datetime"))
  218. if start_time != None and end_time != None:
  219. if start_time >= end_time:
  220. core.error(_("End time must be later than start time"))
  221. return False
  222. self.base.set_ttype("absolute")
  223. self.absolute_time.set_start_time(start_time)
  224. self.absolute_time.set_end_time(end_time)
  225. self.absolute_time.set_timezone(timezone)
  226. return True
  227. def update_absolute_time(self, start_time, end_time=None, timezone=None, dbif = None):
  228. """Update the absolute time
  229. @param start_time: a datetime object specifying the start time of the map
  230. @param end_time: a datetime object specifying the end time of the map
  231. @param timezone: Thee timezone of the map
  232. """
  233. connect = False
  234. if dbif == None:
  235. dbif = sql_database_interface()
  236. dbif.connect()
  237. connect = True
  238. self.set_absolute_time(start_time, end_time, timezone)
  239. self.absolute_time.update_all(dbif)
  240. self.base.update(dbif)
  241. if connect == True:
  242. dbif.close()
  243. def set_relative_time(self, start_time, end_time=None):
  244. """Set the relative time interval
  245. @param start_time: A double value in days
  246. @param end_time: A double value in days
  247. """
  248. if start_time != None and end_time != None:
  249. if abs(float(start_time)) >= abs(float(end_time)):
  250. core.error(_("End time must be greater than start time"))
  251. return False
  252. self.base.set_ttype("relative")
  253. self.relative_time.set_start_time(float(start_time))
  254. if end_time != None:
  255. self.relative_time.set_end_time(float(end_time))
  256. else:
  257. self.relative_time.set_end_time(None)
  258. return True
  259. def update_relative_time(self, start_time, end_time=None, dbif = None):
  260. """Update the relative time interval
  261. @param start_time: A double value in days
  262. @param end_time: A double value in days
  263. @param dbif: The database interface to be used
  264. """
  265. connect = False
  266. if dbif == None:
  267. dbif = sql_database_interface()
  268. dbif.connect()
  269. connect = True
  270. self.set_relative_time(start_time, end_time)
  271. self.relative_time.update_all(dbif)
  272. self.base.update(dbif)
  273. dbif.connection.commit()
  274. if connect == True:
  275. dbif.close()
  276. def set_spatial_extent(self, north, south, east, west, top=0, bottom=0):
  277. """Set the spatial extent of the map
  278. @param north: The northern edge
  279. @param south: The southern edge
  280. @param east: The eastern edge
  281. @param west: The western edge
  282. @param top: The top edge
  283. @param bottom: The bottom ege
  284. """
  285. self.spatial_extent.set_spatial_extent(north, south, east, west, top, bottom)
  286. def delete(self, dbif=None):
  287. """Delete a map entry from database if it exists
  288. Remove dependent entries:
  289. * Remove the map entry in each space time dataset in which this map is registered
  290. * Remove the space time dataset register table
  291. @param dbif: The database interface to be used
  292. """
  293. connect = False
  294. if dbif == None:
  295. dbif = sql_database_interface()
  296. dbif.connect()
  297. connect = True
  298. if self.is_in_db(dbif):
  299. # SELECT all needed informations from the database
  300. self.select(dbif)
  301. # First we unregister from all dependent space time datasets
  302. self.unregister(dbif)
  303. # Remove the strds register table
  304. if self.get_stds_register():
  305. sql = "DROP TABLE " + self.get_stds_register()
  306. #print sql
  307. try:
  308. dbif.cursor.execute(sql)
  309. except:
  310. core.error(_("Unable to remove space time dataset register table <%s>") % (self.get_stds_register()))
  311. core.verbose(_("Delete %s dataset <%s> from temporal database") % (self.get_type(), self.get_id()))
  312. # Delete yourself from the database, trigger functions will take care of dependencies
  313. self.base.delete(dbif)
  314. self.reset(None)
  315. dbif.connection.commit()
  316. if connect == True:
  317. dbif.close()
  318. def unregister(self, dbif=None):
  319. """ Remove the map entry in each space time dataset in which this map is registered
  320. @param dbif: The database interface to be used
  321. """
  322. core.verbose(_("Unregister %s dataset <%s> from space time datasets") % (self.get_type(), self.get_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. for row in rows:
  333. # Create a space time dataset object to remove the map
  334. # from its register
  335. stds = self.get_new_stds_instance(row["id"])
  336. stds.select(dbif)
  337. stds.unregister_map(self, dbif)
  338. # Take care to update the space time dataset after
  339. # the map has been unregistred
  340. stds.update_from_registered_maps(dbif)
  341. dbif.connection.commit()
  342. if connect == True:
  343. dbif.close()
  344. def get_registered_datasets(self, dbif=None):
  345. """Return all space time dataset ids in which this map is registered as
  346. dictionary like rows with column "id" or None if this map is not registered in any
  347. space time dataset.
  348. @param dbif: The database interface to be used
  349. """
  350. connect = False
  351. if dbif == None:
  352. dbif = sql_database_interface()
  353. dbif.connect()
  354. connect = True
  355. rows = None
  356. try:
  357. if self.get_stds_register() != None:
  358. # Select all stds tables in which this map is registered
  359. sql = "SELECT id FROM " + self.get_stds_register()
  360. dbif.cursor.execute(sql)
  361. rows = dbif.cursor.fetchall()
  362. except:
  363. core.error(_("Unable to select space time dataset register table <%s>") % (self.get_stds_register()))
  364. if connect == True:
  365. dbif.close()
  366. return rows
  367. ###############################################################################
  368. class abstract_space_time_dataset(abstract_dataset):
  369. """Abstract space time dataset class
  370. This class represents a space time dataset. Convenient functions
  371. to select, update, insert or delete objects of this type in the SQL
  372. temporal database exists as well as functions to register or unregister
  373. raster maps.
  374. Parts of the temporal logic are implemented in the SQL temporal database,
  375. like the computation of the temporal and spatial extent as well as the
  376. collecting of metadata.
  377. """
  378. def __init__(self, ident):
  379. self.reset(ident)
  380. def get_new_instance(self, ident=None):
  381. """Return a new instance with the type of this class
  382. @param ident: The unique identifier of the new object
  383. """
  384. raise IOError("This method must be implemented in the subclasses")
  385. def get_new_map_instance(self, ident=None):
  386. """Return a new instance of a map dataset which is associated with the type of this class
  387. @param ident: The unique identifier of the new object
  388. """
  389. raise IOError("This method must be implemented in the subclasses")
  390. def get_map_register(self):
  391. """Return the name of the map register table"""
  392. raise IOError("This method must be implemented in the subclasses")
  393. def set_map_register(self, name):
  394. """Set the name of the map register table
  395. This table stores all map names which are registered in this space time dataset.
  396. @param name: The name of the register table
  397. """
  398. raise IOError("This method must be implemented in the subclasses")
  399. def set_initial_values(self, granularity, temporal_type, semantic_type, \
  400. title=None, description=None):
  401. """Set the initial values of the space time dataset
  402. @param granularity: The temporal granularity of this dataset. This value
  403. should be computed by the space time dataset itself,
  404. based on the granularity of the registered maps
  405. @param temporal_type: The temporal type of this space time dataset (absolute or relative)
  406. @param semantic_type: The semantic type of this dataset
  407. @param title: The title
  408. @param description: The description of this dataset
  409. """
  410. if temporal_type == "absolute":
  411. self.set_time_to_absolute()
  412. self.absolute_time.set_granularity(granularity)
  413. elif temporal_type == "relative":
  414. self.set_time_to_relative()
  415. self.relative_time.set_granularity(granularity)
  416. else:
  417. core.fatal(_("Unknown temporal type \"%s\"") % (temporal_type))
  418. self.base.set_semantic_type(semantic_type)
  419. self.metadata.set_title(title)
  420. self.metadata.set_description(description)
  421. def get_initial_values(self):
  422. """Return the initial values: granularity, temporal_type, semantic_type, title, description"""
  423. temporal_type = self.get_temporal_type()
  424. if temporal_type == "absolute":
  425. granularity = self.absolute_time.get_granularity()
  426. elif temporal_type == "relative":
  427. granularity = self.relative_time.get_granularity()
  428. semantic_type = self.base.get_semantic_type()
  429. title = self.metadata.get_title()
  430. description = self.metadata.get_description()
  431. return granularity, temporal_type, semantic_type, title, description
  432. def get_temporal_relation_matrix(self, dbif=None):
  433. """Return the temporal relation matrix of all registered maps as listof lists
  434. The temproal relation matrix includes the temporal relations between
  435. all registered maps. The relations are strings stored in a list of lists.
  436. @param dbif: The database interface to be used
  437. """
  438. connect = False
  439. if dbif == None:
  440. dbif = sql_database_interface()
  441. dbif.connect()
  442. connect = True
  443. matrix = []
  444. maps = self.get_registered_maps_as_objects(where=None, order="start_time", dbif=dbif)
  445. # Create the temporal relation matrix
  446. # Add the map names first
  447. row = []
  448. for map in maps:
  449. row.append(map.get_id())
  450. matrix.append(row)
  451. for mapA in maps:
  452. row = []
  453. for mapB in maps:
  454. row.append(mapA.temporal_relation(mapB))
  455. matrix.append(row)
  456. if connect == True:
  457. dbif.close()
  458. return matrix
  459. def get_registered_maps_as_objects(self, where = None, order = None, dbif=None):
  460. """Return all registered maps as ordered object list
  461. @param where: The SQL where statement to select a subset of the registered maps without "WHERE"
  462. @param order: The SQL order statement to be used to order the objects in the list without "ORDER BY"
  463. @param dbif: The database interface to be used
  464. In case nothing found None is returned
  465. """
  466. connect = False
  467. if dbif == None:
  468. dbif = sql_database_interface()
  469. dbif.connect()
  470. connect = True
  471. obj_list = []
  472. rows = self.get_registered_maps("id", where, order, dbif)
  473. if rows:
  474. for row in rows:
  475. map = self.get_new_map_instance(row["id"])
  476. map.select(dbif)
  477. obj_list.append(copy.copy(map))
  478. if connect == True:
  479. dbif.close()
  480. return obj_list
  481. def get_registered_maps(self, columns=None, where = None, order = None, dbif=None):
  482. """Return sqlite rows of all registered maps.
  483. Each row includes all columns specified in the datatype specific view
  484. @param columns: Columns to be selected as SQL compliant string
  485. @param where: The SQL where statement to select a subset of the registered maps without "WHERE"
  486. @param order: The SQL order statement to be used to order the objects in the list without "ORDER BY"
  487. @param dbif: The database interface to be used
  488. In case nothing found None is returned
  489. """
  490. connect = False
  491. if dbif == None:
  492. dbif = sql_database_interface()
  493. dbif.connect()
  494. connect = True
  495. rows = None
  496. if self.get_map_register():
  497. # Use the correct temporal table
  498. if self.get_temporal_type() == "absolute":
  499. map_view = self.get_new_map_instance(None).get_type() + "_view_abs_time"
  500. else:
  501. map_view = self.get_new_map_instance(None).get_type() + "_view_rel_time"
  502. if columns:
  503. sql = "SELECT %s FROM %s WHERE %s.id IN (SELECT id FROM %s)" % (columns, map_view, map_view, self.get_map_register())
  504. else:
  505. sql = "SELECT * FROM %s WHERE %s.id IN (SELECT id FROM %s)" % (map_view, map_view, self.get_map_register())
  506. if where:
  507. sql += " AND %s" % (where)
  508. if order:
  509. sql += " ORDER BY %s" % (order)
  510. try:
  511. dbif.cursor.execute(sql)
  512. rows = dbif.cursor.fetchall()
  513. except:
  514. if connect == True:
  515. dbif.close()
  516. core.error(_("Unable to get map ids from register table <%s>") % (self.get_map_register()))
  517. raise
  518. if connect == True:
  519. dbif.close()
  520. return rows
  521. def delete(self, dbif=None):
  522. """Delete a space time dataset from the temporal database
  523. This method removes the space time dataset from the temporal database and drops its map register table
  524. @param dbif: The database interface to be used
  525. """
  526. # First we need to check if maps are registered in this dataset and
  527. # unregister them
  528. core.verbose(_("Delete space time %s dataset <%s> from temporal database") % (self.get_new_map_instance(ident=None).get_type(), self.get_id()))
  529. connect = False
  530. if dbif == None:
  531. dbif = sql_database_interface()
  532. dbif.connect()
  533. connect = True
  534. # SELECT all needed informations from the database
  535. self.select(dbif)
  536. core.verbose(_("Drop map register table: %s") % (self.get_map_register()))
  537. if self.get_map_register():
  538. rows = self.get_registered_maps("id", None, None, dbif)
  539. # Unregister each registered map in the table
  540. if rows:
  541. for row in rows:
  542. # Unregister map
  543. map = self.get_new_map_instance(row["id"])
  544. self.unregister_map(map, dbif)
  545. try:
  546. # Drop the map register table
  547. sql = "DROP TABLE " + self.get_map_register()
  548. dbif.cursor.execute(sql)
  549. dbif.connection.commit()
  550. except:
  551. if connect == True:
  552. dbif.close()
  553. core.error(_("Unable to drop table <%s>") % (self.get_map_register()))
  554. raise
  555. # Remove the primary key, the foreign keys will be removed by trigger
  556. self.base.delete(dbif)
  557. self.reset(None)
  558. if connect == True:
  559. dbif.close()
  560. def register_map(self, map, dbif=None):
  561. """ Register a map in the space time dataset.
  562. This method takes care of the registration of a map
  563. in a space time dataset.
  564. In case the map is already registered this function will break with a warning
  565. and return False
  566. @param dbif: The database interface to be used
  567. """
  568. connect = False
  569. if dbif == None:
  570. dbif = sql_database_interface()
  571. dbif.connect()
  572. connect = True
  573. if map.is_in_db(dbif) == False:
  574. dbif.close()
  575. core.fatal(_("Only maps with absolute or relative valid time can be registered"))
  576. core.verbose(_("Register %s map <%s> in space time %s dataset <%s>") % (map.get_type(), map.get_id(), map.get_type(), self.get_id()))
  577. # First select all data from the database
  578. map.select(dbif)
  579. map_id = map.base.get_id()
  580. map_name = map.base.get_name()
  581. map_mapset = map.base.get_mapset()
  582. map_register_table = map.get_stds_register()
  583. #print "Map register table", map_register_table
  584. # Get basic info
  585. stds_name = self.base.get_name()
  586. stds_mapset = self.base.get_mapset()
  587. stds_register_table = self.get_map_register()
  588. #print "STDS register table", stds_register_table
  589. if stds_mapset != map_mapset:
  590. dbif.close()
  591. core.fatal(_("Only maps from the same mapset can be registered"))
  592. # Check if map is already registred
  593. if stds_register_table:
  594. if dbmi.paramstyle == "qmark":
  595. sql = "SELECT id FROM " + stds_register_table + " WHERE id = (?)"
  596. else:
  597. sql = "SELECT id FROM " + stds_register_table + " WHERE id = (%s)"
  598. dbif.cursor.execute(sql, (map_id,))
  599. row = dbif.cursor.fetchone()
  600. # In case of no entry make a new one
  601. if row and row[0] == map_id:
  602. if connect == True:
  603. dbif.close()
  604. core.warning(_("Map <%s> is already registered.") % (map_id))
  605. return False
  606. # Create tables
  607. sql_path = get_sql_template_path()
  608. # We need to create the stmap raster register table bevor we can register the map
  609. if map_register_table == None:
  610. # Create a unique id
  611. uuid_rand = "map_" + str(uuid.uuid4()).replace("-", "")
  612. map_register_table = uuid_rand + "_" + self.get_type() + "_register"
  613. # Read the SQL template
  614. sql = open(os.path.join(sql_path, "map_stds_register_table_template.sql"), 'r').read()
  615. # Create the raster, raster3d and vector tables
  616. sql = sql.replace("GRASS_MAP", map.get_type())
  617. sql = sql.replace("MAP_NAME", map_name + "_" + map_mapset )
  618. sql = sql.replace("TABLE_NAME", uuid_rand )
  619. sql = sql.replace("MAP_ID", map_id)
  620. sql = sql.replace("STDS", self.get_type())
  621. try:
  622. if dbmi.__name__ == "sqlite3":
  623. dbif.cursor.executescript(sql)
  624. else:
  625. dbif.cursor.execute(sql)
  626. except:
  627. if connect == True:
  628. dbif.close()
  629. core.error(_("Unable to create the space time %s dataset register table for <%s>") % \
  630. (map.get_type(), map.get_id()))
  631. raise
  632. # Set the stds register table name and put it into the DB
  633. map.set_stds_register(map_register_table)
  634. map.metadata.update(dbif)
  635. core.verbose(_("Created register table <%s> for %s map <%s>") % \
  636. (map_register_table, map.get_type(), map.get_id()))
  637. # We need to create the table and register it
  638. if stds_register_table == None:
  639. # Create table name
  640. stds_register_table = stds_name + "_" + stds_mapset + "_" + map.get_type() + "_register"
  641. # Read the SQL template
  642. sql = open(os.path.join(sql_path, "stds_map_register_table_template.sql"), 'r').read()
  643. # Create the raster, raster3d and vector tables
  644. sql = sql.replace("GRASS_MAP", map.get_type())
  645. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  646. sql = sql.replace("SPACETIME_ID", self.base.get_id())
  647. sql = sql.replace("STDS", self.get_type())
  648. sql_script = ""
  649. sql_script += "BEGIN TRANSACTION;\n"
  650. sql_script += sql
  651. sql_script += "\n"
  652. sql_script += "END TRANSACTION;"
  653. try:
  654. if dbmi.__name__ == "sqlite3":
  655. dbif.cursor.executescript(sql_script)
  656. else:
  657. dbif.cursor.execute(sql_script)
  658. dbif.connection.commit()
  659. except:
  660. if connect == True:
  661. dbif.close()
  662. core.error(_("Unable to create the space time %s dataset register table for <%s>") % \
  663. (map.get_type(), map.get_id()))
  664. raise
  665. # Set the map register table name and put it into the DB
  666. self.set_map_register(stds_register_table)
  667. self.metadata.update(dbif)
  668. core.verbose(_("Created register table <%s> for space time %s dataset <%s>") % \
  669. (stds_register_table, map.get_type(), self.get_id()))
  670. # Register the stds in the map stds register table
  671. # Check if the entry is already there
  672. if dbmi.paramstyle == "qmark":
  673. sql = "SELECT id FROM " + map_register_table + " WHERE id = ?"
  674. else:
  675. sql = "SELECT id FROM " + map_register_table + " WHERE id = %s"
  676. dbif.cursor.execute(sql, (self.base.get_id(),))
  677. row = dbif.cursor.fetchone()
  678. # In case of no entry make a new one
  679. if row == None:
  680. if dbmi.paramstyle == "qmark":
  681. sql = "INSERT INTO " + map_register_table + " (id) " + "VALUES (?)"
  682. else:
  683. sql = "INSERT INTO " + map_register_table + " (id) " + "VALUES (%s)"
  684. #print sql
  685. dbif.cursor.execute(sql, (self.base.get_id(),))
  686. # Now put the raster name in the stds map register table
  687. if dbmi.paramstyle == "qmark":
  688. sql = "INSERT INTO " + stds_register_table + " (id) " + "VALUES (?)"
  689. else:
  690. sql = "INSERT INTO " + stds_register_table + " (id) " + "VALUES (%s)"
  691. #print sql
  692. dbif.cursor.execute(sql, (map_id,))
  693. if connect == True:
  694. dbif.close()
  695. return True
  696. def unregister_map(self, map, dbif = None):
  697. """Unregister a map from the space time dataset.
  698. This method takes care of the unregistration of a map
  699. from a space time dataset.
  700. @param map: The map object to unregister
  701. @param dbif: The database interface to be used
  702. """
  703. connect = False
  704. if dbif == None:
  705. dbif = sql_database_interface()
  706. dbif.connect()
  707. connect = True
  708. if map.is_in_db(dbif) == False:
  709. dbif.close()
  710. core.fatal(_("Unable to find map <%s> in temporal database") % (map.get_id()))
  711. core.verbose(_("Unregister %s map <%s>") % (map.get_type(), map.get_id()))
  712. # First select all data from the database
  713. map.select(dbif)
  714. map_id = map.base.get_id()
  715. map_register_table = map.get_stds_register()
  716. stds_register_table = self.get_map_register()
  717. # Check if the map is registered in the space time raster dataset
  718. if dbmi.paramstyle == "qmark":
  719. sql = "SELECT id FROM " + map_register_table + " WHERE id = ?"
  720. else:
  721. sql = "SELECT id FROM " + map_register_table + " WHERE id = %s"
  722. dbif.cursor.execute(sql, (self.base.get_id(),))
  723. row = dbif.cursor.fetchone()
  724. # Break if the map is not registered
  725. if row == None:
  726. core.warning(_("Map <%s> is not registered in space time dataset") %(map_id, self.base.get_id()))
  727. if connect == True:
  728. dbif.close()
  729. return False
  730. # Remove the space time raster dataset from the raster dataset register
  731. if map_register_table != None:
  732. if dbmi.paramstyle == "qmark":
  733. sql = "DELETE FROM " + map_register_table + " WHERE id = ?"
  734. else:
  735. sql = "DELETE FROM " + map_register_table + " WHERE id = %s"
  736. dbif.cursor.execute(sql, (self.base.get_id(),))
  737. # Remove the raster map from the space time raster dataset register
  738. if stds_register_table != None:
  739. if dbmi.paramstyle == "qmark":
  740. sql = "DELETE FROM " + stds_register_table + " WHERE id = ?"
  741. else:
  742. sql = "DELETE FROM " + stds_register_table + " WHERE id = %s"
  743. dbif.cursor.execute(sql, (map_id,))
  744. if connect == True:
  745. dbif.close()
  746. def update_from_registered_maps(self, dbif = None):
  747. """This methods updates the spatial and temporal extent as well as
  748. type specific metadata. It should always been called after maps are registered
  749. or unregistered/deleted from the space time dataset.
  750. The update of the temporal extent checks if the end time is set correctly.
  751. In case the registered maps have no valid end time (None) the maximum start time
  752. will be used. If the end time is earlier than the maximum start time, it will
  753. be replaced by the maximum start time.
  754. An other solution to automate this is to use the diactivated trigger
  755. in the SQL files. But this will result in a huge performance issue
  756. in case many maps are registred (>1000).
  757. @param dbif: The database interface to be used
  758. """
  759. core.verbose(_("Update metadata, spatial and temporal extent from all registered maps of <%s>") % (self.get_id()))
  760. # Nothing to do if the register is not present
  761. if not self.get_map_register():
  762. return
  763. connect = False
  764. if dbif == None:
  765. dbif = sql_database_interface()
  766. dbif.connect()
  767. connect = True
  768. map_time = None
  769. use_start_time = False
  770. # Get basic info
  771. stds_name = self.base.get_name()
  772. stds_mapset = self.base.get_mapset()
  773. sql_path = get_sql_template_path()
  774. #We create a transaction
  775. sql_script = ""
  776. sql_script += "BEGIN TRANSACTION;\n"
  777. # Update the spatial and temporal extent from registered maps
  778. # Read the SQL template
  779. sql = open(os.path.join(sql_path, "update_stds_spatial_temporal_extent_template.sql"), 'r').read()
  780. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(None).get_type())
  781. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  782. sql = sql.replace("SPACETIME_ID", self.base.get_id())
  783. sql = sql.replace("STDS", self.get_type())
  784. sql_script += sql
  785. sql_script += "\n"
  786. # Update type specific metadata
  787. sql = open(os.path.join(sql_path, "update_" + self.get_type() + "_metadata_template.sql"), 'r').read()
  788. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(None).get_type())
  789. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  790. sql = sql.replace("SPACETIME_ID", self.base.get_id())
  791. sql = sql.replace("STDS", self.get_type())
  792. sql_script += sql
  793. sql_script += "\n"
  794. sql_script += "END TRANSACTION;"
  795. if dbmi.__name__ == "sqlite3":
  796. dbif.cursor.executescript(sql_script)
  797. else:
  798. dbif.cursor.execute(sql_script)
  799. # Read and validate the selected end time
  800. self.select()
  801. if self.is_time_absolute():
  802. start_time, end_time, tz = self.get_absolute_time()
  803. else:
  804. start_time, end_time = self.get_relative_time()
  805. # In case no end time is set, use the maximum start time of all registered maps as end time
  806. if end_time == None:
  807. use_start_time = True
  808. else:
  809. # Check if the end time is smaller than the maximum start time
  810. if self.is_time_absolute():
  811. sql = """SELECT max(start_time) FROM GRASS_MAP_absolute_time WHERE GRASS_MAP_absolute_time.id IN
  812. (SELECT id FROM SPACETIME_NAME_GRASS_MAP_register);"""
  813. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(None).get_type())
  814. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  815. else:
  816. sql = """SELECT max(start_time) FROM GRASS_MAP_relative_time WHERE GRASS_MAP_relative_time.id IN
  817. (SELECT id FROM SPACETIME_NAME_GRASS_MAP_register);"""
  818. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(None).get_type())
  819. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  820. dbif.cursor.execute(sql)
  821. row = dbif.cursor.fetchone()
  822. if row != None:
  823. # This seems to be a bug in sqlite3 Python driver
  824. if dbmi.__name__ == "sqlite3":
  825. tstring = row[0]
  826. # Convert the unicode string into the datetime format
  827. if tstring.find(":") > 0:
  828. time_format = "%Y-%m-%d %H:%M:%S"
  829. else:
  830. time_format = "%Y-%m-%d"
  831. max_start_time = datetime.strptime(tstring, time_format)
  832. else:
  833. max_start_time = row[0]
  834. if end_time < max_start_time:
  835. map_time = "mixed"
  836. use_start_time = True
  837. else:
  838. map_time = "interval"
  839. # Set the maximum start time as end time
  840. if use_start_time:
  841. if self.is_time_absolute():
  842. sql = """UPDATE STDS_absolute_time SET end_time =
  843. (SELECT max(start_time) FROM GRASS_MAP_absolute_time WHERE GRASS_MAP_absolute_time.id IN
  844. (SELECT id FROM SPACETIME_NAME_GRASS_MAP_register)
  845. ) WHERE id = 'SPACETIME_ID';"""
  846. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(None).get_type())
  847. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  848. sql = sql.replace("SPACETIME_ID", self.base.get_id())
  849. sql = sql.replace("STDS", self.get_type())
  850. elif self.is_time_relative():
  851. sql = """UPDATE STDS_relative_time SET end_time =
  852. (SELECT max(start_time) FROM GRASS_MAP_relative_time WHERE GRASS_MAP_relative_time.id IN
  853. (SELECT id FROM SPACETIME_NAME_GRASS_MAP_register)
  854. ) WHERE id = 'SPACETIME_ID';"""
  855. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(None).get_type())
  856. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  857. sql = sql.replace("SPACETIME_ID", self.base.get_id())
  858. sql = sql.replace("STDS", self.get_type())
  859. if dbmi.__name__ == "sqlite3":
  860. dbif.cursor.executescript(sql)
  861. else:
  862. dbif.cursor.execute(sql)
  863. if end_time == None:
  864. map_time = "point"
  865. # Set the map time type
  866. if self.is_time_absolute():
  867. self.absolute_time.select(dbif)
  868. self.metadata.select(dbif)
  869. if self.metadata.get_number_of_maps() > 0:
  870. self.absolute_time.set_map_time(map_time)
  871. else:
  872. self.absolute_time.set_map_time(None)
  873. self.absolute_time.update_all(dbif)
  874. else:
  875. self.relative_time.select(dbif)
  876. self.metadata.select(dbif)
  877. if self.metadata.get_number_of_maps() > 0:
  878. self.relative_time.set_map_time(map_time)
  879. else:
  880. self.relative_time.set_map_time(None)
  881. self.relative_time.update_all(dbif)
  882. # TODO: Compute the granularity of the dataset and update the database entry
  883. if connect == True:
  884. dbif.close()