abstract_space_time_dataset.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902
  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. from abstract_dataset import *
  16. from temporal_granularity import *
  17. ###############################################################################
  18. class abstract_space_time_dataset(abstract_dataset):
  19. """Abstract space time dataset class
  20. This class represents a space time dataset. Convenient functions
  21. to select, update, insert or delete objects of this type in the SQL
  22. temporal database exists as well as functions to register or unregister
  23. raster maps.
  24. Parts of the temporal logic are implemented in the SQL temporal database,
  25. like the computation of the temporal and spatial extent as well as the
  26. collecting of metadata.
  27. """
  28. def __init__(self, ident):
  29. self.reset(ident)
  30. def get_new_instance(self, ident=None):
  31. """Return a new instance with the type of this class
  32. @param ident: The unique identifier of the new object
  33. """
  34. raise IOError("This method must be implemented in the subclasses")
  35. def get_new_map_instance(self, ident=None):
  36. """Return a new instance of a map dataset which is associated with the type of this class
  37. @param ident: The unique identifier of the new object
  38. """
  39. raise IOError("This method must be implemented in the subclasses")
  40. def get_map_register(self):
  41. """Return the name of the map register table"""
  42. raise IOError("This method must be implemented in the subclasses")
  43. def set_map_register(self, name):
  44. """Set the name of the map register table
  45. This table stores all map names which are registered in this space time dataset.
  46. @param name: The name of the register table
  47. """
  48. raise IOError("This method must be implemented in the subclasses")
  49. def set_initial_values(self, temporal_type, semantic_type, \
  50. title=None, description=None):
  51. """Set the initial values of the space time dataset
  52. @param temporal_type: The temporal type of this space time dataset (absolute or relative)
  53. @param semantic_type: The semantic type of this dataset
  54. @param title: The title
  55. @param description: The description of this dataset
  56. """
  57. if temporal_type == "absolute":
  58. self.set_time_to_absolute()
  59. elif temporal_type == "relative":
  60. self.set_time_to_relative()
  61. else:
  62. core.fatal(_("Unknown temporal type \"%s\"") % (temporal_type))
  63. self.base.set_semantic_type(semantic_type)
  64. self.metadata.set_title(title)
  65. self.metadata.set_description(description)
  66. def get_initial_values(self):
  67. """Return the initial values: temporal_type, semantic_type, title, description"""
  68. temporal_type = self.get_temporal_type()
  69. semantic_type = self.base.get_semantic_type()
  70. title = self.metadata.get_title()
  71. description = self.metadata.get_description()
  72. return temporal_type, semantic_type, title, description
  73. def get_granularity(self):
  74. """Return the granularity"""
  75. temporal_type = self.get_temporal_type()
  76. if temporal_type == "absolute":
  77. granularity = self.absolute_time.get_granularity()
  78. elif temporal_type == "relative":
  79. granularity = self.relative_time.get_granularity()
  80. return granularity
  81. def set_granularity(self, granularity):
  82. temporal_type = self.get_temporal_type()
  83. if temporal_type == "absolute":
  84. self.set_time_to_absolute()
  85. self.absolute_time.set_granularity(granularity)
  86. elif temporal_type == "relative":
  87. self.set_time_to_relative()
  88. self.relative_time.set_granularity(granularity)
  89. else:
  90. core.fatal(_("Unknown temporal type \"%s\"") % (temporal_type))
  91. def get_map_time(self):
  92. """Return the type of the map time, interval, point, maixed or invalid"""
  93. temporal_type = self.get_temporal_type()
  94. if temporal_type == "absolute":
  95. map_time = self.absolute_time.get_map_time()
  96. elif temporal_type == "relative":
  97. map_time = self.relative_time.get_map_time()
  98. return map_time
  99. def print_temporal_relation_matrix(self, maps):
  100. """Print the temporal relation matrix of all registered maps to stdout
  101. The temproal relation matrix includes the temporal relations between
  102. all registered maps. The relations are strings stored in a list of lists.
  103. @param dbif: The database interface to be used
  104. """
  105. for i in range(len(maps)):
  106. reltations = ""
  107. count = 0
  108. for j in range(i + 1, len(maps)):
  109. relation = maps[j].temporal_relation(maps[i])
  110. # print maps[j].base.get_name(), maps[i].base.get_name(), relation
  111. if count == 0:
  112. relations = relation
  113. else:
  114. relations += "," + relation
  115. count += 1
  116. # Break if the the next map follows
  117. if relation == "follows":
  118. break
  119. # Break if the the next map is after
  120. if relation == "after":
  121. break
  122. if i < len(maps) - 1:
  123. print maps[i].base.get_name(), relations
  124. else:
  125. print maps[i].base.get_name()
  126. def get_temporal_relation_matrix(self, maps):
  127. """Return the temporal relation matrix of all registered maps as listof lists
  128. The map list must be ordered by start time
  129. The temproal relation matrix includes the temporal relations between
  130. all registered maps. The relations are strings stored in a list of lists.
  131. @param maps: a ordered by start_time list of map objects
  132. """
  133. matrix = []
  134. # Create the temporal relation matrix
  135. # Add the map names first
  136. row = []
  137. for map in maps:
  138. row.append(map.get_id())
  139. matrix.append(row)
  140. for mapA in maps:
  141. row = []
  142. for mapB in maps:
  143. row.append(mapA.temporal_relation(mapB))
  144. matrix.append(row)
  145. return matrix
  146. def count_temporal_types(self, maps):
  147. """Return the temporal type of the registered maps as dictionary
  148. The map list must be ordered by start time
  149. The temporal type can be:
  150. * point -> only the start time is present
  151. * interval -> start and end time
  152. * invalid -> No valid time point or interval found
  153. @param maps: A sorted (start_time) list of abstract_dataset objects
  154. """
  155. time_invalid = 0
  156. time_point = 0
  157. time_interval = 0
  158. tcount = {}
  159. for i in range(len(maps)):
  160. # Check for point and interval data
  161. if maps[i].is_time_absolute():
  162. start, end, tz = maps[i].get_absolute_time()
  163. if maps[i].is_time_relative():
  164. start, end = maps[i].get_relative_time()
  165. if start and end:
  166. time_interval += 1
  167. elif start and not end:
  168. time_point += 1
  169. else:
  170. time_invalid += 1
  171. tcount["point"] = time_point
  172. tcount["interval"] = time_interval
  173. tcount["invalid"] = time_invalid
  174. return tcount
  175. def count_gaps(self, maps):
  176. """Count the number of gaps between temporal neighbours
  177. @param maps: A sorted (start_time) list of abstract_dataset objects
  178. @return The numbers of gaps between temporal neighbours
  179. """
  180. gaps = 0
  181. # Check for gaps
  182. for i in range(len(maps)):
  183. if i < len(maps) - 1:
  184. relation = maps[i + 1].temporal_relation(maps[i])
  185. if relation == "after":
  186. gaps += 1
  187. return gaps
  188. def count_temporal_relations(self, maps):
  189. """Count the temporal relations between the registered maps.
  190. The map list must be ordered by start time
  191. @param maps: A sorted (start_time) list of abstract_dataset objects
  192. @return A dictionary with counted temporal relationships
  193. """
  194. tcount = {}
  195. for i in range(len(maps)):
  196. # Check for point and interval data
  197. for j in range(i + 1, len(maps)):
  198. relation = maps[j].temporal_relation(maps[i])
  199. if tcount.has_key(relation):
  200. tcount[relation] = tcount[relation] + 1
  201. else:
  202. tcount[relation] = 1
  203. # Break if the the next map follows
  204. if relation == "follows":
  205. break
  206. # Break if the the next map is after
  207. if relation == "after":
  208. break
  209. return tcount
  210. def check_temporal_topology(self, maps=None, dbif=None):
  211. """Check the temporal topology
  212. Correct topology means, that time intervals are not overlap or
  213. that intervals does not contain other intervals. Equal time intervals or
  214. points of time are not allowed.
  215. The map list must be ordered by start time
  216. Allowed and not allowed temporal relationships for correct topology
  217. after -> allowed
  218. before -> allowed
  219. follows -> allowed
  220. precedes -> allowed
  221. equivalent -> not allowed
  222. during -> not allowed
  223. contains -> not allowed
  224. overlaps -> not allowed
  225. overlapped -> not allowed
  226. starts -> not allowed
  227. finishes -> not allowed
  228. started -> not allowed
  229. finished -> not allowed
  230. @param maps: A sorted (start_time) list of abstract_dataset objects
  231. @return True if topology is correct
  232. """
  233. if maps == None:
  234. maps = get_registered_maps_as_objects(where=None, order="start_time", dbif=dbif)
  235. relations = self.count_temporal_relations(maps)
  236. map_time = self.get_map_time()
  237. if map_time == "interval" or map_time == "mixed":
  238. if relations.has_key("equivalent"):
  239. return False
  240. if relations.has_key("during"):
  241. return False
  242. if relations.has_key("contains"):
  243. return False
  244. if relations.has_key("overlaps"):
  245. return False
  246. if relations.has_key("overlapped"):
  247. return False
  248. if relations.has_key("starts"):
  249. return False
  250. if relations.has_key("finishes"):
  251. return False
  252. if relations.has_key("started"):
  253. return False
  254. if relations.has_key("finished"):
  255. return False
  256. elif map_time == "point":
  257. if relations.has_key("equivalent"):
  258. return False
  259. else:
  260. return False
  261. return True
  262. def get_registered_maps_as_objects(self, where=None, order="start_time", dbif=None):
  263. """Return all registered maps as ordered object list
  264. @param where: The SQL where statement to select a subset of the registered maps without "WHERE"
  265. @param order: The SQL order statement to be used to order the objects in the list without "ORDER BY"
  266. @param dbif: The database interface to be used
  267. In case nothing found None is returned
  268. """
  269. connect = False
  270. if dbif == None:
  271. dbif = sql_database_interface()
  272. dbif.connect()
  273. connect = True
  274. obj_list = []
  275. rows = self.get_registered_maps("id", where, order, dbif)
  276. if rows:
  277. for row in rows:
  278. map = self.get_new_map_instance(row["id"])
  279. map.select(dbif)
  280. obj_list.append(copy.copy(map))
  281. if connect == True:
  282. dbif.close()
  283. return obj_list
  284. def get_registered_maps(self, columns=None, where = None, order = None, dbif=None):
  285. """Return sqlite rows of all registered maps.
  286. Each row includes all columns specified in the datatype specific view
  287. @param columns: Columns to be selected as SQL compliant string
  288. @param where: The SQL where statement to select a subset of the registered maps without "WHERE"
  289. @param order: The SQL order statement to be used to order the objects in the list without "ORDER BY"
  290. @param dbif: The database interface to be used
  291. In case nothing found None is returned
  292. """
  293. connect = False
  294. if dbif == None:
  295. dbif = sql_database_interface()
  296. dbif.connect()
  297. connect = True
  298. rows = None
  299. if self.get_map_register():
  300. # Use the correct temporal table
  301. if self.get_temporal_type() == "absolute":
  302. map_view = self.get_new_map_instance(None).get_type() + "_view_abs_time"
  303. else:
  304. map_view = self.get_new_map_instance(None).get_type() + "_view_rel_time"
  305. if columns:
  306. sql = "SELECT %s FROM %s WHERE %s.id IN (SELECT id FROM %s)" % (columns, map_view, map_view, self.get_map_register())
  307. else:
  308. sql = "SELECT * FROM %s WHERE %s.id IN (SELECT id FROM %s)" % (map_view, map_view, self.get_map_register())
  309. if where:
  310. sql += " AND %s" % (where)
  311. if order:
  312. sql += " ORDER BY %s" % (order)
  313. try:
  314. dbif.cursor.execute(sql)
  315. rows = dbif.cursor.fetchall()
  316. except:
  317. if connect == True:
  318. dbif.close()
  319. core.error(_("Unable to get map ids from register table <%s>") % (self.get_map_register()))
  320. raise
  321. if connect == True:
  322. dbif.close()
  323. return rows
  324. def delete(self, dbif=None):
  325. """Delete a space time dataset from the temporal database
  326. This method removes the space time dataset from the temporal database and drops its map register table
  327. @param dbif: The database interface to be used
  328. """
  329. # First we need to check if maps are registered in this dataset and
  330. # unregister them
  331. core.verbose(_("Delete space time %s dataset <%s> from temporal database") % (self.get_new_map_instance(ident=None).get_type(), self.get_id()))
  332. connect = False
  333. if dbif == None:
  334. dbif = sql_database_interface()
  335. dbif.connect()
  336. connect = True
  337. # SELECT all needed informations from the database
  338. self.select(dbif)
  339. core.verbose(_("Drop map register table: %s") % (self.get_map_register()))
  340. if self.get_map_register():
  341. rows = self.get_registered_maps("id", None, None, dbif)
  342. # Unregister each registered map in the table
  343. if rows:
  344. num_maps = len(rows)
  345. count = 0
  346. for row in rows:
  347. core.percent(count, num_maps, 1)
  348. # Unregister map
  349. map = self.get_new_map_instance(row["id"])
  350. self.unregister_map(map, dbif)
  351. count += 1
  352. core.percent(1, 1, 1)
  353. try:
  354. # Drop the map register table
  355. sql = "DROP TABLE " + self.get_map_register()
  356. dbif.cursor.execute(sql)
  357. dbif.connection.commit()
  358. except:
  359. if connect == True:
  360. dbif.close()
  361. core.error(_("Unable to drop table <%s>") % (self.get_map_register()))
  362. raise
  363. # Remove the primary key, the foreign keys will be removed by trigger
  364. self.base.delete(dbif)
  365. self.reset(None)
  366. if connect == True:
  367. dbif.close()
  368. def register_map(self, map, dbif=None):
  369. """ Register a map in the space time dataset.
  370. This method takes care of the registration of a map
  371. in a space time dataset.
  372. In case the map is already registered this function will break with a warning
  373. and return False
  374. @param dbif: The database interface to be used
  375. """
  376. connect = False
  377. if dbif == None:
  378. dbif = sql_database_interface()
  379. dbif.connect()
  380. connect = True
  381. if map.is_in_db(dbif) == False:
  382. dbif.close()
  383. core.fatal(_("Only maps with absolute or relative valid time can be registered"))
  384. core.verbose(_("Register %s map <%s> in space time %s dataset <%s>") % (map.get_type(), map.get_id(), map.get_type(), self.get_id()))
  385. # First select all data from the database
  386. map.select(dbif)
  387. if not map.check_valid_time():
  388. core.fatal(_("Map <%s> has invalid time") % map.get_id())
  389. map_id = map.base.get_id()
  390. map_name = map.base.get_name()
  391. map_mapset = map.base.get_mapset()
  392. map_register_table = map.get_stds_register()
  393. #print "Map register table", map_register_table
  394. # Get basic info
  395. stds_name = self.base.get_name()
  396. stds_mapset = self.base.get_mapset()
  397. stds_register_table = self.get_map_register()
  398. #print "STDS register table", stds_register_table
  399. if stds_mapset != map_mapset:
  400. dbif.close()
  401. core.fatal(_("Only maps from the same mapset can be registered"))
  402. # Check if map is already registred
  403. if stds_register_table:
  404. if dbmi.paramstyle == "qmark":
  405. sql = "SELECT id FROM " + stds_register_table + " WHERE id = (?)"
  406. else:
  407. sql = "SELECT id FROM " + stds_register_table + " WHERE id = (%s)"
  408. dbif.cursor.execute(sql, (map_id,))
  409. row = dbif.cursor.fetchone()
  410. # In case of no entry make a new one
  411. if row and row[0] == map_id:
  412. if connect == True:
  413. dbif.close()
  414. core.warning(_("Map <%s> is already registered.") % (map_id))
  415. return False
  416. # Create tables
  417. sql_path = get_sql_template_path()
  418. # We need to create the stmap raster register table bevor we can register the map
  419. if map_register_table == None:
  420. # Create a unique id
  421. uuid_rand = "map_" + str(uuid.uuid4()).replace("-", "")
  422. map_register_table = uuid_rand + "_" + self.get_type() + "_register"
  423. # Read the SQL template
  424. sql = open(os.path.join(sql_path, "map_stds_register_table_template.sql"), 'r').read()
  425. # Create the raster, raster3d and vector tables
  426. sql = sql.replace("GRASS_MAP", map.get_type())
  427. sql = sql.replace("MAP_NAME", map_name + "_" + map_mapset )
  428. sql = sql.replace("TABLE_NAME", uuid_rand )
  429. sql = sql.replace("MAP_ID", map_id)
  430. sql = sql.replace("STDS", self.get_type())
  431. try:
  432. if dbmi.__name__ == "sqlite3":
  433. dbif.cursor.executescript(sql)
  434. else:
  435. dbif.cursor.execute(sql)
  436. except:
  437. if connect == True:
  438. dbif.close()
  439. core.error(_("Unable to create the space time %s dataset register table for <%s>") % \
  440. (map.get_type(), map.get_id()))
  441. raise
  442. # Set the stds register table name and put it into the DB
  443. map.set_stds_register(map_register_table)
  444. map.metadata.update(dbif)
  445. core.verbose(_("Created register table <%s> for %s map <%s>") % \
  446. (map_register_table, map.get_type(), map.get_id()))
  447. # We need to create the table and register it
  448. if stds_register_table == None:
  449. # Create table name
  450. stds_register_table = stds_name + "_" + stds_mapset + "_" + map.get_type() + "_register"
  451. # Read the SQL template
  452. sql = open(os.path.join(sql_path, "stds_map_register_table_template.sql"), 'r').read()
  453. # Create the raster, raster3d and vector tables
  454. sql = sql.replace("GRASS_MAP", map.get_type())
  455. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  456. sql = sql.replace("SPACETIME_ID", self.base.get_id())
  457. sql = sql.replace("STDS", self.get_type())
  458. sql_script = ""
  459. sql_script += "BEGIN TRANSACTION;\n"
  460. sql_script += sql
  461. sql_script += "\n"
  462. sql_script += "END TRANSACTION;"
  463. try:
  464. if dbmi.__name__ == "sqlite3":
  465. dbif.cursor.executescript(sql_script)
  466. else:
  467. dbif.cursor.execute(sql_script)
  468. dbif.connection.commit()
  469. except:
  470. if connect == True:
  471. dbif.close()
  472. core.error(_("Unable to create the space time %s dataset register table for <%s>") % \
  473. (map.get_type(), map.get_id()))
  474. raise
  475. # Set the map register table name and put it into the DB
  476. self.set_map_register(stds_register_table)
  477. self.metadata.update(dbif)
  478. core.verbose(_("Created register table <%s> for space time %s dataset <%s>") % \
  479. (stds_register_table, map.get_type(), self.get_id()))
  480. # Register the stds in the map stds register table
  481. # Check if the entry is already there
  482. if dbmi.paramstyle == "qmark":
  483. sql = "SELECT id FROM " + map_register_table + " WHERE id = ?"
  484. else:
  485. sql = "SELECT id FROM " + map_register_table + " WHERE id = %s"
  486. dbif.cursor.execute(sql, (self.base.get_id(),))
  487. row = dbif.cursor.fetchone()
  488. # In case of no entry make a new one
  489. if row == None:
  490. if dbmi.paramstyle == "qmark":
  491. sql = "INSERT INTO " + map_register_table + " (id) " + "VALUES (?)"
  492. else:
  493. sql = "INSERT INTO " + map_register_table + " (id) " + "VALUES (%s)"
  494. #print sql
  495. dbif.cursor.execute(sql, (self.base.get_id(),))
  496. # Now put the raster name in the stds map register table
  497. if dbmi.paramstyle == "qmark":
  498. sql = "INSERT INTO " + stds_register_table + " (id) " + "VALUES (?)"
  499. else:
  500. sql = "INSERT INTO " + stds_register_table + " (id) " + "VALUES (%s)"
  501. #print sql
  502. dbif.cursor.execute(sql, (map_id,))
  503. if connect == True:
  504. dbif.close()
  505. return True
  506. def unregister_map(self, map, dbif = None):
  507. """Unregister a map from the space time dataset.
  508. This method takes care of the unregistration of a map
  509. from a space time dataset.
  510. @param map: The map object to unregister
  511. @param dbif: The database interface to be used
  512. """
  513. connect = False
  514. if dbif == None:
  515. dbif = sql_database_interface()
  516. dbif.connect()
  517. connect = True
  518. if map.is_in_db(dbif) == False:
  519. dbif.close()
  520. core.fatal(_("Unable to find map <%s> in temporal database") % (map.get_id()))
  521. core.verbose(_("Unregister %s map <%s>") % (map.get_type(), map.get_id()))
  522. # First select all data from the database
  523. map.select(dbif)
  524. map_id = map.base.get_id()
  525. map_register_table = map.get_stds_register()
  526. stds_register_table = self.get_map_register()
  527. # Check if the map is registered in the space time raster dataset
  528. if dbmi.paramstyle == "qmark":
  529. sql = "SELECT id FROM " + map_register_table + " WHERE id = ?"
  530. else:
  531. sql = "SELECT id FROM " + map_register_table + " WHERE id = %s"
  532. dbif.cursor.execute(sql, (self.base.get_id(),))
  533. row = dbif.cursor.fetchone()
  534. # Break if the map is not registered
  535. if row == None:
  536. core.warning(_("Map <%s> is not registered in space time dataset") %(map_id, self.base.get_id()))
  537. if connect == True:
  538. dbif.close()
  539. return False
  540. # Remove the space time raster dataset from the raster dataset register
  541. if map_register_table != None:
  542. if dbmi.paramstyle == "qmark":
  543. sql = "DELETE FROM " + map_register_table + " WHERE id = ?"
  544. else:
  545. sql = "DELETE FROM " + map_register_table + " WHERE id = %s"
  546. dbif.cursor.execute(sql, (self.base.get_id(),))
  547. # Remove the raster map from the space time raster dataset register
  548. if stds_register_table != None:
  549. if dbmi.paramstyle == "qmark":
  550. sql = "DELETE FROM " + stds_register_table + " WHERE id = ?"
  551. else:
  552. sql = "DELETE FROM " + stds_register_table + " WHERE id = %s"
  553. dbif.cursor.execute(sql, (map_id,))
  554. if connect == True:
  555. dbif.close()
  556. def update_from_registered_maps(self, dbif = None):
  557. """This methods updates the spatial and temporal extent as well as
  558. type specific metadata. It should always been called after maps are registered
  559. or unregistered/deleted from the space time dataset.
  560. The update of the temporal extent checks if the end time is set correctly.
  561. In case the registered maps have no valid end time (None) the maximum start time
  562. will be used. If the end time is earlier than the maximum start time, it will
  563. be replaced by the maximum start time.
  564. An other solution to automate this is to use the diactivated trigger
  565. in the SQL files. But this will result in a huge performance issue
  566. in case many maps are registred (>1000).
  567. @param dbif: The database interface to be used
  568. """
  569. core.verbose(_("Update metadata, spatial and temporal extent from all registered maps of <%s>") % (self.get_id()))
  570. # Nothing to do if the register is not present
  571. if not self.get_map_register():
  572. return
  573. connect = False
  574. if dbif == None:
  575. dbif = sql_database_interface()
  576. dbif.connect()
  577. connect = True
  578. map_time = None
  579. use_start_time = False
  580. # Get basic info
  581. stds_name = self.base.get_name()
  582. stds_mapset = self.base.get_mapset()
  583. sql_path = get_sql_template_path()
  584. #We create a transaction
  585. sql_script = ""
  586. sql_script += "BEGIN TRANSACTION;\n"
  587. # Update the spatial and temporal extent from registered maps
  588. # Read the SQL template
  589. sql = open(os.path.join(sql_path, "update_stds_spatial_temporal_extent_template.sql"), 'r').read()
  590. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(None).get_type())
  591. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  592. sql = sql.replace("SPACETIME_ID", self.base.get_id())
  593. sql = sql.replace("STDS", self.get_type())
  594. sql_script += sql
  595. sql_script += "\n"
  596. # Update type specific metadata
  597. sql = open(os.path.join(sql_path, "update_" + self.get_type() + "_metadata_template.sql"), 'r').read()
  598. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(None).get_type())
  599. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  600. sql = sql.replace("SPACETIME_ID", self.base.get_id())
  601. sql = sql.replace("STDS", self.get_type())
  602. sql_script += sql
  603. sql_script += "\n"
  604. sql_script += "END TRANSACTION;"
  605. if dbmi.__name__ == "sqlite3":
  606. dbif.cursor.executescript(sql_script)
  607. else:
  608. dbif.cursor.execute(sql_script)
  609. # Read and validate the selected end time
  610. self.select()
  611. if self.is_time_absolute():
  612. start_time, end_time, tz = self.get_absolute_time()
  613. else:
  614. start_time, end_time = self.get_relative_time()
  615. # In case no end time is set, use the maximum start time of all registered maps as end time
  616. if end_time == None:
  617. use_start_time = True
  618. else:
  619. # Check if the end time is smaller than the maximum start time
  620. if self.is_time_absolute():
  621. sql = """SELECT max(start_time) FROM GRASS_MAP_absolute_time WHERE GRASS_MAP_absolute_time.id IN
  622. (SELECT id FROM SPACETIME_NAME_GRASS_MAP_register);"""
  623. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(None).get_type())
  624. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  625. else:
  626. sql = """SELECT max(start_time) FROM GRASS_MAP_relative_time WHERE GRASS_MAP_relative_time.id IN
  627. (SELECT id FROM SPACETIME_NAME_GRASS_MAP_register);"""
  628. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(None).get_type())
  629. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  630. dbif.cursor.execute(sql)
  631. row = dbif.cursor.fetchone()
  632. if row != None:
  633. # This seems to be a bug in sqlite3 Python driver
  634. if dbmi.__name__ == "sqlite3":
  635. tstring = row[0]
  636. # Convert the unicode string into the datetime format
  637. if tstring.find(":") > 0:
  638. time_format = "%Y-%m-%d %H:%M:%S"
  639. else:
  640. time_format = "%Y-%m-%d"
  641. max_start_time = datetime.strptime(tstring, time_format)
  642. else:
  643. max_start_time = row[0]
  644. if end_time < max_start_time:
  645. use_start_time = True
  646. # Set the maximum start time as end time
  647. if use_start_time:
  648. if self.is_time_absolute():
  649. sql = """UPDATE STDS_absolute_time SET end_time =
  650. (SELECT max(start_time) FROM GRASS_MAP_absolute_time WHERE GRASS_MAP_absolute_time.id IN
  651. (SELECT id FROM SPACETIME_NAME_GRASS_MAP_register)
  652. ) WHERE id = 'SPACETIME_ID';"""
  653. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(None).get_type())
  654. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  655. sql = sql.replace("SPACETIME_ID", self.base.get_id())
  656. sql = sql.replace("STDS", self.get_type())
  657. elif self.is_time_relative():
  658. sql = """UPDATE STDS_relative_time SET end_time =
  659. (SELECT max(start_time) FROM GRASS_MAP_relative_time WHERE GRASS_MAP_relative_time.id IN
  660. (SELECT id FROM SPACETIME_NAME_GRASS_MAP_register)
  661. ) WHERE id = 'SPACETIME_ID';"""
  662. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(None).get_type())
  663. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  664. sql = sql.replace("SPACETIME_ID", self.base.get_id())
  665. sql = sql.replace("STDS", self.get_type())
  666. if dbmi.__name__ == "sqlite3":
  667. dbif.cursor.executescript(sql)
  668. else:
  669. dbif.cursor.execute(sql)
  670. # Count the temporal map types
  671. maps = self.get_registered_maps_as_objects(dbif=dbif)
  672. tlist = self.count_temporal_types(maps)
  673. if tlist["interval"] > 0 and tlist["point"] == 0 and tlist["invalid"] == 0:
  674. map_time = "interval"
  675. elif tlist["interval"] == 0 and tlist["point"] > 0 and tlist["invalid"] == 0:
  676. map_time = "point"
  677. elif tlist["interval"] > 0 and tlist["point"] > 0 and tlist["invalid"] == 0:
  678. map_time = "mixed"
  679. else:
  680. map_time = "invalid"
  681. # Compute the granularity
  682. if map_time != "invalid":
  683. # Smallest supported temporal resolution
  684. if self.is_time_absolute():
  685. gran = compute_absolute_time_granularity(maps)
  686. elif self.is_time_relative():
  687. gran = compute_absolute_time_granularity(maps)
  688. else:
  689. gran = None
  690. # Set the map time type and update the time objects
  691. if self.is_time_absolute():
  692. self.absolute_time.select(dbif)
  693. self.metadata.select(dbif)
  694. if self.metadata.get_number_of_maps() > 0:
  695. self.absolute_time.set_map_time(map_time)
  696. self.absolute_time.set_granularity(gran)
  697. else:
  698. self.absolute_time.set_map_time(None)
  699. self.absolute_time.set_granularity(None)
  700. self.absolute_time.update_all(dbif)
  701. else:
  702. self.relative_time.select(dbif)
  703. self.metadata.select(dbif)
  704. if self.metadata.get_number_of_maps() > 0:
  705. self.relative_time.set_map_time(map_time)
  706. self.relative_time.set_granularity(gran)
  707. else:
  708. self.relative_time.set_map_time(None)
  709. self.relative_time.set_granularity(None)
  710. self.relative_time.update_all(dbif)
  711. if connect == True:
  712. dbif.close()