t.rast.accumulate.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. #!/usr/bin/env python3
  2. ############################################################################
  3. #
  4. # MODULE: t.rast.accumulate
  5. # AUTHOR(S): Soeren Gebbert
  6. #
  7. # PURPOSE: Compute cyclic accumulations of a space time raster dataset
  8. # COPYRIGHT: (C) 2013-2017 by the GRASS Development Team
  9. #
  10. # This program is free software; you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation; either version 2 of the License, or
  13. # (at your option) any later version.
  14. #
  15. # This program is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. #############################################################################
  21. # %module
  22. # % description: Computes cyclic accumulations of a space time raster dataset.
  23. # % keyword: temporal
  24. # % keyword: accumulation
  25. # % keyword: raster
  26. # % keyword: time
  27. # %end
  28. # %option G_OPT_STRDS_INPUT
  29. # %end
  30. # %option G_OPT_STRDS_OUTPUT
  31. # %end
  32. # %option G_OPT_STRDS_INPUT
  33. # % key: lower
  34. # % description: Input space time raster dataset that defines the lower threshold, values lower than this threshold are excluded from accumulation
  35. # % required: no
  36. # %end
  37. # %option G_OPT_STRDS_INPUT
  38. # % key: upper
  39. # % description: Input space time raster dataset that defines the upper threshold, values higher than this threshold are excluded from accumulation
  40. # % required: no
  41. # %end
  42. # %option
  43. # % key: start
  44. # % type: string
  45. # % description: The temporal starting point to begin the accumulation, eg '2001-01-01'
  46. # % required: yes
  47. # % multiple: no
  48. # %end
  49. # %option
  50. # % key: stop
  51. # % type: string
  52. # % description: The temporal date to stop the accumulation, eg '2009-01-01'
  53. # % required: no
  54. # % multiple: no
  55. # %end
  56. # %option
  57. # % key: cycle
  58. # % type: string
  59. # % description: The temporal cycle to restart the accumulation, eg '12 months'
  60. # % required: yes
  61. # % multiple: no
  62. # %end
  63. # %option
  64. # % key: offset
  65. # % type: string
  66. # % description: The temporal offset to the beginning of the next cycle, eg '6 months'
  67. # % required: no
  68. # % multiple: no
  69. # %end
  70. # %option
  71. # % key: granularity
  72. # % type: string
  73. # % description: The granularity for accumulation '1 day'
  74. # % answer: 1 day
  75. # % required: no
  76. # % multiple: no
  77. # %end
  78. # %option
  79. # % key: basename
  80. # % type: string
  81. # % label: Basename of the new generated output maps
  82. # % description: A numerical suffix separated by an underscore will be attached to create a unique identifier
  83. # % required: yes
  84. # % multiple: no
  85. # % gisprompt:
  86. # %end
  87. # %option
  88. # % key: suffix
  89. # % type: string
  90. # % description: Suffix to add to the basename. Set 'gran' for granularity, 'time' for the full time format, 'num' for numerical suffix with a specific number of digits (default %05)
  91. # % answer: gran
  92. # % required: no
  93. # % multiple: no
  94. # %end
  95. # %option
  96. # % key: limits
  97. # % type: double
  98. # % key_desc: lower,upper
  99. # % description: Use these limits in case lower and/or upper input space time raster datasets are not defined or contain NULL values
  100. # % required: yes
  101. # % multiple: no
  102. # %end
  103. # %option
  104. # % key: scale
  105. # % type: double
  106. # % description: Scale factor for input space time raster dataset
  107. # % required: no
  108. # % multiple: no
  109. # %end
  110. # %option
  111. # % key: shift
  112. # % type: double
  113. # % description: Shift factor for input space time raster dataset
  114. # % required: no
  115. # % multiple: no
  116. # %end
  117. # %option
  118. # % key: method
  119. # % type: string
  120. # % label: This method will be applied to compute the accumulative values from the input maps in a single granule
  121. # % description: Growing Degree Days or Winkler indices; Mean: sum(input maps)/(number of input maps); Biologically Effective Degree Days; Huglin Heliothermal index
  122. # % options: mean,gdd,bedd,huglin
  123. # % answer: mean
  124. # % required: no
  125. # % multiple: no
  126. # %end
  127. # %flag
  128. # % key: n
  129. # % description: Register empty maps in the output space time raster dataset, otherwise they will be deleted
  130. # %end
  131. # %flag
  132. # % key: r
  133. # % description: Reverse time direction in cyclic accumulation
  134. # %end
  135. from __future__ import print_function
  136. import grass.script as grass
  137. from copy import copy
  138. ############################################################################
  139. def main():
  140. # lazy imports
  141. import grass.temporal as tgis
  142. from grass.pygrass.modules import Module
  143. # Get the options
  144. input = options["input"]
  145. output = options["output"]
  146. start = options["start"]
  147. stop = options["stop"]
  148. base = options["basename"]
  149. cycle = options["cycle"]
  150. lower = options["lower"]
  151. upper = options["upper"]
  152. offset = options["offset"]
  153. limits = options["limits"]
  154. shift = options["shift"]
  155. scale = options["scale"]
  156. method = options["method"]
  157. granularity = options["granularity"]
  158. register_null = flags["n"]
  159. reverse = flags["r"]
  160. time_suffix = options["suffix"]
  161. # Make sure the temporal database exists
  162. tgis.init()
  163. # We need a database interface
  164. dbif = tgis.SQLDatabaseInterfaceConnection()
  165. dbif.connect()
  166. mapset = tgis.get_current_mapset()
  167. if input.find("@") >= 0:
  168. id = input
  169. else:
  170. id = input + "@" + mapset
  171. input_strds = tgis.SpaceTimeRasterDataset(id)
  172. if not input_strds.is_in_db():
  173. dbif.close()
  174. grass.fatal(_("Space time raster dataset <%s> not found") % (id))
  175. input_strds.select(dbif)
  176. if output.find("@") >= 0:
  177. out_id = output
  178. else:
  179. out_id = output + "@" + mapset
  180. # The output space time raster dataset
  181. output_strds = tgis.SpaceTimeRasterDataset(out_id)
  182. if output_strds.is_in_db(dbif):
  183. if not grass.overwrite():
  184. dbif.close()
  185. grass.fatal(
  186. _(
  187. "Space time raster dataset <%s> is already in the "
  188. "database, use overwrite flag to overwrite"
  189. )
  190. % out_id
  191. )
  192. if (
  193. tgis.check_granularity_string(granularity, input_strds.get_temporal_type())
  194. is False
  195. ):
  196. dbif.close()
  197. grass.fatal(_("Invalid granularity"))
  198. if tgis.check_granularity_string(cycle, input_strds.get_temporal_type()) is False:
  199. dbif.close()
  200. grass.fatal(_("Invalid cycle"))
  201. if offset:
  202. if (
  203. tgis.check_granularity_string(offset, input_strds.get_temporal_type())
  204. is False
  205. ):
  206. dbif.close()
  207. grass.fatal(_("Invalid offset"))
  208. # The lower threshold space time raster dataset
  209. if lower:
  210. if not range:
  211. dbif.close()
  212. grass.fatal(
  213. _(
  214. "You need to set the range to compute the occurrence"
  215. " space time raster dataset"
  216. )
  217. )
  218. if lower.find("@") >= 0:
  219. lower_id = lower
  220. else:
  221. lower_id = lower + "@" + mapset
  222. lower_strds = tgis.SpaceTimeRasterDataset(lower_id)
  223. if not lower_strds.is_in_db():
  224. dbif.close()
  225. grass.fatal(
  226. _("Space time raster dataset <%s> not found") % (lower_strds.get_id())
  227. )
  228. if lower_strds.get_temporal_type() != input_strds.get_temporal_type():
  229. dbif.close()
  230. grass.fatal(_("Temporal type of input strds and lower strds must be equal"))
  231. lower_strds.select(dbif)
  232. # The upper threshold space time raster dataset
  233. if upper:
  234. if not lower:
  235. dbif.close()
  236. grass.fatal(
  237. _("The upper option works only in conjunction with the lower option")
  238. )
  239. if upper.find("@") >= 0:
  240. upper = upper
  241. else:
  242. upper_id = upper + "@" + mapset
  243. upper_strds = tgis.SpaceTimeRasterDataset(upper_id)
  244. if not upper_strds.is_in_db():
  245. dbif.close()
  246. grass.fatal(
  247. _("Space time raster dataset <%s> not found") % (upper_strds.get_id())
  248. )
  249. if upper_strds.get_temporal_type() != input_strds.get_temporal_type():
  250. dbif.close()
  251. grass.fatal(_("Temporal type of input strds and upper strds must be equal"))
  252. upper_strds.select(dbif)
  253. input_strds_start, input_strds_end = input_strds.get_temporal_extent_as_tuple()
  254. if input_strds.is_time_absolute():
  255. start = tgis.string_to_datetime(start)
  256. if stop:
  257. stop = tgis.string_to_datetime(stop)
  258. else:
  259. stop = input_strds_end
  260. start = tgis.adjust_datetime_to_granularity(start, granularity)
  261. else:
  262. start = int(start)
  263. if stop:
  264. stop = int(stop)
  265. else:
  266. stop = input_strds_end
  267. if input_strds.is_time_absolute():
  268. end = tgis.increment_datetime_by_string(start, cycle)
  269. else:
  270. end = start + cycle
  271. limit_relations = ["EQUALS", "DURING", "OVERLAPS", "OVERLAPPING", "CONTAINS"]
  272. count = 1
  273. output_maps = []
  274. while input_strds_end > start and stop > start:
  275. # Make sure that the cyclic computation will stop at the correct time
  276. if stop and end > stop:
  277. end = stop
  278. where = "start_time >= '%s' AND start_time < '%s'" % (str(start), str(end))
  279. input_maps = input_strds.get_registered_maps_as_objects(where=where, dbif=dbif)
  280. grass.message(_("Processing cycle %s - %s" % (str(start), str(end))))
  281. if len(input_maps) == 0:
  282. continue
  283. # Lets create a dummy list of maps with granularity conform intervals
  284. gran_list = []
  285. gran_list_low = []
  286. gran_list_up = []
  287. gran_start = start
  288. while gran_start < end:
  289. map = input_strds.get_new_map_instance("%i@%i" % (count, count))
  290. if input_strds.is_time_absolute():
  291. gran_end = tgis.increment_datetime_by_string(gran_start, granularity)
  292. map.set_absolute_time(gran_start, gran_end)
  293. gran_start = tgis.increment_datetime_by_string(gran_start, granularity)
  294. else:
  295. gran_end = gran_start + granularity
  296. map.set_relative_time(
  297. gran_start, gran_end, input_strds.get_relative_time_unit()
  298. )
  299. gran_start = gran_start + granularity
  300. gran_list.append(copy(map))
  301. gran_list_low.append(copy(map))
  302. gran_list_up.append(copy(map))
  303. # Lists to compute the topology with upper and lower datasets
  304. # Create the topology between the granularity conform list and all maps
  305. # of the current cycle
  306. gran_topo = tgis.SpatioTemporalTopologyBuilder()
  307. gran_topo.build(gran_list, input_maps)
  308. if lower:
  309. lower_maps = lower_strds.get_registered_maps_as_objects(dbif=dbif)
  310. gran_lower_topo = tgis.SpatioTemporalTopologyBuilder()
  311. gran_lower_topo.build(gran_list_low, lower_maps)
  312. if upper:
  313. upper_maps = upper_strds.get_registered_maps_as_objects(dbif=dbif)
  314. gran_upper_topo = tgis.SpatioTemporalTopologyBuilder()
  315. gran_upper_topo.build(gran_list_up, upper_maps)
  316. old_map_name = None
  317. # Aggregate
  318. num_maps = len(gran_list)
  319. for i in range(num_maps):
  320. if reverse:
  321. map = gran_list[num_maps - i - 1]
  322. else:
  323. map = gran_list[i]
  324. # Select input maps based on temporal topology relations
  325. input_maps = []
  326. if map.get_equal():
  327. input_maps += map.get_equal()
  328. elif map.get_contains():
  329. input_maps += map.get_contains()
  330. elif map.get_overlaps():
  331. input_maps += map.get_overlaps()
  332. elif map.get_overlapped():
  333. input_maps += map.get_overlapped()
  334. elif map.get_during():
  335. input_maps += map.get_during()
  336. # Check input maps
  337. if len(input_maps) == 0:
  338. continue
  339. # New output map
  340. if input_strds.get_temporal_type() == "absolute" and time_suffix == "gran":
  341. suffix = tgis.create_suffix_from_datetime(
  342. map.temporal_extent.get_start_time(), input_strds.get_granularity()
  343. )
  344. output_map_name = "{ba}_{su}".format(ba=base, su=suffix)
  345. elif (
  346. input_strds.get_temporal_type() == "absolute" and time_suffix == "time"
  347. ):
  348. suffix = tgis.create_time_suffix(map)
  349. output_map_name = "{ba}_{su}".format(ba=base, su=suffix)
  350. else:
  351. output_map_name = tgis.create_numeric_suffix(base, count, time_suffix)
  352. output_map_id = map.build_id(output_map_name, mapset)
  353. output_map = input_strds.get_new_map_instance(output_map_id)
  354. # Check if new map is in the temporal database
  355. if output_map.is_in_db(dbif):
  356. if grass.overwrite():
  357. # Remove the existing temporal database entry
  358. output_map.delete(dbif)
  359. output_map = input_strds.get_new_map_instance(output_map_id)
  360. else:
  361. grass.fatal(
  362. _(
  363. "Map <%s> is already registered in the temporal"
  364. " database, use overwrite flag to overwrite."
  365. )
  366. % (output_map.get_map_id())
  367. )
  368. map_start, map_end = map.get_temporal_extent_as_tuple()
  369. if map.is_time_absolute():
  370. output_map.set_absolute_time(map_start, map_end)
  371. else:
  372. output_map.set_relative_time(
  373. map_start, map_end, map.get_relative_time_unit()
  374. )
  375. limits_vals = limits.split(",")
  376. limits_lower = float(limits_vals[0])
  377. limits_upper = float(limits_vals[1])
  378. lower_map_name = None
  379. if lower:
  380. relations = gran_list_low[i].get_temporal_relations()
  381. for relation in limit_relations:
  382. if relation in relations:
  383. lower_map_name = str(relations[relation][0].get_id())
  384. break
  385. upper_map_name = None
  386. if upper:
  387. relations = gran_list_up[i].get_temporal_relations()
  388. for relation in limit_relations:
  389. if relation in relations:
  390. upper_map_name = str(relations[relation][0].get_id())
  391. break
  392. input_map_names = []
  393. for input_map in input_maps:
  394. input_map_names.append(input_map.get_id())
  395. # Set up the module
  396. accmod = Module(
  397. "r.series.accumulate",
  398. input=input_map_names,
  399. output=output_map_name,
  400. run_=False,
  401. )
  402. if old_map_name:
  403. accmod.inputs["basemap"].value = old_map_name
  404. if lower_map_name:
  405. accmod.inputs["lower"].value = lower_map_name
  406. if upper_map_name:
  407. accmod.inputs["upper"].value = upper_map_name
  408. accmod.inputs["limits"].value = (limits_lower, limits_upper)
  409. if shift:
  410. accmod.inputs["shift"].value = float(shift)
  411. if scale:
  412. accmod.inputs["scale"].value = float(scale)
  413. if method:
  414. accmod.inputs["method"].value = method
  415. print(accmod)
  416. accmod.run()
  417. if accmod.returncode != 0:
  418. dbif.close()
  419. grass.fatal(_("Error running r.series.accumulate"))
  420. output_maps.append(output_map)
  421. old_map_name = output_map_name
  422. count += 1
  423. # Increment the cycle
  424. start = end
  425. if input_strds.is_time_absolute():
  426. start = end
  427. if offset:
  428. start = tgis.increment_datetime_by_string(end, offset)
  429. end = tgis.increment_datetime_by_string(start, cycle)
  430. else:
  431. if offset:
  432. start = end + offset
  433. end = start + cycle
  434. # Insert the maps into the output space time dataset
  435. if output_strds.is_in_db(dbif):
  436. if grass.overwrite():
  437. output_strds.delete(dbif)
  438. output_strds = input_strds.get_new_instance(out_id)
  439. temporal_type, semantic_type, title, description = input_strds.get_initial_values()
  440. output_strds.set_initial_values(temporal_type, semantic_type, title, description)
  441. output_strds.insert(dbif)
  442. empty_maps = []
  443. # Register the maps in the database
  444. count = 0
  445. for output_map in output_maps:
  446. count += 1
  447. if count % 10 == 0:
  448. grass.percent(count, len(output_maps), 1)
  449. # Read the raster map data
  450. output_map.load()
  451. # In case of a empty map continue, do not register empty maps
  452. if not register_null:
  453. if (
  454. output_map.metadata.get_min() is None
  455. and output_map.metadata.get_max() is None
  456. ):
  457. empty_maps.append(output_map)
  458. continue
  459. # Insert map in temporal database
  460. output_map.insert(dbif)
  461. output_strds.register_map(output_map, dbif)
  462. # Update the spatio-temporal extent and the metadata table entries
  463. output_strds.update_from_registered_maps(dbif)
  464. grass.percent(1, 1, 1)
  465. dbif.close()
  466. # Remove empty maps
  467. if len(empty_maps) > 0:
  468. for map in empty_maps:
  469. grass.run_command(
  470. "g.remove", flags="f", type="raster", name=map.get_name(), quiet=True
  471. )
  472. if __name__ == "__main__":
  473. options, flags = grass.parser()
  474. main()