t.rast.accdetect.py 21 KB

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