t.rast.what.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. ############################################################################
  4. #
  5. # MODULE: t.rast.what
  6. # AUTHOR(S): Soeren Gebbert
  7. #
  8. # PURPOSE: Sample a space time raster dataset at specific vector point
  9. # coordinates and write the output to stdout using different
  10. # layouts
  11. # COPYRIGHT: (C) 2015-2017 by the GRASS Development Team
  12. #
  13. # This program is free software; you can redistribute it and/or modify
  14. # it under the terms of the GNU General Public License as published by
  15. # the Free Software Foundation; either version 2 of the License, or
  16. # (at your option) any later version.
  17. #
  18. # This program is distributed in the hope that it will be useful,
  19. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. # GNU General Public License for more details.
  22. #
  23. #############################################################################
  24. #%module
  25. #% description: Sample a space time raster dataset at specific vector point coordinates and write the output to stdout using different layouts
  26. #% keyword: temporal
  27. #% keyword: sampling
  28. #% keyword: raster
  29. #% keyword: time
  30. #%end
  31. #%option G_OPT_V_INPUT
  32. #% key: points
  33. #% required: no
  34. #%end
  35. #%option G_OPT_M_COORDS
  36. #% required: no
  37. #% description: Comma separated list of coordinates
  38. #%end
  39. #%option G_OPT_STRDS_INPUT
  40. #% key: strds
  41. #%end
  42. #%option G_OPT_F_OUTPUT
  43. #% required: no
  44. #% description: Name for the output file or "-" in case stdout should be used
  45. #% answer: -
  46. #%end
  47. #%option G_OPT_T_WHERE
  48. #%end
  49. #%option G_OPT_M_NULL_VALUE
  50. #%end
  51. #%option G_OPT_F_SEP
  52. #%end
  53. #%option
  54. #% key: order
  55. #% type: string
  56. #% description: Sort the maps by category
  57. #% required: no
  58. #% multiple: yes
  59. #% options: id, name, creator, mapset, creation_time, modification_time, start_time, end_time, north, south, west, east, min, max
  60. #% answer: start_time
  61. #%end
  62. #%option
  63. #% key: layout
  64. #% type: string
  65. #% description: The layout of the output. One point per row (row), one point per column (col), all timsteps in one row (timerow)
  66. #% required: no
  67. #% multiple: no
  68. #% options: row, col, timerow
  69. #% answer: row
  70. #%end
  71. #%option
  72. #% key: nprocs
  73. #% type: integer
  74. #% description: Number of r.what processes to run in parallel
  75. #% required: no
  76. #% multiple: no
  77. #% answer: 1
  78. #%end
  79. #%flag
  80. #% key: n
  81. #% description: Output header row
  82. #%end
  83. #%flag
  84. #% key: i
  85. #% description: Use stdin as input and ignore coordinates and point option
  86. #%end
  87. ## Temporary disabled the r.what flags due to test issues
  88. ##%flag
  89. ##% key: f
  90. ##% description: Show the category labels of the grid cell(s)
  91. ##%end
  92. ##%flag
  93. ##% key: r
  94. ##% description: Output color values as RRR:GGG:BBB
  95. ##%end
  96. ##%flag
  97. ##% key: i
  98. ##% description: Output integer category values, not cell values
  99. ##%end
  100. #%flag
  101. #% key: v
  102. #% description: Show the category for vector points map
  103. #%end
  104. import sys
  105. import copy
  106. import grass.script as gscript
  107. import pdb
  108. ############################################################################
  109. def main(options, flags):
  110. # lazy imports
  111. import grass.temporal as tgis
  112. import grass.pygrass.modules as pymod
  113. # Get the options
  114. points = options["points"]
  115. coordinates = options["coordinates"]
  116. strds = options["strds"]
  117. output = options["output"]
  118. where = options["where"]
  119. order = options["order"]
  120. layout = options["layout"]
  121. null_value = options["null_value"]
  122. separator = gscript.separator(options["separator"])
  123. nprocs = int(options["nprocs"])
  124. write_header = flags["n"]
  125. use_stdin = flags["i"]
  126. vcat = flags["v"]
  127. #output_cat_label = flags["f"]
  128. #output_color = flags["r"]
  129. #output_cat = flags["i"]
  130. overwrite = gscript.overwrite()
  131. if coordinates and points:
  132. gscript.fatal(_("Options coordinates and points are mutually exclusive"))
  133. if not coordinates and not points and not use_stdin:
  134. gscript.fatal(_("Please specify the coordinates, the points option or use the 'i' flag to pipe coordinate positions to t.rast.what from stdin, to provide the sampling coordinates"))
  135. if vcat and not points:
  136. gscript.fatal(_("Flag 'v' required option 'points'"))
  137. if use_stdin:
  138. coordinates_stdin = str(sys.__stdin__.read())
  139. # Check if coordinates are given with site names or IDs
  140. stdin_length = len(coordinates_stdin.split('\n')[0].split())
  141. if stdin_length <= 2:
  142. site_input = False
  143. elif stdin_length >= 3:
  144. site_input = True
  145. else:
  146. site_input = False
  147. # Make sure the temporal database exists
  148. tgis.init()
  149. # We need a database interface
  150. dbif = tgis.SQLDatabaseInterfaceConnection()
  151. dbif.connect()
  152. sp = tgis.open_old_stds(strds, "strds", dbif)
  153. maps = sp.get_registered_maps_as_objects(where=where, order=order,
  154. dbif=dbif)
  155. dbif.close()
  156. if not maps:
  157. gscript.fatal(_("Space time raster dataset <%s> is empty") % sp.get_id())
  158. # Setup flags are disabled due to test issues
  159. flags = ""
  160. #if output_cat_label is True:
  161. # flags += "f"
  162. #if output_color is True:
  163. # flags += "r"
  164. #if output_cat is True:
  165. # flags += "i"
  166. if vcat is True:
  167. flags += "v"
  168. # Configure the r.what module
  169. if points:
  170. r_what = pymod.Module("r.what", map="dummy",
  171. output="dummy", run_=False,
  172. separator=separator, points=points,
  173. overwrite=overwrite, flags=flags,
  174. null_value=null_value,
  175. quiet=True)
  176. elif coordinates:
  177. # Create a list of values
  178. coord_list = coordinates.split(",")
  179. r_what = pymod.Module("r.what", map="dummy",
  180. output="dummy", run_=False,
  181. separator=separator,
  182. coordinates=coord_list,
  183. overwrite=overwrite, flags=flags,
  184. null_value=null_value,
  185. quiet=True)
  186. elif use_stdin:
  187. r_what = pymod.Module("r.what", map="dummy",
  188. output="dummy", run_=False,
  189. separator=separator,
  190. stdin_=coordinates_stdin,
  191. overwrite=overwrite, flags=flags,
  192. null_value=null_value,
  193. quiet=True)
  194. else:
  195. gscript.error(_("Please specify points or coordinates"))
  196. if len(maps) < nprocs:
  197. nprocs = len(maps)
  198. # The module queue for parallel execution
  199. process_queue = pymod.ParallelModuleQueue(int(nprocs))
  200. num_maps = len(maps)
  201. # 400 Maps is the absolute maximum in r.what
  202. # We need to determie the number of maps that can be processed
  203. # in parallel
  204. # First estimate the number of maps per process. We use 400 maps
  205. # simultaniously as maximum for a single process
  206. num_loops = int(num_maps / (400 * nprocs))
  207. remaining_maps = num_maps % (400 * nprocs)
  208. if num_loops == 0:
  209. num_loops = 1
  210. remaining_maps = 0
  211. # Compute the number of maps for each process
  212. maps_per_loop = int((num_maps - remaining_maps) / num_loops)
  213. maps_per_process = int(maps_per_loop / nprocs)
  214. remaining_maps_per_loop = maps_per_loop % nprocs
  215. # We put the output files in an ordered list
  216. output_files = []
  217. output_time_list = []
  218. count = 0
  219. for loop in range(num_loops):
  220. file_name = gscript.tempfile() + "_%i"%(loop)
  221. count = process_loop(nprocs, maps, file_name, count, maps_per_process,
  222. remaining_maps_per_loop, output_files,
  223. output_time_list, r_what, process_queue)
  224. process_queue.wait()
  225. gscript.verbose("Number of raster map layers remaining for sampling %i"%(remaining_maps))
  226. if remaining_maps > 0:
  227. # Use a single process if less then 100 maps
  228. if remaining_maps <= 100:
  229. map_names = []
  230. for i in range(remaining_maps):
  231. map = maps[count]
  232. map_names.append(map.get_id())
  233. count += 1
  234. mod = copy.deepcopy(r_what)
  235. mod(map=map_names, output=file_name)
  236. process_queue.put(mod)
  237. else:
  238. maps_per_process = int(remaining_maps / nprocs)
  239. remaining_maps_per_loop = remaining_maps % nprocs
  240. file_name = "out_remain"
  241. process_loop(nprocs, maps, file_name, count, maps_per_process,
  242. remaining_maps_per_loop, output_files,
  243. output_time_list, r_what, process_queue)
  244. # Wait for unfinished processes
  245. process_queue.wait()
  246. # Out the output files in the correct order together
  247. if layout == "row":
  248. one_point_per_row_output(separator, output_files, output_time_list,
  249. output, write_header, site_input, vcat)
  250. elif layout == "col":
  251. one_point_per_col_output(separator, output_files, output_time_list,
  252. output, write_header, site_input, vcat)
  253. else:
  254. one_point_per_timerow_output(separator, output_files, output_time_list,
  255. output, write_header, site_input, vcat)
  256. ############################################################################
  257. def one_point_per_row_output(separator, output_files, output_time_list,
  258. output, write_header, site_input, vcat):
  259. """Write one point per row
  260. output is of type: x,y,start,end,value
  261. """
  262. # open the output file for writing
  263. out_file = open(output, 'w') if output != "-" else sys.stdout
  264. if write_header is True:
  265. out_str = ""
  266. if vcat:
  267. out_str += "cat{sep}"
  268. if site_input:
  269. out_str += "x{sep}y{sep}site{sep}start{sep}end{sep}value\n"
  270. else:
  271. out_str += "x{sep}y{sep}start{sep}end{sep}value\n"
  272. out_file.write(out_str.format(sep=separator))
  273. for count in range(len(output_files)):
  274. file_name = output_files[count]
  275. gscript.verbose(_("Transforming r.what output file %s"%(file_name)))
  276. map_list = output_time_list[count]
  277. in_file = open(file_name, "r")
  278. for line in in_file:
  279. line = line.split(separator)
  280. if vcat:
  281. cat = line[0]
  282. x = line[1]
  283. y = line[2]
  284. values = line[4:]
  285. if site_input:
  286. site = line[3]
  287. values = line[5:]
  288. else:
  289. x = line[0]
  290. y = line[1]
  291. if site_input:
  292. site = line[2]
  293. values = line[3:]
  294. for i in range(len(values)):
  295. start, end = map_list[i].get_temporal_extent_as_tuple()
  296. if vcat:
  297. cat_str = "{ca}{sep}".format(ca=cat, sep=separator)
  298. else:
  299. cat_str = ""
  300. if site_input:
  301. coor_string = "%(x)10.10f%(sep)s%(y)10.10f%(sep)s%(site_name)s%(sep)s"\
  302. %({"x":float(x),"y":float(y),"site_name":str(site),"sep":separator})
  303. else:
  304. coor_string = "%(x)10.10f%(sep)s%(y)10.10f%(sep)s"\
  305. %({"x":float(x),"y":float(y),"sep":separator})
  306. time_string = "%(start)s%(sep)s%(end)s%(sep)s%(val)s\n"\
  307. %({"start":str(start), "end":str(end),
  308. "val":(values[i].strip()),"sep":separator})
  309. out_file.write(cat_str + coor_string + time_string)
  310. in_file.close()
  311. if out_file is not sys.stdout:
  312. out_file.close()
  313. ############################################################################
  314. def one_point_per_col_output(separator, output_files, output_time_list,
  315. output, write_header, site_input, vcat):
  316. """Write one point per col
  317. output is of type:
  318. start,end,point_1 value,point_2 value,...,point_n value
  319. Each row represents a single raster map, hence a single time stamp
  320. """
  321. # open the output file for writing
  322. out_file = open(output, 'w') if output != "-" else sys.stdout
  323. first = True
  324. for count in range(len(output_files)):
  325. file_name = output_files[count]
  326. gscript.verbose(_("Transforming r.what output file %s"%(file_name)))
  327. map_list = output_time_list[count]
  328. in_file = open(file_name, "r")
  329. lines = in_file.readlines()
  330. matrix = []
  331. for line in lines:
  332. matrix.append(line.split(separator))
  333. num_cols = len(matrix[0])
  334. if first is True:
  335. if write_header is True:
  336. out_str = "start%(sep)send"%({"sep":separator})
  337. # Define different separator for coordinates and sites
  338. if separator == ',':
  339. coor_sep = ';'
  340. else:
  341. coor_sep = ','
  342. for row in matrix:
  343. if vcat:
  344. cat = row[0]
  345. x = row[1]
  346. y = row[2]
  347. out_str += "{sep}{cat}{csep}{x:10.10f}{csep}" \
  348. "{y:10.10f}".format(cat=cat, x=float(x),
  349. y=float(y),
  350. sep=separator,
  351. csep=coor_sep)
  352. if site_input:
  353. site = row[3]
  354. out_str += "{sep}{site}".format(sep=coor_sep,
  355. site=site)
  356. else:
  357. x = row[0]
  358. y = row[1]
  359. out_str += "{sep}{x:10.10f}{csep}" \
  360. "{y:10.10f}".format(x=float(x), y=float(y),
  361. sep=separator,
  362. csep=coor_sep)
  363. if site_input:
  364. site = row[2]
  365. out_str += "{sep}{site}".format(sep=coor_sep,
  366. site=site)
  367. out_file.write(out_str + "\n")
  368. first = False
  369. if vcat:
  370. ncol = 4
  371. else:
  372. ncol = 3
  373. for col in range(num_cols - ncol):
  374. start, end = output_time_list[count][col].get_temporal_extent_as_tuple()
  375. time_string = "%(start)s%(sep)s%(end)s"\
  376. %({"start":str(start), "end":str(end),
  377. "sep":separator})
  378. out_file.write(time_string)
  379. for row in range(len(matrix)):
  380. value = matrix[row][col + ncol]
  381. out_file.write("%(sep)s%(value)s"\
  382. %({"sep":separator,
  383. "value":value.strip()}))
  384. out_file.write("\n")
  385. in_file.close()
  386. if out_file is not sys.stdout:
  387. out_file.close()
  388. ############################################################################
  389. def one_point_per_timerow_output(separator, output_files, output_time_list,
  390. output, write_header, site_input, vcat):
  391. """Use the original layout of the r.what output and print instead of
  392. the raster names, the time stamps as header
  393. One point per line for all time stamps:
  394. x|y|1991-01-01 00:00:00;1991-01-02 00:00:00|1991-01-02 00:00:00;1991-01-03 00:00:00|1991-01-03 00:00:00;1991-01-04 00:00:00|1991-01-04 00:00:00;1991-01-05 00:00:00
  395. 3730731.49590371|5642483.51236521|6|8|7|7
  396. 3581249.04638104|5634411.97526282|5|8|7|7
  397. """
  398. out_file = open(output, 'w') if output != "-" else sys.stdout
  399. matrix = []
  400. header = ""
  401. first = True
  402. for count in range(len(output_files)):
  403. file_name = output_files[count]
  404. gscript.verbose("Transforming r.what output file %s"%(file_name))
  405. map_list = output_time_list[count]
  406. in_file = open(file_name, "r")
  407. if write_header:
  408. if first is True:
  409. if vcat:
  410. header = "cat{sep}".format(sep=separator)
  411. else:
  412. header = ""
  413. if site_input:
  414. header += "x%(sep)sy%(sep)ssite"%({"sep":separator})
  415. else:
  416. header += "x%(sep)sy"%({"sep":separator})
  417. for map in map_list:
  418. start, end = map.get_temporal_extent_as_tuple()
  419. time_string = "%(sep)s%(start)s;%(end)s"\
  420. %({"start":str(start), "end":str(end),
  421. "sep":separator})
  422. header += time_string
  423. lines = in_file.readlines()
  424. for i in range(len(lines)):
  425. cols = lines[i].split(separator)
  426. if first is True:
  427. if vcat and site_input:
  428. matrix.append(cols[:4])
  429. elif vcat or site_input:
  430. matrix.append(cols[:3])
  431. else:
  432. matrix.append(cols[:2])
  433. if vcat:
  434. matrix[i] = matrix[i] + cols[4:]
  435. else:
  436. matrix[i] = matrix[i] + cols[3:]
  437. first = False
  438. in_file.close()
  439. if write_header:
  440. out_file.write(header + "\n")
  441. gscript.verbose(_("Writing the output file <%s>"%(output)))
  442. for row in matrix:
  443. first = True
  444. for col in row:
  445. value = col.strip()
  446. if first is False:
  447. out_file.write("%s"%(separator))
  448. out_file.write(value)
  449. first = False
  450. out_file.write("\n")
  451. if out_file is not sys.stdout:
  452. out_file.close()
  453. ############################################################################
  454. def process_loop(nprocs, maps, file_name, count, maps_per_process,
  455. remaining_maps_per_loop, output_files,
  456. output_time_list, r_what, process_queue):
  457. """Call r.what in parallel subprocesses"""
  458. first = True
  459. for process in range(nprocs):
  460. num = maps_per_process
  461. # Add the remaining maps to the first process
  462. if first is True:
  463. num += remaining_maps_per_loop
  464. first = False
  465. # Temporary output file
  466. final_file_name = file_name + "_%i"%(process)
  467. output_files.append(final_file_name)
  468. map_names = []
  469. map_list = []
  470. for i in range(num):
  471. map = maps[count]
  472. map_names.append(map.get_id())
  473. map_list.append(map)
  474. count += 1
  475. output_time_list.append(map_list)
  476. gscript.verbose(_("Process maps %(samp_start)i to %(samp_end)i (of %(total)i)"\
  477. %({"samp_start":count-len(map_names)+1,
  478. "samp_end":count, "total":len(maps)})))
  479. mod = copy.deepcopy(r_what)
  480. mod(map=map_names, output=final_file_name)
  481. #print(mod.get_bash())
  482. process_queue.put(mod)
  483. return count
  484. ############################################################################
  485. if __name__ == "__main__":
  486. options, flags = gscript.parser()
  487. main(options, flags)