space_time_datasets_tools.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. """!@package grass.temporal
  2. @brief GRASS Python scripting module (temporal GIS functions)
  3. Temporal GIS related functions to be used in Python scripts.
  4. Usage:
  5. @code
  6. import grass.temporal as tgis
  7. tgis.register_maps_in_space_time_dataset(type, name, maps)
  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 space_time_datasets import *
  17. ###############################################################################
  18. def register_maps_in_space_time_dataset(type, name, maps=None, file=None, start=None, \
  19. end=None, unit=None, increment=None, dbif = None, \
  20. interval=False, fs="|"):
  21. """Use this method to register maps in space time datasets. This function is generic and
  22. Additionally a start time string and an increment string can be specified
  23. to assign a time interval automatically to the maps.
  24. It takes care of the correct update of the space time datasets from all
  25. registered maps.
  26. @param type: The type of the maps rast, rast3d or vect
  27. @param name: The name of the space time dataset
  28. @param maps: A comma separated list of map names
  29. @param file: Input file one map with start and optional end time, one per line
  30. @param start: The start date and time of the first raster map (format absolute: "yyyy-mm-dd HH:MM:SS" or "yyyy-mm-dd", format relative is integer 5)
  31. @param end: The end date and time of the first raster map (format absolute: "yyyy-mm-dd HH:MM:SS" or "yyyy-mm-dd", format relative is integer 5)
  32. @param unit: The unit of the relative time: years, months, days, hours, minutes, seconds
  33. @param increment: Time increment between maps for time stamp creation (format absolute: NNN seconds, minutes, hours, days, weeks, months, years; format relative: 1.0)
  34. @param dbif: The database interface to be used
  35. @param interval: If True, time intervals are created in case the start time and an increment is provided
  36. @param fs: Field separator used in input file
  37. """
  38. start_time_in_file = False
  39. end_time_in_file = False
  40. if maps and file:
  41. core.fatal(_("%s= and %s= are mutually exclusive") % ("input","file"))
  42. if end and increment:
  43. core.fatal(_("%s= and %s= are mutually exclusive") % ("end","increment"))
  44. if end and not start:
  45. core.fatal(_("Please specify %s= and %s=") % ("start_time","end_time"))
  46. if not maps and not file:
  47. core.fatal(_("Please specify %s= or %s=") % ("input","file"))
  48. # We may need the mapset
  49. mapset = core.gisenv()["MAPSET"]
  50. # The name of the space time dataset is optional
  51. if name:
  52. # Check if the dataset name contains the mapset as well
  53. if name.find("@") < 0:
  54. id = name + "@" + mapset
  55. else:
  56. id = name
  57. if type == "rast":
  58. sp = dataset_factory("strds", id)
  59. elif type == "rast3d":
  60. sp = dataset_factory("str3ds", id)
  61. elif type == "vect":
  62. sp = dataset_factory("stvds", id)
  63. else:
  64. core.fatal(_("Unkown map type: %s")%(type))
  65. connect = False
  66. if dbif == None:
  67. dbif = sql_database_interface()
  68. dbif.connect()
  69. connect = True
  70. if name:
  71. # Read content from temporal database
  72. sp.select(dbif)
  73. if sp.is_in_db(dbif) == False:
  74. dbif.close()
  75. core.fatal(_("Space time %s dataset <%s> no found") % (sp.get_new_map_instance(None).get_type(), name))
  76. if sp.is_time_relative() and not unit:
  77. dbif.close()
  78. core.fatal(_("Space time %s dataset <%s> with relative time found, but no relative unit set for %s maps") % (sp.get_new_map_instance(None).get_type(), name, sp.get_new_map_instance(None).get_type()))
  79. # We need a dummy map object to build the map ids
  80. dummy = dataset_factory(type, None)
  81. maplist = []
  82. # Map names as comma separated string
  83. if maps:
  84. if maps.find(",") < 0:
  85. maplist = [maps,]
  86. else:
  87. maplist = maps.split(",")
  88. # Build the maplist again with the ids
  89. for count in range(len(maplist)):
  90. row = {}
  91. mapid = dummy.build_id(maplist[count], mapset, None)
  92. row["id"] = mapid
  93. maplist[count] = row
  94. # Read the map list from file
  95. if file:
  96. fd = open(file, "r")
  97. line = True
  98. while True:
  99. line = fd.readline()
  100. if not line:
  101. break
  102. line_list = line.split(fs)
  103. # Detect start and end time
  104. if len(line_list) == 2:
  105. start_time_in_file = True
  106. end_time_in_file = False
  107. elif len(line_list) == 3:
  108. start_time_in_file = True
  109. end_time_in_file = True
  110. else:
  111. start_time_in_file = False
  112. end_time_in_file = False
  113. mapname = line_list[0].strip()
  114. row = {}
  115. if start_time_in_file and end_time_in_file:
  116. row["start"] = line_list[1].strip()
  117. row["end"] = line_list[2].strip()
  118. if start_time_in_file and not end_time_in_file:
  119. row["start"] = line_list[1].strip()
  120. row["id"] = dummy.build_id(mapname, mapset)
  121. maplist.append(row)
  122. num_maps = len(maplist)
  123. for count in range(len(maplist)):
  124. core.percent(count, num_maps, 1)
  125. # Get a new instance of the space time dataset map type
  126. map = dataset_factory(type, maplist[count]["id"])
  127. # Use the time data from file
  128. if maplist[count].has_key("start"):
  129. start = maplist[count]["start"]
  130. if maplist[count].has_key("end"):
  131. end = maplist[count]["end"]
  132. # Put the map into the database
  133. if map.is_in_db(dbif) == False:
  134. # Break in case no valid time is provided
  135. if start == "" or start == None:
  136. dbif.close()
  137. if map.get_layer():
  138. core.fatal(_("Unable to register %s map <%s> with layer %s. The map has no valid time and the start time is not set.") % \
  139. (map.get_type(), map.get_map_id(), map.get_layer() ))
  140. else:
  141. core.fatal(_("Unable to register %s map <%s>. The map has no valid time and the start time is not set.") % \
  142. (map.get_type(), map.get_map_id() ))
  143. # Load the data from the grass file database
  144. map.load()
  145. # We need to check the temporal type based on the time stamp
  146. if unit:
  147. map.set_time_to_relative()
  148. else:
  149. map.set_time_to_absolute()
  150. # Put it into the temporal database
  151. map.insert(dbif)
  152. else:
  153. map.select(dbif)
  154. if name and map.get_temporal_type() != sp.get_temporal_type():
  155. dbif.close()
  156. if map.get_layer():
  157. core.fatal(_("Unable to register %s map <%s> with layer. The temporal types are different.") % \
  158. (map.get_type(), map.get_map_id(), map.get_layer()))
  159. core.fatal(_("Unable to register %s map <%s>. The temporal types are different.") % \
  160. (map.get_type(), map.get_map_id()))
  161. # In case the time is in the input file we ignore the increment counter
  162. if start_time_in_file:
  163. count = 1
  164. # Set the valid time
  165. if start:
  166. assign_valid_time_to_map(ttype=map.get_temporal_type(), map=map, start=start, end=end, unit=unit, increment=increment, mult=count, dbif=dbif, interval=interval)
  167. # Finally Register map in the space time dataset
  168. if name:
  169. sp.register_map(map, dbif)
  170. # Update the space time tables
  171. if name:
  172. sp.update_from_registered_maps(dbif)
  173. if connect == True:
  174. dbif.close()
  175. core.percent(num_maps, num_maps, 1)
  176. ###############################################################################
  177. def assign_valid_time_to_map(ttype, map, start, end, unit, increment=None, mult=1, dbif = None, interval=False):
  178. """Assign the valid time to a map dataset
  179. @param ttype: The temporal type which should be assigned and which the time format is of
  180. @param map: A map dataset object derived from abstract_map_dataset
  181. @param start: The start date and time of the first raster map (format absolute: "yyyy-mm-dd HH:MM:SS" or "yyyy-mm-dd", format relative is integer 5)
  182. @param end: The end date and time of the first raster map (format absolute: "yyyy-mm-dd HH:MM:SS" or "yyyy-mm-dd", format relative is integer 5)
  183. @param unit: The unit of the relative time: years, months, days, hours, minutes, seconds
  184. @param increment: Time increment between maps for time stamp creation (format absolute: NNN seconds, minutes, hours, days, weeks, months, years; format relative is integer 1)
  185. @param multi: A multiplier for the increment
  186. @param dbif: The database interface to use for sql queries
  187. @param interval: If True, time intervals are created in case the start time and an increment is provided
  188. """
  189. connect = False
  190. if dbif == None:
  191. dbif = sql_database_interface()
  192. dbif.connect()
  193. connect = True
  194. if ttype == "absolute":
  195. start_time = string_to_datetime(start)
  196. if start_time == None:
  197. dbif.close()
  198. core.fatal(_("Unable to convert string \"%s\"into a datetime object")%(start))
  199. end_time = None
  200. if end:
  201. end_time = string_to_datetime(end)
  202. if end_time == None:
  203. dbif.close()
  204. core.fatal(_("Unable to convert string \"%s\"into a datetime object")%(end))
  205. # Add the increment
  206. if increment:
  207. start_time = increment_datetime_by_string(start_time, increment, mult)
  208. if interval:
  209. end_time = increment_datetime_by_string(start_time, increment, 1)
  210. if map.get_layer():
  211. core.verbose(_("Set absolute valid time for map <%s> with layer %s to %s - %s") % (map.get_map_id(), map.get_layer(), str(start_time), str(end_time)))
  212. else:
  213. core.verbose(_("Set absolute valid time for map <%s> to %s - %s") % (map.get_map_id(), str(start_time), str(end_time)))
  214. map.update_absolute_time(start_time, end_time, None, dbif)
  215. else:
  216. start_time = int(start)
  217. end_time = None
  218. if end:
  219. end_time = int(end)
  220. if increment:
  221. start_time = start_time + mult * int(increment)
  222. if interval:
  223. end_time = start_time + int(increment)
  224. if map.get_layer():
  225. core.verbose(_("Set relative valid time for map <%s> with layer %s to %i - %s with unit %s") % (map.get_map_id(), map.get_layer(), start_time, str(end_time), unit))
  226. else:
  227. core.verbose(_("Set relative valid time for map <%s> to %i - %s with unit %s") % (map.get_map_id(), start_time, str(end_time), unit))
  228. map.update_relative_time(start_time, end_time, unit, dbif)
  229. if connect == True:
  230. dbif.close()
  231. ###############################################################################
  232. def dataset_factory(type, id):
  233. """A factory functions to create space time or map datasets
  234. @param type: the dataset type: rast, rast3d, vect, strds, str3ds, stvds
  235. @param id: The id of the dataset ("name@mapset")
  236. """
  237. if type == "strds":
  238. sp = space_time_raster_dataset(id)
  239. elif type == "str3ds":
  240. sp = space_time_raster3d_dataset(id)
  241. elif type == "stvds":
  242. sp = space_time_vector_dataset(id)
  243. elif type == "rast":
  244. sp = raster_dataset(id)
  245. elif type == "rast3d":
  246. sp = raster3d_dataset(id)
  247. elif type == "vect":
  248. sp = vector_dataset(id)
  249. else:
  250. core.error(_("Unknown dataset type: %s") % type)
  251. return None
  252. return sp
  253. ###############################################################################
  254. def list_maps_of_stds(type, input, columns, order, where, separator, method, header):
  255. """ List the maps of a space time dataset using diffetent methods
  256. @param type: The type of the maps raster, raster3d or vector
  257. @param input: Name of a space time raster dataset
  258. @param columns: A comma separated list of columns to be printed to stdout
  259. @param order: A comma separated list of columns to order the space time dataset by category
  260. @param where: A where statement for selected listing without "WHERE" e.g: start_time < "2001-01-01" and end_time > "2001-01-01"
  261. @param separator: The field separator character between the columns
  262. @param method: String identifier to select a method out of cols,comma,delta or deltagaps
  263. * "cols": Print preselected columns specified by columns
  264. * "comma": Print the map ids (name@mapset) as comma separated string
  265. * "delta": Print the map ids (name@mapset) with start time, end time, relative length of intervals and the relative distance to the begin
  266. * "deltagaps": Same as "delta" with additional listing of gaps. Gaps can be simply identified as the id is "None"
  267. * "gran": List map using the granularity of the space time dataset, columns are identical to deltagaps
  268. @param header: Set True to print column names
  269. """
  270. mapset = core.gisenv()["MAPSET"]
  271. if input.find("@") >= 0:
  272. id = input
  273. else:
  274. id = input + "@" + mapset
  275. sp = dataset_factory(type, id)
  276. if sp.is_in_db() == False:
  277. core.fatal(_("Dataset <%s> not found in temporal database") % (id))
  278. sp.select()
  279. if separator == None or separator == "":
  280. separator = "\t"
  281. # This method expects a list of objects for gap detection
  282. if method == "delta" or method == "deltagaps" or method == "gran":
  283. if type == "stvds":
  284. columns = "id,name,layer,mapset,start_time,end_time"
  285. else:
  286. columns = "id,name,mapset,start_time,end_time"
  287. if method == "deltagaps":
  288. maps = sp.get_registered_maps_as_objects_with_gaps(where, None)
  289. elif method == "delta":
  290. maps = sp.get_registered_maps_as_objects(where, "start_time", None)
  291. elif method == "gran":
  292. maps = sp.get_registered_maps_as_objects_by_granularity(None)
  293. if header:
  294. string = ""
  295. string += "%s%s" % ("id", separator)
  296. string += "%s%s" % ("name", separator)
  297. if type == "stvds":
  298. string += "%s%s" % ("layer", separator)
  299. string += "%s%s" % ("mapset", separator)
  300. string += "%s%s" % ("start_time", separator)
  301. string += "%s%s" % ("end_time", separator)
  302. string += "%s%s" % ("interval_length", separator)
  303. string += "%s" % ("distance_from_begin")
  304. print string
  305. if maps and len(maps) > 0:
  306. if isinstance(maps[0], list):
  307. first_time, dummy = maps[0][0].get_valid_time()
  308. else:
  309. first_time, dummy = maps[0].get_valid_time()
  310. for mymap in maps:
  311. if isinstance(mymap, list):
  312. map = mymap[0]
  313. else:
  314. map = mymap
  315. start, end = map.get_valid_time()
  316. if end:
  317. delta = end -start
  318. else:
  319. delta = None
  320. delta_first = start - first_time
  321. if map.is_time_absolute():
  322. if end:
  323. delta = time_delta_to_relative_time(delta)
  324. delta_first = time_delta_to_relative_time(delta_first)
  325. string = ""
  326. string += "%s%s" % (map.get_id(), separator)
  327. string += "%s%s" % (map.get_name(), separator)
  328. if type == "stvds":
  329. string += "%s%s" % (map.get_layer(), separator)
  330. string += "%s%s" % (map.get_mapset(), separator)
  331. string += "%s%s" % (start, separator)
  332. string += "%s%s" % (end, separator)
  333. string += "%s%s" % (delta, separator)
  334. string += "%s" % (delta_first)
  335. print string
  336. else:
  337. # In comma separated mode only map ids are needed
  338. if method == "comma":
  339. columns = "id"
  340. rows = sp.get_registered_maps(columns, where, order, None)
  341. if rows:
  342. if method == "comma":
  343. string = ""
  344. count = 0
  345. for row in rows:
  346. if count == 0:
  347. string += row["id"]
  348. else:
  349. string += ",%s" % row["id"]
  350. count += 1
  351. print string
  352. elif method == "cols":
  353. # Print the column names if requested
  354. if header:
  355. output = ""
  356. count = 0
  357. collist = columns.split(",")
  358. for key in collist:
  359. if count > 0:
  360. output += separator + str(key)
  361. else:
  362. output += str(key)
  363. count += 1
  364. print output
  365. for row in rows:
  366. output = ""
  367. count = 0
  368. for col in row:
  369. if count > 0:
  370. output += separator + str(col)
  371. else:
  372. output += str(col)
  373. count += 1
  374. print output
  375. ###############################################################################
  376. def sample_stds_by_stds_topology(intype, sampletype, inputs, sampler, header, separator, method, spatial=False):
  377. """ Sample the input space time datasets with a sample space time dataset and print the result to stdout
  378. In case multiple maps are located in the current granule, the map names are separated by comma.
  379. In case a layer is present, the names map ids are extended in this form: name:layer@mapset
  380. Attention: Do not use the comma as separator
  381. @param intype: Type of the input space time dataset (strds, stvds or str3ds)
  382. @param samtype: Type of the sample space time dataset (strds, stvds or str3ds)
  383. @param input: Name of a space time dataset
  384. @param sampler: Name of a space time dataset used for temporal sampling
  385. @param header: Set True to print column names
  386. @param separator: The field separator character between the columns
  387. @param method: The method to be used for temporal sampling (start,during,contain,overlap,equal)
  388. @param spatial: Perform spatial overlapping check
  389. """
  390. mapset = core.gisenv()["MAPSET"]
  391. input_list = inputs.split(",")
  392. sts = []
  393. for input in input_list:
  394. if input.find("@") >= 0:
  395. id = input
  396. else:
  397. id = input + "@" + mapset
  398. st = dataset_factory(intype, id)
  399. sts.append(st)
  400. if sampler.find("@") >= 0:
  401. sid = sampler
  402. else:
  403. sid = sampler + "@" + mapset
  404. sst = dataset_factory(sampletype, sid)
  405. dbif = sql_database_interface()
  406. dbif.connect()
  407. for st in sts:
  408. if st.is_in_db(dbif) == False:
  409. core.fatal(_("Dataset <%s> not found in temporal database") % (id))
  410. st.select(dbif)
  411. if sst.is_in_db(dbif) == False:
  412. core.fatal(_("Dataset <%s> not found in temporal database") % (sid))
  413. sst.select(dbif)
  414. if separator == None or separator == "" or separator.find(",") >= 0:
  415. separator = " | "
  416. mapmatrizes = []
  417. for st in sts:
  418. mapmatrix = st.sample_by_dataset(sst, method, spatial, dbif)
  419. if mapmatrix and len(mapmatrix) > 0:
  420. mapmatrizes.append(mapmatrix)
  421. if len(mapmatrizes) > 0:
  422. if header:
  423. string = ""
  424. string += "%s%s" % (sst.get_id(), separator)
  425. for st in sts:
  426. string += "%s%s" % (st.get_id(), separator)
  427. string += "%s%s" % ("start_time", separator)
  428. string += "%s%s" % ("end_time", separator)
  429. string += "%s%s" % ("interval_length", separator)
  430. string += "%s" % ("distance_from_begin")
  431. print string
  432. first_time, dummy = mapmatrizes[0][0]["granule"].get_valid_time()
  433. for i in range(len(mapmatrizes[0])):
  434. mapname_list = []
  435. for mapmatrix in mapmatrizes:
  436. mapnames = ""
  437. count = 0
  438. entry = mapmatrix[i]
  439. for sample in entry["samples"]:
  440. if count == 0:
  441. mapnames += str(sample.get_id())
  442. else:
  443. mapnames += ",%s" % str(sample.get_id())
  444. count += 1
  445. mapname_list.append(mapnames)
  446. entry = mapmatrizes[0][i]
  447. map = entry["granule"]
  448. start, end = map.get_valid_time()
  449. if end:
  450. delta = end - start
  451. else:
  452. delta = None
  453. delta_first = start - first_time
  454. if map.is_time_absolute():
  455. if end:
  456. delta = time_delta_to_relative_time(delta)
  457. delta_first = time_delta_to_relative_time(delta_first)
  458. string = ""
  459. string += "%s%s" % (map.get_id(), separator)
  460. for mapnames in mapname_list:
  461. string += "%s%s" % (mapnames, separator)
  462. string += "%s%s" % (start, separator)
  463. string += "%s%s" % (end, separator)
  464. string += "%s%s" % (delta, separator)
  465. string += "%s" % (delta_first)
  466. print string
  467. dbif.close()