t.rast.accdetect.py 21 KB

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