v.report.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. #!/usr/bin/env python
  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, 2007-2017, 2019 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. #%flag
  45. #% key: c
  46. #% description: Do not include column names in output
  47. #%end
  48. #%flag
  49. #% key: d
  50. #% description: Report for geometries with no database records
  51. #%end
  52. import sys
  53. import os
  54. import grass.script as grass
  55. from grass.script.utils import decode
  56. def uniq(l):
  57. result = []
  58. last = None
  59. for i in l:
  60. if i != last:
  61. result.append(i)
  62. last = i
  63. return result
  64. def main():
  65. mapname = options['map']
  66. option = options['option']
  67. layer = options['layer']
  68. units = options['units']
  69. nuldev = open(os.devnull, 'w')
  70. if not grass.find_file(mapname, 'vector')['file']:
  71. grass.fatal(_("Vector map <%s> not found") % mapname)
  72. if int(layer) in grass.vector_db(mapname):
  73. colnames = grass.vector_columns(mapname, layer, getDict=False, stderr=nuldev)
  74. isConnection = True
  75. else:
  76. isConnection = False
  77. colnames = ['cat']
  78. if option == 'coor':
  79. extracolnames = ['x', 'y', 'z']
  80. else:
  81. extracolnames = [option]
  82. if units == 'percent':
  83. unitsp = 'meters'
  84. elif units:
  85. unitsp = units
  86. else:
  87. unitsp = None
  88. # NOTE: we suppress -1 cat and 0 cat
  89. if isConnection:
  90. f = grass.vector_db(map=mapname)[int(layer)]
  91. p = grass.pipe_command('v.db.select', quiet=True, map=mapname, layer=layer)
  92. records1 = []
  93. catcol = -1
  94. ncols = 0
  95. for line in p.stdout:
  96. cols = decode(line).rstrip('\r\n').split('|')
  97. if catcol == -1:
  98. ncols = len(cols)
  99. for i in range(0, ncols):
  100. if cols[i] == f['key']:
  101. catcol = i
  102. break
  103. if catcol == -1:
  104. grass.fatal(_("There is a table connected to input vector map '%s', but "
  105. "there is no key column '%s'.") % (mapname, f['key']))
  106. continue
  107. if cols[catcol] == '-1' or cols[catcol] == '0':
  108. continue
  109. records1.append(cols[:catcol] + [int(cols[catcol])] + cols[(catcol+1):])
  110. p.wait()
  111. if p.returncode != 0:
  112. sys.exit(1)
  113. records1.sort(key=lambda r: r[catcol])
  114. if len(records1) == 0:
  115. try:
  116. grass.fatal(_("There is a table connected to input vector map '%s', but "
  117. "there are no categories present in the key column '%s'. Consider using "
  118. "v.to.db to correct this.") % (mapname, f['key']))
  119. except KeyError:
  120. pass
  121. # fetch the requested attribute sorted by cat:
  122. p = grass.pipe_command('v.to.db', flags='p', quiet=True,
  123. map=mapname, option=option,
  124. layer=layer, units=unitsp)
  125. records2 = []
  126. for line in p.stdout:
  127. fields = decode(line).rstrip('\r\n').split('|')
  128. if fields[0] in ['cat', '-1', '0']:
  129. continue
  130. records2.append([int(fields[0])] + fields[1:])
  131. p.wait()
  132. records2.sort()
  133. # make pre-table
  134. # len(records1) may not be the same as len(records2) because
  135. # v.db.select can return attributes that are not linked to features.
  136. records3 = []
  137. for r2 in records2:
  138. rec = list(filter(lambda r1: r1[catcol] == r2[0], records1))
  139. if len(rec) > 0:
  140. res = rec[0] + r2[1:]
  141. elif flags['d']:
  142. res = [r2[0]] + [''] * (ncols - 1) + r2[1:]
  143. else:
  144. continue
  145. records3.append(res)
  146. else:
  147. catcol = 0
  148. records1 = []
  149. p = grass.pipe_command('v.category', inp=mapname, layer=layer, option='print')
  150. for line in p.stdout:
  151. field = int(decode(line).rstrip())
  152. if field > 0:
  153. records1.append(field)
  154. p.wait()
  155. records1.sort()
  156. records1 = uniq(records1)
  157. # make pre-table
  158. p = grass.pipe_command('v.to.db', flags='p', quiet=True,
  159. map=mapname, option=option,
  160. layer=layer, units=unitsp)
  161. records3 = []
  162. for line in p.stdout:
  163. fields = decode(line).rstrip('\r\n').split('|')
  164. if fields[0] in ['cat', '-1', '0']:
  165. continue
  166. records3.append([int(fields[0])] + fields[1:])
  167. p.wait()
  168. records3.sort()
  169. # print table header
  170. if not flags['c']:
  171. sys.stdout.write('|'.join(colnames + extracolnames) + '\n')
  172. # make and print the table:
  173. numcols = len(colnames) + len(extracolnames)
  174. # calculate percents if requested
  175. if units == 'percent' and option != 'coor':
  176. # calculate total value
  177. total = 0
  178. for r in records3:
  179. total += float(r[-1])
  180. # calculate percentages
  181. records4 = [float(r[-1]) * 100 / total for r in records3]
  182. if type(records1[0]) == int:
  183. records3 = [[r1] + [r4] for r1, r4 in zip(records1, records4)]
  184. else:
  185. records3 = [r1 + [r4] for r1, r4 in zip(records1, records4)]
  186. # sort results
  187. if options['sort']:
  188. if options['sort'] == 'asc':
  189. if options['option'] == 'coor':
  190. records3.sort(key=lambda r: (float(r[-3]), float(r[-2]), float(r[-1])))
  191. else:
  192. records3.sort(key=lambda r: float(r[-1]))
  193. else:
  194. if options['option'] == 'coor':
  195. records3.sort(key=lambda r: (float(r[-3]), float(r[-2]), float(r[-1])), reverse=True)
  196. else:
  197. records3.sort(key=lambda r: float(r[-1]), reverse=True)
  198. for r in records3:
  199. sys.stdout.write('|'.join(map(str, r)) + '\n')
  200. if __name__ == "__main__":
  201. options, flags = grass.parser()
  202. main()