abstract_space_time_dataset.py 32 KB

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