abstract_space_time_dataset.py 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045
  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 != None and end != None:
  169. time_interval += 1
  170. elif start != None and end == None:
  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_by_granularity(self, gran=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 using the
  268. granularity of the space time dataset as increment
  269. A valid temporal topology (no overlapping or inclusion allowed) is needed to get correct results.
  270. The dataset must have "interval" as temporal map type, so all maps have valid interval time.
  271. Gaps between maps are identified as unregistered maps with id==None.
  272. The objects are initialized with the id and the temporal extent (temporal type, start time, end time).
  273. In case more map informations are needed, use the select() method for each listed object.
  274. @param gran: The granularity to be used
  275. @param dbif: The database interface to be used
  276. In case nothing found None is returned
  277. """
  278. connect = False
  279. if dbif == None:
  280. dbif = sql_database_interface()
  281. dbif.connect()
  282. connect = True
  283. obj_list = []
  284. if self.get_map_time() != "interval":
  285. core.error(_("The temporal map type must be interval"))
  286. #TODO: Check for valid topology too?
  287. return None
  288. if gran == None:
  289. gran = self.get_granularity()
  290. start, end = self.get_valid_time()
  291. while start < end:
  292. if self.is_time_absolute():
  293. next = increment_datetime_by_string(start, gran)
  294. else:
  295. next = start + gran
  296. where = "(start_time <= '%s' and end_time >= '%s')" % (start, next)
  297. rows = self.get_registered_maps("id", where, "start_time", dbif)
  298. if rows:
  299. if len(rows) > 1:
  300. core.error(_("More than one map found in a granule. Temporal granularity seems to be invalid or the chosen granularity is not a greatest common divider of all intervals and gaps in the dataset."))
  301. # Take the first map
  302. map = self.get_new_map_instance(rows[0]["id"])
  303. else:
  304. # In case of a gap, create a dummy map with identifier None
  305. map = self.get_new_map_instance(None)
  306. if self.is_time_absolute():
  307. map.set_absolute_time(start, next)
  308. elif self.is_time_relative():
  309. map.set_relative_time(start, next)
  310. obj_list.append(copy.copy(map))
  311. start = next
  312. if connect == True:
  313. dbif.close()
  314. return obj_list
  315. def get_registered_maps_as_objects_with_gaps(self, where=None, dbif=None):
  316. """Return all registered maps as ordered (by start_time) object list with
  317. "gap" map objects (id==None) for temporal topological operations
  318. Gaps between maps are identified as maps with id==None
  319. The objects are initialized with the id and the temporal extent (temporal type, start time, end time).
  320. In case more map informations are needed, use the select() method for each listed object.
  321. @param where: The SQL where statement to select a subset of the registered maps without "WHERE"
  322. @param dbif: The database interface to be used
  323. In case nothing found None is returned
  324. """
  325. connect = False
  326. if dbif == None:
  327. dbif = sql_database_interface()
  328. dbif.connect()
  329. connect = True
  330. obj_list = []
  331. maps = self.get_registered_maps_as_objects(where, "start_time", dbif)
  332. if maps and len(maps) > 0:
  333. for i in range(len(maps)):
  334. obj_list.append(maps[i])
  335. # Detect and insert gaps
  336. if i < len(maps) - 1:
  337. relation = maps[i + 1].temporal_relation(maps[i])
  338. if relation == "after":
  339. start1, end1 = maps[i].get_valid_time()
  340. start2, end2 = maps[i + 1].get_valid_time()
  341. end = start2
  342. if end1:
  343. start = end1
  344. else:
  345. start = start1
  346. map = self.get_new_map_instance(None)
  347. if self.is_time_absolute():
  348. map.set_absolute_time(start, end)
  349. elif self.is_time_relative():
  350. map.set_relative_time(start, end)
  351. obj_list.append(copy.copy(map))
  352. if connect == True:
  353. dbif.close()
  354. return obj_list
  355. def get_registered_maps_as_objects(self, where=None, order="start_time", dbif=None):
  356. """Return all registered maps as ordered object list for temporal topological operations
  357. The objects are initialized with the id and the temporal extent (temporal type, start time, end time).
  358. In case more map informations are needed, use the select() method for each listed object.
  359. @param where: The SQL where statement to select a subset of the registered maps without "WHERE"
  360. @param order: The SQL order statement to be used to order the objects in the list without "ORDER BY"
  361. @param dbif: The database interface to be used
  362. In case nothing found None is returned
  363. """
  364. connect = False
  365. if dbif == None:
  366. dbif = sql_database_interface()
  367. dbif.connect()
  368. connect = True
  369. obj_list = []
  370. rows = self.get_registered_maps("id,start_time,end_time", where, order, dbif)
  371. count = 0
  372. if rows:
  373. for row in rows:
  374. core.percent(count, len(rows), 1)
  375. map = self.get_new_map_instance(row["id"])
  376. if self.is_time_absolute():
  377. map.set_absolute_time(row["start_time"], row["end_time"])
  378. elif self.is_time_relative():
  379. map.set_relative_time(row["start_time"], row["end_time"])
  380. obj_list.append(copy.copy(map))
  381. count += 1
  382. core.percent(1, 1, 1)
  383. if connect == True:
  384. dbif.close()
  385. return obj_list
  386. def get_registered_maps(self, columns=None, where = None, order = None, dbif=None):
  387. """Return sqlite rows of all registered maps.
  388. In case columsn are not specified, each row includes all columns specified in the datatype specific view
  389. @param columns: Columns to be selected as SQL compliant string
  390. @param where: The SQL where statement to select a subset of the registered maps without "WHERE"
  391. @param order: The SQL order statement to be used to order the objects in the list without "ORDER BY"
  392. @param dbif: The database interface to be used
  393. In case nothing found None is returned
  394. """
  395. connect = False
  396. if dbif == None:
  397. dbif = sql_database_interface()
  398. dbif.connect()
  399. connect = True
  400. rows = None
  401. if self.get_map_register():
  402. # Use the correct temporal table
  403. if self.get_temporal_type() == "absolute":
  404. map_view = self.get_new_map_instance(None).get_type() + "_view_abs_time"
  405. else:
  406. map_view = self.get_new_map_instance(None).get_type() + "_view_rel_time"
  407. if columns:
  408. sql = "SELECT %s FROM %s WHERE %s.id IN (SELECT id FROM %s)" % (columns, map_view, map_view, self.get_map_register())
  409. else:
  410. sql = "SELECT * FROM %s WHERE %s.id IN (SELECT id FROM %s)" % (map_view, map_view, self.get_map_register())
  411. if where:
  412. sql += " AND %s" % (where)
  413. if order:
  414. sql += " ORDER BY %s" % (order)
  415. try:
  416. dbif.cursor.execute(sql)
  417. rows = dbif.cursor.fetchall()
  418. except:
  419. if connect == True:
  420. dbif.close()
  421. core.error(_("Unable to get map ids from register table <%s>") % (self.get_map_register()))
  422. raise
  423. if connect == True:
  424. dbif.close()
  425. return rows
  426. def delete(self, dbif=None):
  427. """Delete a space time dataset from the temporal database
  428. This method removes the space time dataset from the temporal database and drops its map register table
  429. @param dbif: The database interface to be used
  430. """
  431. # First we need to check if maps are registered in this dataset and
  432. # unregister them
  433. core.verbose(_("Delete space time %s dataset <%s> from temporal database") % (self.get_new_map_instance(ident=None).get_type(), self.get_id()))
  434. connect = False
  435. if dbif == None:
  436. dbif = sql_database_interface()
  437. dbif.connect()
  438. connect = True
  439. # SELECT all needed informations from the database
  440. self.select(dbif)
  441. core.verbose(_("Drop map register table: %s") % (self.get_map_register()))
  442. if self.get_map_register():
  443. rows = self.get_registered_maps("id", None, None, dbif)
  444. # Unregister each registered map in the table
  445. if rows:
  446. num_maps = len(rows)
  447. count = 0
  448. for row in rows:
  449. core.percent(count, num_maps, 1)
  450. # Unregister map
  451. map = self.get_new_map_instance(row["id"])
  452. self.unregister_map(map, dbif)
  453. count += 1
  454. core.percent(1, 1, 1)
  455. try:
  456. # Drop the map register table
  457. sql = "DROP TABLE " + self.get_map_register()
  458. dbif.cursor.execute(sql)
  459. dbif.connection.commit()
  460. except:
  461. if connect == True:
  462. dbif.close()
  463. core.error(_("Unable to drop table <%s>") % (self.get_map_register()))
  464. raise
  465. # Remove the primary key, the foreign keys will be removed by trigger
  466. self.base.delete(dbif)
  467. self.reset(None)
  468. if connect == True:
  469. dbif.close()
  470. def register_map(self, map, dbif=None):
  471. """ Register a map in the space time dataset.
  472. This method takes care of the registration of a map
  473. in a space time dataset.
  474. In case the map is already registered this function will break with a warning
  475. and return False
  476. @param dbif: The database interface to be used
  477. """
  478. connect = False
  479. if dbif == None:
  480. dbif = sql_database_interface()
  481. dbif.connect()
  482. connect = True
  483. if map.is_in_db(dbif) == False:
  484. dbif.close()
  485. core.fatal(_("Only maps with absolute or relative valid time can be registered"))
  486. core.verbose(_("Register %s map <%s> in space time %s dataset <%s>") % (map.get_type(), map.get_id(), map.get_type(), self.get_id()))
  487. # First select all data from the database
  488. map.select(dbif)
  489. if not map.check_valid_time():
  490. core.fatal(_("Map <%s> has invalid time") % map.get_id())
  491. map_id = map.base.get_id()
  492. map_name = map.base.get_name()
  493. map_mapset = map.base.get_mapset()
  494. map_register_table = map.get_stds_register()
  495. #print "Map register table", map_register_table
  496. # Get basic info
  497. stds_name = self.base.get_name()
  498. stds_mapset = self.base.get_mapset()
  499. stds_register_table = self.get_map_register()
  500. #print "STDS register table", stds_register_table
  501. if stds_mapset != map_mapset:
  502. dbif.close()
  503. core.fatal(_("Only maps from the same mapset can be registered"))
  504. # Check if map is already registred
  505. if stds_register_table:
  506. if dbmi.paramstyle == "qmark":
  507. sql = "SELECT id FROM " + stds_register_table + " WHERE id = (?)"
  508. else:
  509. sql = "SELECT id FROM " + stds_register_table + " WHERE id = (%s)"
  510. dbif.cursor.execute(sql, (map_id,))
  511. row = dbif.cursor.fetchone()
  512. # In case of no entry make a new one
  513. if row and row[0] == map_id:
  514. if connect == True:
  515. dbif.close()
  516. core.warning(_("Map <%s> is already registered.") % (map_id))
  517. return False
  518. # Create tables
  519. sql_path = get_sql_template_path()
  520. # We need to create the stmap raster register table bevor we can register the map
  521. if map_register_table == None:
  522. # Create a unique id
  523. uuid_rand = "map_" + str(uuid.uuid4()).replace("-", "")
  524. map_register_table = uuid_rand + "_" + self.get_type() + "_register"
  525. # Read the SQL template
  526. sql = open(os.path.join(sql_path, "map_stds_register_table_template.sql"), 'r').read()
  527. # Create the raster, raster3d and vector tables
  528. sql = sql.replace("GRASS_MAP", map.get_type())
  529. sql = sql.replace("MAP_NAME", map_name + "_" + map_mapset )
  530. sql = sql.replace("TABLE_NAME", uuid_rand )
  531. sql = sql.replace("MAP_ID", map_id)
  532. sql = sql.replace("STDS", self.get_type())
  533. try:
  534. if dbmi.__name__ == "sqlite3":
  535. dbif.cursor.executescript(sql)
  536. else:
  537. dbif.cursor.execute(sql)
  538. except:
  539. if connect == True:
  540. dbif.close()
  541. core.error(_("Unable to create the space time %s dataset register table for <%s>") % \
  542. (map.get_type(), map.get_id()))
  543. raise
  544. # Set the stds register table name and put it into the DB
  545. map.set_stds_register(map_register_table)
  546. map.metadata.update(dbif)
  547. core.verbose(_("Created register table <%s> for %s map <%s>") % \
  548. (map_register_table, map.get_type(), map.get_id()))
  549. # We need to create the table and register it
  550. if stds_register_table == None:
  551. # Create table name
  552. stds_register_table = stds_name + "_" + stds_mapset + "_" + map.get_type() + "_register"
  553. # Read the SQL template
  554. sql = open(os.path.join(sql_path, "stds_map_register_table_template.sql"), 'r').read()
  555. # Create the raster, raster3d and vector tables
  556. sql = sql.replace("GRASS_MAP", map.get_type())
  557. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  558. sql = sql.replace("SPACETIME_ID", self.base.get_id())
  559. sql = sql.replace("STDS", self.get_type())
  560. sql_script = ""
  561. sql_script += "BEGIN TRANSACTION;\n"
  562. sql_script += sql
  563. sql_script += "\n"
  564. sql_script += "END TRANSACTION;"
  565. try:
  566. if dbmi.__name__ == "sqlite3":
  567. dbif.cursor.executescript(sql_script)
  568. else:
  569. dbif.cursor.execute(sql_script)
  570. dbif.connection.commit()
  571. except:
  572. if connect == True:
  573. dbif.close()
  574. core.error(_("Unable to create the space time %s dataset register table for <%s>") % \
  575. (map.get_type(), map.get_id()))
  576. raise
  577. # Set the map register table name and put it into the DB
  578. self.set_map_register(stds_register_table)
  579. self.metadata.update(dbif)
  580. core.verbose(_("Created register table <%s> for space time %s dataset <%s>") % \
  581. (stds_register_table, map.get_type(), self.get_id()))
  582. # Register the stds in the map stds register table
  583. # Check if the entry is already there
  584. if dbmi.paramstyle == "qmark":
  585. sql = "SELECT id FROM " + map_register_table + " WHERE id = ?"
  586. else:
  587. sql = "SELECT id FROM " + map_register_table + " WHERE id = %s"
  588. dbif.cursor.execute(sql, (self.base.get_id(),))
  589. row = dbif.cursor.fetchone()
  590. # In case of no entry make a new one
  591. if row == None:
  592. if dbmi.paramstyle == "qmark":
  593. sql = "INSERT INTO " + map_register_table + " (id) " + "VALUES (?)"
  594. else:
  595. sql = "INSERT INTO " + map_register_table + " (id) " + "VALUES (%s)"
  596. #print sql
  597. dbif.cursor.execute(sql, (self.base.get_id(),))
  598. # Now put the raster name in the stds map register table
  599. if dbmi.paramstyle == "qmark":
  600. sql = "INSERT INTO " + stds_register_table + " (id) " + "VALUES (?)"
  601. else:
  602. sql = "INSERT INTO " + stds_register_table + " (id) " + "VALUES (%s)"
  603. #print sql
  604. dbif.cursor.execute(sql, (map_id,))
  605. if connect == True:
  606. dbif.close()
  607. return True
  608. def unregister_map(self, map, dbif = None):
  609. """Unregister a map from the space time dataset.
  610. This method takes care of the unregistration of a map
  611. from a space time dataset.
  612. @param map: The map object to unregister
  613. @param dbif: The database interface to be used
  614. """
  615. connect = False
  616. if dbif == None:
  617. dbif = sql_database_interface()
  618. dbif.connect()
  619. connect = True
  620. if map.is_in_db(dbif) == False:
  621. dbif.close()
  622. core.fatal(_("Unable to find map <%s> in temporal database") % (map.get_id()))
  623. core.verbose(_("Unregister %s map <%s>") % (map.get_type(), map.get_id()))
  624. # First select all data from the database
  625. map.select(dbif)
  626. map_id = map.base.get_id()
  627. map_register_table = map.get_stds_register()
  628. stds_register_table = self.get_map_register()
  629. # Check if the map is registered in the space time raster dataset
  630. if dbmi.paramstyle == "qmark":
  631. sql = "SELECT id FROM " + map_register_table + " WHERE id = ?"
  632. else:
  633. sql = "SELECT id FROM " + map_register_table + " WHERE id = %s"
  634. dbif.cursor.execute(sql, (self.base.get_id(),))
  635. row = dbif.cursor.fetchone()
  636. # Break if the map is not registered
  637. if row == None:
  638. core.warning(_("Map <%s> is not registered in space time dataset") %(map_id, self.base.get_id()))
  639. if connect == True:
  640. dbif.close()
  641. return False
  642. # Remove the space time raster dataset from the raster dataset register
  643. if map_register_table != None:
  644. if dbmi.paramstyle == "qmark":
  645. sql = "DELETE FROM " + map_register_table + " WHERE id = ?"
  646. else:
  647. sql = "DELETE FROM " + map_register_table + " WHERE id = %s"
  648. dbif.cursor.execute(sql, (self.base.get_id(),))
  649. # Remove the raster map from the space time raster dataset register
  650. if stds_register_table != None:
  651. if dbmi.paramstyle == "qmark":
  652. sql = "DELETE FROM " + stds_register_table + " WHERE id = ?"
  653. else:
  654. sql = "DELETE FROM " + stds_register_table + " WHERE id = %s"
  655. dbif.cursor.execute(sql, (map_id,))
  656. if connect == True:
  657. dbif.close()
  658. def update_from_registered_maps(self, dbif = None):
  659. """This methods updates the spatial and temporal extent as well as
  660. type specific metadata. It should always been called after maps are registered
  661. or unregistered/deleted from the space time dataset.
  662. The update of the temporal extent checks if the end time is set correctly.
  663. In case the registered maps have no valid end time (None) the maximum start time
  664. will be used. If the end time is earlier than the maximum start time, it will
  665. be replaced by the maximum start time.
  666. An other solution to automate this is to use the diactivated trigger
  667. in the SQL files. But this will result in a huge performance issue
  668. in case many maps are registred (>1000).
  669. @param dbif: The database interface to be used
  670. """
  671. core.verbose(_("Update metadata, spatial and temporal extent from all registered maps of <%s>") % (self.get_id()))
  672. # Nothing to do if the register is not present
  673. if not self.get_map_register():
  674. return
  675. connect = False
  676. if dbif == None:
  677. dbif = sql_database_interface()
  678. dbif.connect()
  679. connect = True
  680. map_time = None
  681. use_start_time = False
  682. # Get basic info
  683. stds_name = self.base.get_name()
  684. stds_mapset = self.base.get_mapset()
  685. sql_path = get_sql_template_path()
  686. #We create a transaction
  687. sql_script = ""
  688. sql_script += "BEGIN TRANSACTION;\n"
  689. # Update the spatial and temporal extent from registered maps
  690. # Read the SQL template
  691. sql = open(os.path.join(sql_path, "update_stds_spatial_temporal_extent_template.sql"), 'r').read()
  692. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(None).get_type())
  693. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  694. sql = sql.replace("SPACETIME_ID", self.base.get_id())
  695. sql = sql.replace("STDS", self.get_type())
  696. sql_script += sql
  697. sql_script += "\n"
  698. # Update type specific metadata
  699. sql = open(os.path.join(sql_path, "update_" + self.get_type() + "_metadata_template.sql"), 'r').read()
  700. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(None).get_type())
  701. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  702. sql = sql.replace("SPACETIME_ID", self.base.get_id())
  703. sql = sql.replace("STDS", self.get_type())
  704. sql_script += sql
  705. sql_script += "\n"
  706. sql_script += "END TRANSACTION;"
  707. if dbmi.__name__ == "sqlite3":
  708. dbif.cursor.executescript(sql_script)
  709. else:
  710. dbif.cursor.execute(sql_script)
  711. # Read and validate the selected end time
  712. self.select()
  713. if self.is_time_absolute():
  714. start_time, end_time, tz = self.get_absolute_time()
  715. else:
  716. start_time, end_time = self.get_relative_time()
  717. # In case no end time is set, use the maximum start time of all registered maps as end time
  718. if end_time == None:
  719. use_start_time = True
  720. else:
  721. # Check if the end time is smaller than the maximum start time
  722. if self.is_time_absolute():
  723. sql = """SELECT max(start_time) FROM GRASS_MAP_absolute_time WHERE GRASS_MAP_absolute_time.id IN
  724. (SELECT id FROM SPACETIME_NAME_GRASS_MAP_register);"""
  725. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(None).get_type())
  726. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  727. else:
  728. sql = """SELECT max(start_time) FROM GRASS_MAP_relative_time WHERE GRASS_MAP_relative_time.id IN
  729. (SELECT id FROM SPACETIME_NAME_GRASS_MAP_register);"""
  730. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(None).get_type())
  731. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  732. dbif.cursor.execute(sql)
  733. row = dbif.cursor.fetchone()
  734. if row != None:
  735. # This seems to be a bug in sqlite3 Python driver
  736. if dbmi.__name__ == "sqlite3":
  737. tstring = row[0]
  738. # Convert the unicode string into the datetime format
  739. if self.is_time_absolute():
  740. if tstring.find(":") > 0:
  741. time_format = "%Y-%m-%d %H:%M:%S"
  742. else:
  743. time_format = "%Y-%m-%d"
  744. max_start_time = datetime.strptime(tstring, time_format)
  745. else:
  746. max_start_time = row[0]
  747. else:
  748. max_start_time = row[0]
  749. if end_time < max_start_time:
  750. use_start_time = True
  751. # Set the maximum start time as end time
  752. if use_start_time:
  753. if self.is_time_absolute():
  754. sql = """UPDATE STDS_absolute_time SET end_time =
  755. (SELECT max(start_time) FROM GRASS_MAP_absolute_time WHERE GRASS_MAP_absolute_time.id IN
  756. (SELECT id FROM SPACETIME_NAME_GRASS_MAP_register)
  757. ) WHERE id = 'SPACETIME_ID';"""
  758. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(None).get_type())
  759. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  760. sql = sql.replace("SPACETIME_ID", self.base.get_id())
  761. sql = sql.replace("STDS", self.get_type())
  762. elif self.is_time_relative():
  763. sql = """UPDATE STDS_relative_time SET end_time =
  764. (SELECT max(start_time) FROM GRASS_MAP_relative_time WHERE GRASS_MAP_relative_time.id IN
  765. (SELECT id FROM SPACETIME_NAME_GRASS_MAP_register)
  766. ) WHERE id = 'SPACETIME_ID';"""
  767. sql = sql.replace("GRASS_MAP", self.get_new_map_instance(None).get_type())
  768. sql = sql.replace("SPACETIME_NAME", stds_name + "_" + stds_mapset )
  769. sql = sql.replace("SPACETIME_ID", self.base.get_id())
  770. sql = sql.replace("STDS", self.get_type())
  771. if dbmi.__name__ == "sqlite3":
  772. dbif.cursor.executescript(sql)
  773. else:
  774. dbif.cursor.execute(sql)
  775. # Count the temporal map types
  776. maps = self.get_registered_maps_as_objects(dbif=dbif)
  777. tlist = self.count_temporal_types(maps)
  778. if tlist["interval"] > 0 and tlist["point"] == 0 and tlist["invalid"] == 0:
  779. map_time = "interval"
  780. elif tlist["interval"] == 0 and tlist["point"] > 0 and tlist["invalid"] == 0:
  781. map_time = "point"
  782. elif tlist["interval"] > 0 and tlist["point"] > 0 and tlist["invalid"] == 0:
  783. map_time = "mixed"
  784. else:
  785. map_time = "invalid"
  786. # Compute the granularity
  787. if map_time != "invalid":
  788. # Smallest supported temporal resolution
  789. if self.is_time_absolute():
  790. gran = compute_absolute_time_granularity(maps)
  791. elif self.is_time_relative():
  792. gran = compute_relative_time_granularity(maps)
  793. else:
  794. gran = None
  795. # Set the map time type and update the time objects
  796. if self.is_time_absolute():
  797. self.absolute_time.select(dbif)
  798. self.metadata.select(dbif)
  799. if self.metadata.get_number_of_maps() > 0:
  800. self.absolute_time.set_map_time(map_time)
  801. self.absolute_time.set_granularity(gran)
  802. else:
  803. self.absolute_time.set_map_time(None)
  804. self.absolute_time.set_granularity(None)
  805. self.absolute_time.update_all(dbif)
  806. else:
  807. self.relative_time.select(dbif)
  808. self.metadata.select(dbif)
  809. if self.metadata.get_number_of_maps() > 0:
  810. self.relative_time.set_map_time(map_time)
  811. self.relative_time.set_granularity(gran)
  812. else:
  813. self.relative_time.set_map_time(None)
  814. self.relative_time.set_granularity(None)
  815. self.relative_time.update_all(dbif)
  816. if connect == True:
  817. dbif.close()