abstract_space_time_dataset.py 36 KB

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