abstract_space_time_dataset.py 33 KB

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