v.report.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. #!/usr/bin/env python3
  2. #
  3. ############################################################################
  4. #
  5. # MODULE: v.report
  6. # AUTHOR(S): Markus Neteler, converted to Python by Glynn Clements
  7. # Bug fixes, sort for coor by Huidae Cho <grass4u gmail.com>
  8. # PURPOSE: Reports geometry statistics for vector maps
  9. # COPYRIGHT: (C) 2005-2021 by MN and the GRASS Development Team
  10. #
  11. # This program is free software under the GNU General Public
  12. # License (>=v2). Read the file COPYING that comes with GRASS
  13. # for details.
  14. #
  15. #############################################################################
  16. # %module
  17. # % description: Reports geometry statistics for vector maps.
  18. # % keyword: vector
  19. # % keyword: geometry
  20. # % keyword: statistics
  21. # %end
  22. # %option G_OPT_V_MAP
  23. # %end
  24. # %option G_OPT_V_FIELD
  25. # % guisection: Selection
  26. # %end
  27. # %option
  28. # % key: option
  29. # % type: string
  30. # % description: Value to calculate
  31. # % options: area,length,coor
  32. # % required: yes
  33. # %end
  34. # %option G_OPT_M_UNITS
  35. # % options: miles,feet,meters,kilometers,acres,hectares,percent
  36. # %end
  37. # %option
  38. # % key: sort
  39. # % type: string
  40. # % description: Sort the result
  41. # % options: asc,desc
  42. # % descriptions: asc;Sort in ascending order;desc;Sort in descending order
  43. # %end
  44. # %option G_OPT_F_SEP
  45. # %end
  46. # %flag
  47. # % key: c
  48. # % description: Do not include column names in output
  49. # %end
  50. # %flag
  51. # % key: d
  52. # % description: Report for geometries with no database records
  53. # %end
  54. import sys
  55. import os
  56. import grass.script as grass
  57. from grass.script.utils import separator, decode
  58. def uniq(items):
  59. result = []
  60. last = None
  61. for i in items:
  62. if i != last:
  63. result.append(i)
  64. last = i
  65. return result
  66. def main():
  67. mapname = options["map"]
  68. layer = options["layer"]
  69. option = options["option"]
  70. units = options["units"]
  71. sort = options["sort"]
  72. fs = separator(options["separator"])
  73. nuldev = open(os.devnull, "w")
  74. if not grass.find_file(mapname, "vector")["file"]:
  75. grass.fatal(_("Vector map <%s> not found") % mapname)
  76. if int(layer) in grass.vector_db(mapname):
  77. colnames = grass.vector_columns(mapname, layer, getDict=False, stderr=nuldev)
  78. isConnection = True
  79. else:
  80. isConnection = False
  81. colnames = ["cat"]
  82. if option == "coor":
  83. extracolnames = ["x", "y", "z"]
  84. else:
  85. extracolnames = [option]
  86. if units == "percent":
  87. unitsp = "meters"
  88. elif units:
  89. unitsp = units
  90. else:
  91. unitsp = None
  92. # NOTE: we suppress -1 cat and 0 cat
  93. if isConnection:
  94. f = grass.vector_db(map=mapname)[int(layer)]
  95. p = grass.pipe_command(
  96. "v.db.select", flags="e", quiet=True, map=mapname, layer=layer
  97. )
  98. records1 = []
  99. catcol = -1
  100. ncols = 0
  101. for line in p.stdout:
  102. cols = decode(line).rstrip("\r\n").split("|")
  103. if catcol == -1:
  104. ncols = len(cols)
  105. for i in range(0, ncols):
  106. if cols[i] == f["key"]:
  107. catcol = i
  108. break
  109. if catcol == -1:
  110. grass.fatal(
  111. _(
  112. "There is a table connected to input vector map '%s', but "
  113. "there is no key column '%s'."
  114. )
  115. % (mapname, f["key"])
  116. )
  117. continue
  118. if cols[catcol] == "-1" or cols[catcol] == "0":
  119. continue
  120. records1.append(cols[:catcol] + [int(cols[catcol])] + cols[(catcol + 1) :])
  121. p.wait()
  122. if p.returncode != 0:
  123. sys.exit(1)
  124. records1.sort(key=lambda r: r[catcol])
  125. if len(records1) == 0:
  126. try:
  127. grass.fatal(
  128. _(
  129. "There is a table connected to input vector map '%s', but "
  130. "there are no categories present in the key column '%s'. Consider using "
  131. "v.to.db to correct this."
  132. )
  133. % (mapname, f["key"])
  134. )
  135. except KeyError:
  136. pass
  137. # fetch the requested attribute sorted by cat:
  138. p = grass.pipe_command(
  139. "v.to.db",
  140. flags="p",
  141. quiet=True,
  142. map=mapname,
  143. option=option,
  144. layer=layer,
  145. units=unitsp,
  146. )
  147. records2 = []
  148. for line in p.stdout:
  149. fields = decode(line).rstrip("\r\n").split("|")
  150. if fields[0] in ["cat", "-1", "0"]:
  151. continue
  152. records2.append([int(fields[0])] + fields[1:])
  153. p.wait()
  154. records2.sort()
  155. # make pre-table
  156. # len(records1) may not be the same as len(records2) because
  157. # v.db.select can return attributes that are not linked to features.
  158. records3 = []
  159. for r2 in records2:
  160. rec = list(filter(lambda r1: r1[catcol] == r2[0], records1))
  161. if len(rec) > 0:
  162. res = rec[0] + r2[1:]
  163. elif flags["d"]:
  164. res = [r2[0]] + [""] * (ncols - 1) + r2[1:]
  165. else:
  166. continue
  167. records3.append(res)
  168. else:
  169. catcol = 0
  170. records1 = []
  171. p = grass.pipe_command("v.category", inp=mapname, layer=layer, option="print")
  172. for line in p.stdout:
  173. field = int(decode(line).rstrip())
  174. if field > 0:
  175. records1.append(field)
  176. p.wait()
  177. records1.sort()
  178. records1 = uniq(records1)
  179. # make pre-table
  180. p = grass.pipe_command(
  181. "v.to.db",
  182. flags="p",
  183. quiet=True,
  184. map=mapname,
  185. option=option,
  186. layer=layer,
  187. units=unitsp,
  188. )
  189. records3 = []
  190. for line in p.stdout:
  191. fields = decode(line).rstrip("\r\n").split("|")
  192. if fields[0] in ["cat", "-1", "0"]:
  193. continue
  194. records3.append([int(fields[0])] + fields[1:])
  195. p.wait()
  196. records3.sort()
  197. # print table header
  198. if not flags["c"]:
  199. sys.stdout.write(fs.join(colnames + extracolnames) + "\n")
  200. # make and print the table:
  201. numcols = len(colnames) + len(extracolnames)
  202. # calculate percents if requested
  203. if units == "percent" and option != "coor":
  204. # calculate total value
  205. total = 0
  206. for r in records3:
  207. total += float(r[-1])
  208. # calculate percentages
  209. records4 = [float(r[-1]) * 100 / total for r in records3]
  210. if type(records1[0]) == int:
  211. records3 = [[r1] + [r4] for r1, r4 in zip(records1, records4)]
  212. else:
  213. records3 = [r1 + [r4] for r1, r4 in zip(records1, records4)]
  214. # sort results
  215. if sort:
  216. if sort == "asc":
  217. if option == "coor":
  218. records3.sort(key=lambda r: (float(r[-3]), float(r[-2]), float(r[-1])))
  219. else:
  220. records3.sort(key=lambda r: float(r[-1]))
  221. else:
  222. if option == "coor":
  223. records3.sort(
  224. key=lambda r: (float(r[-3]), float(r[-2]), float(r[-1])),
  225. reverse=True,
  226. )
  227. else:
  228. records3.sort(key=lambda r: float(r[-1]), reverse=True)
  229. for r in records3:
  230. sys.stdout.write(fs.join(map(str, r)) + "\n")
  231. if __name__ == "__main__":
  232. options, flags = grass.parser()
  233. main()