t.rast.accdetect.py 21 KB

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