abstract_space_time_dataset.py 44 KB

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