t.rast.accdetect.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. ############################################################################
  4. #
  5. # MODULE: t.rast.accdetect
  6. # AUTHOR(S): Soeren Gebbert
  7. #
  8. # PURPOSE: Detect accumulation pattern in temporally accumulated space time raster datasets created by t.rast.accumulate.
  9. # COPYRIGHT: (C) 2013-2017 by the GRASS Development Team
  10. #
  11. # This program is free software; you can redistribute it and/or modify
  12. # it under the terms of the GNU General Public License as published by
  13. # the Free Software Foundation; either version 2 of the License, or
  14. # (at your option) any later version.
  15. #
  16. # This program is distributed in the hope that it will be useful,
  17. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. # GNU General Public License for more details.
  20. #
  21. #############################################################################
  22. #%module
  23. #% description: Detects accumulation patterns in temporally accumulated space time raster datasets created by t.rast.accumulate.
  24. #% keyword: temporal
  25. #% keyword: accumulation
  26. #% keyword: raster
  27. #% keyword: time
  28. #%end
  29. #%option G_OPT_STRDS_INPUT
  30. #%end
  31. #%option G_OPT_STRDS_INPUT
  32. #% key: minimum
  33. #% description: Input space time raster dataset that specifies the minimum values to detect the accumulation pattern
  34. #% required: no
  35. #%end
  36. #%option G_OPT_STRDS_INPUT
  37. #% key: maximum
  38. #% description: Input space time raster dataset that specifies the maximum values to detect the accumulation pattern
  39. #% required: no
  40. #%end
  41. #%option G_OPT_STRDS_OUTPUT
  42. #% key: occurrence
  43. #% description: The output space time raster dataset that stores the occurrence of the the accumulation pattern using the provided data range
  44. #% required: yes
  45. #%end
  46. #%option G_OPT_STRDS_OUTPUT
  47. #% key: indicator
  48. #% description: The output space time raster dataset that stores the indication of the start, intermediate and end of the specified data range
  49. #% required: no
  50. #%end
  51. #%option
  52. #% key: start
  53. #% type: string
  54. #% description: The temporal starting point to begin the accumulation, eg '2001-01-01'
  55. #% required: yes
  56. #% multiple: no
  57. #%end
  58. #%option
  59. #% key: stop
  60. #% type: string
  61. #% description: The temporal date to stop the accumulation, eg '2009-01-01'
  62. #% required: no
  63. #% multiple: no
  64. #%end
  65. #%option
  66. #% key: cycle
  67. #% type: string
  68. #% description: The temporal cycle to restart the accumulation, eg '12 months'
  69. #% required: yes
  70. #% multiple: no
  71. #%end
  72. #%option
  73. #% key: offset
  74. #% type: string
  75. #% description: The temporal offset to the begin of the next cycle, eg '6 months'
  76. #% required: no
  77. #% multiple: no
  78. #%end
  79. #%option
  80. #% key: basename
  81. #% type: string
  82. #% label: Basename of the new generated output maps
  83. #% description: A numerical suffix separated by an underscore will be attached to create a unique identifier
  84. #% required: yes
  85. #% multiple: no
  86. #% gisprompt:
  87. #%end
  88. #%option
  89. #% key: suffix
  90. #% type: string
  91. #% description: Suffix to add at basename: set 'gran' for granularity, 'time' for the full time format, 'count' for numerical suffix with a specific number of digits (default %05)
  92. #% answer: gran
  93. #% required: no
  94. #% multiple: no
  95. #%end
  96. #%option
  97. #% key: range
  98. #% type: double
  99. #% key_desc: min,max
  100. #% description: The minimum and maximum value of the occurrence of accumulated values, these values will be used if the min/max space time raster datasets are not specified
  101. #% required: no
  102. #% multiple: no
  103. #%end
  104. #%option
  105. #% key: staend
  106. #% type: integer
  107. #% key_desc: start,intermediate,end
  108. #% description: The user defined values that indicate start, intermediate and end status in the indicator output space time raster dataset
  109. #% answer: 1,2,3
  110. #% required: no
  111. #% multiple: no
  112. #%end
  113. #%flag
  114. #% key: n
  115. #% description: Register empty maps in the output space time raster dataset, otherwise they will be deleted
  116. #%end
  117. #%flag
  118. #% key: r
  119. #% description: Reverse time direction in cyclic accumulation
  120. #%end
  121. import grass.script as grass
  122. # lazy imports at the end of the file
  123. ############################################################################
  124. range_relations = ["EQUALS", "DURING", "OVERLAPS", "OVERLAPPING", "CONTAINS"]
  125. def main():
  126. # Get the options
  127. input = options["input"]
  128. start = options["start"]
  129. stop = options["stop"]
  130. base = options["basename"]
  131. cycle = options["cycle"]
  132. offset = options["offset"]
  133. minimum = options["minimum"]
  134. maximum = options["maximum"]
  135. occurrence = options["occurrence"]
  136. range_ = options["range"]
  137. indicator = options["indicator"]
  138. staend = options["staend"]
  139. register_null = flags["n"]
  140. reverse = flags["r"]
  141. time_suffix = options["suffix"]
  142. grass.set_raise_on_error(True)
  143. # Make sure the temporal database exists
  144. tgis.init()
  145. # We need a database interface
  146. dbif = tgis.SQLDatabaseInterfaceConnection()
  147. dbif.connect()
  148. mapset = tgis.get_current_mapset()
  149. if input.find("@") >= 0:
  150. id = input
  151. else:
  152. id = input + "@" + mapset
  153. input_strds = tgis.SpaceTimeRasterDataset(id)
  154. if input_strds.is_in_db() == False:
  155. dbif.close()
  156. grass.fatal(_("Space time %s dataset <%s> not found") % (
  157. input_strds.get_output_map_instance(None).get_type(), id))
  158. input_strds.select(dbif)
  159. dummy = input_strds.get_new_map_instance(None)
  160. # The occurrence space time raster dataset
  161. if occurrence:
  162. if not minimum or not maximum:
  163. if not range_:
  164. dbif.close()
  165. grass.fatal(_("You need to set the range to compute the occurrence"
  166. " space time raster dataset"))
  167. if occurrence.find("@") >= 0:
  168. occurrence_id = occurrence
  169. else:
  170. occurrence_id = occurrence + "@" + mapset
  171. occurrence_strds = tgis.SpaceTimeRasterDataset(occurrence_id)
  172. if occurrence_strds.is_in_db(dbif):
  173. if not grass.overwrite():
  174. dbif.close()
  175. grass.fatal(_("Space time raster dataset <%s> is already in the "
  176. "database, use overwrite flag to overwrite") % occurrence_id)
  177. # The indicator space time raster dataset
  178. if indicator:
  179. if not occurrence:
  180. dbif.close()
  181. grass.fatal(_("You need to set the occurrence to compute the indicator"
  182. " space time raster dataset"))
  183. if not staend:
  184. dbif.close()
  185. grass.fatal(_("You need to set the staend options to compute the indicator"
  186. " space time raster dataset"))
  187. if indicator.find("@") >= 0:
  188. indicator = indicator
  189. else:
  190. indicator_id = indicator + "@" + mapset
  191. indicator_strds = tgis.SpaceTimeRasterDataset(indicator_id)
  192. if indicator_strds.is_in_db(dbif):
  193. if not grass.overwrite():
  194. dbif.close()
  195. grass.fatal(_("Space time raster dataset <%s> is already in the "
  196. "database, use overwrite flag to overwrite") % indicator_id)
  197. staend = staend.split(",")
  198. indicator_start = int(staend[0])
  199. indicator_mid = int(staend[1])
  200. indicator_end = int(staend[2])
  201. # The minimum threshold space time raster dataset
  202. minimum_strds = None
  203. if minimum:
  204. if minimum.find("@") >= 0:
  205. minimum_id = minimum
  206. else:
  207. minimum_id = minimum + "@" + mapset
  208. minimum_strds = tgis.SpaceTimeRasterDataset(minimum_id)
  209. if minimum_strds.is_in_db() == False:
  210. dbif.close()
  211. grass.fatal(_("Space time raster dataset <%s> not found") % (minimum_strds.get_id()))
  212. if minimum_strds.get_temporal_type() != input_strds.get_temporal_type():
  213. dbif.close()
  214. grass.fatal(_("Temporal type of input strds and minimum strds must be equal"))
  215. minimum_strds.select(dbif)
  216. # The maximum threshold space time raster dataset
  217. maximum_strds = None
  218. if maximum:
  219. if maximum.find("@") >= 0:
  220. maximum_id = maximum
  221. else:
  222. maximum_id = maximum + "@" + mapset
  223. maximum_strds = tgis.SpaceTimeRasterDataset(maximum_id)
  224. if maximum_strds.is_in_db() == False:
  225. dbif.close()
  226. grass.fatal(_("Space time raster dataset <%s> not found") % (maximum_strds.get_id()))
  227. if maximum_strds.get_temporal_type() != input_strds.get_temporal_type():
  228. dbif.close()
  229. grass.fatal(_("Temporal type of input strds and maximum strds must be equal"))
  230. maximum_strds.select(dbif)
  231. input_strds_start, input_strds_end = input_strds.get_temporal_extent_as_tuple()
  232. if input_strds.is_time_absolute():
  233. start = tgis.string_to_datetime(start)
  234. if stop:
  235. stop = tgis.string_to_datetime(stop)
  236. else:
  237. stop = input_strds_end
  238. else:
  239. start = int(start)
  240. if stop:
  241. stop = int(stop)
  242. else:
  243. stop = input_strds_end
  244. if input_strds.is_time_absolute():
  245. end = tgis.increment_datetime_by_string(start, cycle)
  246. else:
  247. end = start + cycle
  248. count = 1
  249. indi_count = 1
  250. occurrence_maps = {}
  251. indicator_maps = {}
  252. while input_strds_end > start and stop > start:
  253. # Make sure that the cyclic computation will stop at the correct time
  254. if stop and end > stop:
  255. end = stop
  256. where = "start_time >= \'%s\' AND start_time < \'%s\'"%(str(start),
  257. str(end))
  258. input_maps = input_strds.get_registered_maps_as_objects(where=where,
  259. dbif=dbif)
  260. grass.debug(len(input_maps))
  261. input_topo = tgis.SpatioTemporalTopologyBuilder()
  262. input_topo.build(input_maps, input_maps)
  263. if len(input_maps) == 0:
  264. continue
  265. grass.message(_("Processing cycle %s - %s"%(str(start), str(end))))
  266. count = compute_occurrence(occurrence_maps, input_strds, input_maps,
  267. start, base, count, time_suffix, mapset,
  268. where, reverse, range_, minimum_strds,
  269. maximum_strds, dbif)
  270. # Indicator computation is based on the occurrence so we need to start it after
  271. # the occurrence cycle
  272. if indicator:
  273. num_maps = len(input_maps)
  274. for i in range(num_maps):
  275. if reverse:
  276. map = input_maps[num_maps - i - 1]
  277. else:
  278. map = input_maps[i]
  279. if input_strds.get_temporal_type() == 'absolute' and time_suffix == 'gran':
  280. suffix = tgis.create_suffix_from_datetime(map.temporal_extent.get_start_time(),
  281. input_strds.get_granularity())
  282. indicator_map_name = "{ba}_indicator_{su}".format(ba=base, su=suffix)
  283. elif input_strds.get_temporal_type() == 'absolute' and time_suffix == 'time':
  284. suffix = tgis.create_time_suffix(map)
  285. indicator_map_name = "{ba}_indicator_{su}".format(ba=base, su=suffix)
  286. else:
  287. indicator_map_name = tgis.create_numeric_suffix(base + "_indicator",
  288. indi_count, time_suffix)
  289. indicator_map_id = dummy.build_id(indicator_map_name, mapset)
  290. indicator_map = input_strds.get_new_map_instance(indicator_map_id)
  291. # Check if new map is in the temporal database
  292. if indicator_map.is_in_db(dbif):
  293. if grass.overwrite():
  294. # Remove the existing temporal database entry
  295. indicator_map.delete(dbif)
  296. indicator_map = input_strds.get_new_map_instance(indicator_map_id)
  297. else:
  298. grass.fatal(_("Map <%s> is already registered in the temporal"
  299. " database, use overwrite flag to overwrite.") %
  300. (indicator_map.get_map_id()))
  301. curr_map = occurrence_maps[map.get_id()].get_name()
  302. # Reverse time
  303. if reverse:
  304. if i == 0:
  305. prev_map = curr_map
  306. subexpr1 = "null()"
  307. subexpr3 = "%i"%(indicator_start)
  308. elif i > 0 and i < num_maps - 1:
  309. prev_map = occurrence_maps[map.next().get_id()].get_name()
  310. next_map = occurrence_maps[map.prev().get_id()].get_name()
  311. # In case the previous map is null() set null() or the start indicator
  312. subexpr1 = "if(isnull(%s), null(), %i)"%(curr_map, indicator_start)
  313. # In case the previous map was not null() if the current map is null() set null()
  314. # if the current map is not null() and the next map is not null() set
  315. # intermediate indicator, if the next map is null set the end indicator
  316. subexpr2 = "if(isnull(%s), %i, %i)"%(next_map, indicator_end, indicator_mid)
  317. subexpr3 = "if(isnull(%s), null(), %s)"%(curr_map, subexpr2)
  318. expression = "%s = if(isnull(%s), %s, %s)"%(indicator_map_name,
  319. prev_map, subexpr1,
  320. subexpr3)
  321. else:
  322. prev_map = occurrence_maps[map.next().get_id()].get_name()
  323. subexpr1 = "if(isnull(%s), null(), %i)"%(curr_map, indicator_start)
  324. subexpr3 = "if(isnull(%s), null(), %i)"%(curr_map, indicator_mid)
  325. else:
  326. if i == 0:
  327. prev_map = curr_map
  328. subexpr1 = "null()"
  329. subexpr3 = "%i"%(indicator_start)
  330. elif i > 0 and i < num_maps - 1:
  331. prev_map = occurrence_maps[map.prev().get_id()].get_name()
  332. next_map = occurrence_maps[map.next().get_id()].get_name()
  333. # In case the previous map is null() set null() or the start indicator
  334. subexpr1 = "if(isnull(%s), null(), %i)"%(curr_map, indicator_start)
  335. # In case the previous map was not null() if the current map is null() set null()
  336. # if the current map is not null() and the next map is not null() set
  337. # intermediate indicator, if the next map is null set the end indicator
  338. subexpr2 = "if(isnull(%s), %i, %i)"%(next_map, indicator_end, indicator_mid)
  339. subexpr3 = "if(isnull(%s), null(), %s)"%(curr_map, subexpr2)
  340. expression = "%s = if(isnull(%s), %s, %s)"%(indicator_map_name,
  341. prev_map, subexpr1,
  342. subexpr3)
  343. else:
  344. prev_map = occurrence_maps[map.prev().get_id()].get_name()
  345. subexpr1 = "if(isnull(%s), null(), %i)"%(curr_map, indicator_start)
  346. subexpr3 = "if(isnull(%s), null(), %i)"%(curr_map, indicator_mid)
  347. expression = "%s = if(isnull(%s), %s, %s)"%(indicator_map_name,
  348. prev_map, subexpr1,
  349. subexpr3)
  350. grass.debug(expression)
  351. grass.mapcalc(expression, overwrite=True)
  352. map_start, map_end = map.get_temporal_extent_as_tuple()
  353. if map.is_time_absolute():
  354. indicator_map.set_absolute_time(map_start, map_end)
  355. else:
  356. indicator_map.set_relative_time(map_start, map_end,
  357. map.get_relative_time_unit())
  358. indicator_maps[map.get_id()] = indicator_map
  359. indi_count += 1
  360. # Increment the cycle
  361. start = end
  362. if input_strds.is_time_absolute():
  363. start = end
  364. if offset:
  365. start = tgis.increment_datetime_by_string(end, offset)
  366. end = tgis.increment_datetime_by_string(start, cycle)
  367. else:
  368. if offset:
  369. start = end + offset
  370. end = start + cycle
  371. empty_maps = []
  372. create_strds_register_maps(input_strds, occurrence_strds, occurrence_maps,
  373. register_null, empty_maps, dbif)
  374. if indicator:
  375. create_strds_register_maps(input_strds, indicator_strds, indicator_maps,
  376. register_null, empty_maps, dbif)
  377. dbif.close()
  378. # Remove empty maps
  379. if len(empty_maps) > 0:
  380. for map in empty_maps:
  381. grass.run_command("g.remove", flags='f', type="raster", name=map.get_name(), quiet=True)
  382. ############################################################################
  383. def create_strds_register_maps(in_strds, out_strds, out_maps, register_null,
  384. empty_maps, dbif):
  385. out_id = out_strds.get_id()
  386. if out_strds.is_in_db(dbif):
  387. if grass.overwrite():
  388. out_strds.delete(dbif)
  389. out_strds = in_strds.get_new_instance(out_id)
  390. temporal_type, semantic_type, title, description = in_strds.get_initial_values()
  391. out_strds.set_initial_values(temporal_type, semantic_type, title,
  392. description)
  393. out_strds.insert(dbif)
  394. # Register the maps in the database
  395. count = 0
  396. for map in out_maps.values():
  397. count += 1
  398. if count%10 == 0:
  399. grass.percent(count, len(out_maps), 1)
  400. # Read the raster map data
  401. map.load()
  402. # In case of a empty map continue, do not register empty maps
  403. if not register_null:
  404. if map.metadata.get_min() is None and \
  405. map.metadata.get_max() is None:
  406. empty_maps.append(map)
  407. continue
  408. # Insert map in temporal database
  409. map.insert(dbif)
  410. out_strds.register_map(map, dbif)
  411. out_strds.update_from_registered_maps(dbif)
  412. grass.percent(1, 1, 1)
  413. ############################################################################
  414. def compute_occurrence(occurrence_maps, input_strds, input_maps, start, base,
  415. count, tsuffix, mapset, where, reverse, range_,
  416. minimum_strds, maximum_strds, dbif):
  417. if minimum_strds:
  418. input_maps_minimum = input_strds.get_registered_maps_as_objects(where=where,
  419. dbif=dbif)
  420. minimum_maps = minimum_strds.get_registered_maps_as_objects(dbif=dbif)
  421. minimum_topo = tgis.SpatioTemporalTopologyBuilder()
  422. minimum_topo.build(input_maps_minimum, minimum_maps)
  423. if maximum_strds:
  424. input_maps_maximum = input_strds.get_registered_maps_as_objects(where=where,
  425. dbif=dbif)
  426. maximum_maps = maximum_strds.get_registered_maps_as_objects(dbif=dbif)
  427. maximum_topo = tgis.SpatioTemporalTopologyBuilder()
  428. maximum_topo.build(input_maps_maximum, maximum_maps)
  429. # Aggregate
  430. num_maps = len(input_maps)
  431. for i in range(num_maps):
  432. if reverse:
  433. map = input_maps[num_maps - i - 1]
  434. else:
  435. map = input_maps[i]
  436. # Compute the days since start
  437. input_start, input_end = map.get_temporal_extent_as_tuple()
  438. td = input_start - start
  439. if map.is_time_absolute():
  440. days = tgis.time_delta_to_relative_time(td)
  441. else:
  442. days = td
  443. if input_strds.get_temporal_type() == 'absolute' and tsuffix == 'gran':
  444. suffix = tgis.create_suffix_from_datetime(map.temporal_extent.get_start_time(),
  445. input_strds.get_granularity())
  446. occurrence_map_name = "{ba}_{su}".format(ba=base, su=suffix)
  447. elif input_strds.get_temporal_type() == 'absolute' and tsuffix == 'time':
  448. suffix = tgis.create_time_suffix(map)
  449. occurrence_map_name = "{ba}_{su}".format(ba=base, su=suffix)
  450. else:
  451. occurrence_map_name = tgis.create_numeric_suffix(base, count, tsuffix)
  452. occurrence_map_id = map.build_id(occurrence_map_name, mapset)
  453. occurrence_map = input_strds.get_new_map_instance(occurrence_map_id)
  454. # Check if new map is in the temporal database
  455. if occurrence_map.is_in_db(dbif):
  456. if grass.overwrite():
  457. # Remove the existing temporal database entry
  458. occurrence_map.delete(dbif)
  459. occurrence_map = input_strds.get_new_map_instance(occurrence_map_id)
  460. else:
  461. grass.fatal(_("Map <%s> is already registered in the temporal"
  462. " database, use overwrite flag to overwrite.") %
  463. (occurrence_map.get_map_id()))
  464. range_vals = range_.split(",")
  465. min = range_vals[0]
  466. max = range_vals[1]
  467. if minimum_strds:
  468. relations = input_maps_minimum[i].get_temporal_relations()
  469. for relation in range_relations:
  470. if relation in relations:
  471. min = str(relations[relation][0].get_id())
  472. break
  473. if maximum_strds:
  474. relations = input_maps_maximum[i].get_temporal_relations()
  475. for relation in range_relations:
  476. if relation in relations:
  477. max = str(relations[relation][0].get_id())
  478. break
  479. expression = "%s = if(%s > %s && %s < %s, %s, null())"%(occurrence_map_name,
  480. map.get_name(),
  481. min, map.get_name(),
  482. max, days)
  483. grass.debug(expression)
  484. grass.mapcalc(expression, overwrite=True)
  485. map_start, map_end = map.get_temporal_extent_as_tuple()
  486. if map.is_time_absolute():
  487. occurrence_map.set_absolute_time(map_start, map_end)
  488. else:
  489. occurrence_map.set_relative_time(map_start, map_end,
  490. map.get_relative_time_unit())
  491. # Store the new maps
  492. occurrence_maps[map.get_id()] = occurrence_map
  493. count += 1
  494. return count
  495. ############################################################################
  496. if __name__ == "__main__":
  497. options, flags = grass.parser()
  498. # lazy imports
  499. import grass.temporal as tgis
  500. main()