v.colors.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. #!/usr/bin/env python
  2. ############################################################################
  3. #
  4. # MODULE: v.colors
  5. # AUTHOR: M. Hamish Bowman, Dept. Marine Science, Otago Univeristy,
  6. # New Zealand
  7. # Converted to Python by Glynn Clements
  8. # PURPOSE: Populate a GRASSRGB column with a color map and data column
  9. # Helper script for thematic mapping tasks
  10. #
  11. # COPYRIGHT: (c) 2008, 2010-2011 Hamish Bowman, and the GRASS Development Team
  12. # This program is free software under the GNU General Public
  13. # License (>=v2). Read the file COPYING that comes with GRASS
  14. # for details.
  15. #
  16. #############################################################################
  17. #%module
  18. #% description: Sets color rules for features in a vector map using a numeric attribute column.
  19. #% keywords: vector
  20. #% keywords: color table
  21. #%end
  22. #% option G_OPT_V_MAP
  23. #%end
  24. #%option G_OPT_V_FIELD
  25. #%end
  26. #%option G_OPT_DB_COLUMN
  27. #% description: Name of attribute column containing numeric data
  28. #% required : yes
  29. #%end
  30. #%option G_OPT_DB_COLUMN
  31. #% key: rgb_column
  32. #% description: Name of color column to populate with RGB values
  33. #% answer: GRASSRGB
  34. #% guisection: Colors
  35. #%end
  36. #%option
  37. #% key: range
  38. #% type: double
  39. #% required: no
  40. #% multiple: no
  41. #% key_desc: min,max
  42. #% description: Manually set range (min,max)
  43. #%end
  44. #%option
  45. #% key: color
  46. #% type: string
  47. #% key_desc: string
  48. #% options: aspect,aspectcolr,bcyr,bgyr,byg,byr,celsius,corine,curvature,differences,elevation,etopo2,evi,gdd,grey,grey1.0,grey255,grey.eq,grey.log,gyr,ndvi,population,precipitation,rainbow,ramp,random,ryb,ryg,sepia,slope,srtm,terrain,wave
  49. #% description: Type of color table
  50. #% required: no
  51. #% guisection: Colors
  52. #%end
  53. #%option G_OPT_R_INPUT
  54. #% key: raster
  55. #% required: no
  56. #% description: Name of raster map from which to copy color table
  57. #% guisection: Colors
  58. #%end
  59. #%option G_OPT_F_INPUT
  60. #% key: rules
  61. #% required: no
  62. #% description: Name of file containing rules
  63. #% guisection: Colors
  64. #%end
  65. #%flag
  66. #% key: s
  67. #% description: Save placeholder raster map for use with d.legend
  68. #%end
  69. #%flag
  70. #% key: n
  71. #% description: Invert colors
  72. #% guisection: Colors
  73. #%end
  74. ## TODO: implement -e (equalized) and -g (logarithmic) methods in r.colors
  75. ## 'v.db.select column= | wc -l' to set region size (1xLength)
  76. ## then create r.in.ascii 1xLength matrix with data (WITHOUT uniq)
  77. ## and run r.colors on that raster map.
  78. ## or
  79. ## v.to.rast, r.colors -g, then parse colr/ file. But that's resolution dependent
  80. import sys
  81. import os
  82. import atexit
  83. import string
  84. import grass.script as grass
  85. def cleanup():
  86. if tmp:
  87. grass.try_remove(tmp)
  88. if tmp_vcol:
  89. grass.try_remove(tmp_vcol)
  90. if tmp_colr:
  91. grass.run_command('g.remove', rast = tmp_colr, quiet = True)
  92. def main():
  93. color = options['color']
  94. column = options['column']
  95. layer = options['layer']
  96. map = options['map']
  97. range = options['range']
  98. raster = options['raster']
  99. rgb_column = options['rgb_column']
  100. rules = options['rules']
  101. flip = flags['n']
  102. global tmp, tmp_colr, tmp_vcol
  103. pid = os.getpid()
  104. tmp = tmp_colr = tmp_vcol = None
  105. mapset = grass.gisenv()['MAPSET']
  106. gisbase = os.getenv('GISBASE')
  107. # does map exist in CURRENT mapset?
  108. kv = grass.find_file(map, element = 'vector', mapset = mapset)
  109. if not kv['file']:
  110. grass.fatal(_("Vector map <%s> not found in current mapset") % map)
  111. vector = map.split('@', 1)
  112. # sanity check mutually exclusive color options
  113. if not options['color'] and not options['raster'] and not options['rules']:
  114. grass.fatal(_("Pick one of color, rules, or raster options"))
  115. if color:
  116. #### check the color rule is valid
  117. color_opts = os.listdir(os.path.join(gisbase, 'etc', 'colors'))
  118. color_opts += ['random', 'grey.eq', 'grey.log', 'rules']
  119. if color not in color_opts:
  120. grass.fatal(_("Invalid color rule <%s>\n") % color +
  121. _("Valid options are: %s") % ' '.join(color_opts))
  122. elif raster:
  123. if not grass.find_file(raster)['name']:
  124. grass.fatal(_("Raster raster map <%s> not found") % raster)
  125. elif rules:
  126. if not os.access(rules, os.R_OK):
  127. grass.fatal(_("Unable to read color rules file <%s>") % rules)
  128. # column checks
  129. # check input data column
  130. cols = grass.vector_columns(map, layer = layer)
  131. if column not in cols:
  132. grass.fatal(_("Column <%s> not found") % column)
  133. ncolumn_type = cols[column]['type']
  134. if ncolumn_type not in ["INTEGER", "DOUBLE PRECISION"]:
  135. grass.fatal(_("Column <%s> is not numeric but %s") % (column, ncolumn_type))
  136. # check if GRASSRGB column exists, make it if it doesn't
  137. table = grass.vector_db(map)[int(layer)]['table']
  138. if rgb_column not in cols:
  139. # RGB Column not found, create it
  140. grass.message(_("Creating column <%s>...") % rgb_column)
  141. if 0 != grass.run_command('v.db.addcolumn', map = map, layer = layer, column = "%s varchar(11)" % rgb_column):
  142. grass.fatal(_("Creating color column"))
  143. else:
  144. column_type = cols[rgb_column]['type']
  145. if column_type not in ["CHARACTER", "TEXT"]:
  146. grass.fatal(_("Column <%s> is not of compatible type (found %s)") % (rgb_column, column_type))
  147. else:
  148. num_chars = dict([(v[0], int(v[2])) for v in grass.db_describe(table)['cols']])[rgb_column]
  149. if num_chars < 11:
  150. grass.fatal(_("Color column <%s> is not wide enough (needs 11 characters)"), rgb_column)
  151. cvals = grass.vector_db_select(map, layer = int(layer), columns = column)['values'].values()
  152. # find data range
  153. if range:
  154. # order doesn't matter
  155. vals = range.split(',')
  156. else:
  157. grass.message(_("Scanning values..."))
  158. vals = [float(x[0]) for x in cvals]
  159. minval = min(vals)
  160. maxval = max(vals)
  161. grass.verbose(_("Range: [%s, %s]") % (minval, maxval))
  162. if not minval or not maxval:
  163. grass.fatal(_("Scanning data range"))
  164. # setup internal region
  165. grass.use_temp_region()
  166. grass.run_command('g.region', rows = 2, cols = 2)
  167. tmp_colr = "tmp_colr_%d" % pid
  168. # create dummy raster map
  169. if ncolumn_type == "INTEGER":
  170. grass.mapcalc("$tmp_colr = int(if(row() == 1, $minval, $maxval))",
  171. tmp_colr = tmp_colr, minval = minval, maxval = maxval)
  172. else:
  173. grass.mapcalc("$tmp_colr = double(if(row() == 1, $minval, $maxval))",
  174. tmp_colr = tmp_colr, minval = minval, maxval = maxval)
  175. if color:
  176. color_cmd = {'color': color}
  177. elif raster:
  178. color_cmd = {'raster': raster}
  179. elif rules:
  180. color_cmd = {'rules': rules}
  181. if flip:
  182. flip_flag = 'n'
  183. else:
  184. flip_flag = ''
  185. grass.run_command('r.colors', map = tmp_colr, flags = flip_flag, quiet = True, **color_cmd)
  186. tmp = grass.tempfile()
  187. # calculate colors and write SQL command file
  188. grass.message(_("Looking up colors..."))
  189. f = open(tmp, 'w')
  190. p = grass.feed_command('r.what.color', flags = 'i', input = tmp_colr, stdout = f)
  191. lastval = None
  192. for v in sorted(vals):
  193. if v == lastval:
  194. continue
  195. p.stdin.write('%f\n' % v)
  196. p.stdin.close()
  197. p.wait()
  198. f.close()
  199. tmp_vcol = "%s_vcol.sql" % tmp
  200. fi = open(tmp, 'r')
  201. fo = open(tmp_vcol, 'w')
  202. t = string.Template("UPDATE $table SET $rgb_column = '$colr' WHERE $column = $value;\n")
  203. found = 0
  204. for line in fi:
  205. [value, colr] = line.split(': ')
  206. colr = colr.strip()
  207. if len(colr.split(':')) != 3:
  208. continue
  209. fo.write(t.substitute(table = table, rgb_column = rgb_column, colr = colr, column = column, value = value))
  210. found += 1
  211. fi.close()
  212. fo.close()
  213. if not found:
  214. grass.fatal(_("No values found in color range"))
  215. # apply SQL commands to update the table with values
  216. grass.message(_("Writing %s colors...") % found)
  217. if 0 != grass.run_command('db.execute', input = tmp_vcol):
  218. grass.fatal(_("Processing SQL transaction"))
  219. if flags['s']:
  220. vcolors = "vcolors_%d" % pid
  221. grass.run_command('g.rename', rast = (tmp_colr, vcolors), quiet = True)
  222. grass.message(_("Raster map containing color rules saved to <%s>") % vcolors)
  223. # TODO save full v.colors command line history
  224. grass.run_command('r.support', map = vcolors,
  225. history = "",
  226. source1 = "vector map = %s" % map,
  227. source2 = "column = %s" % column,
  228. title = _("Dummy raster to use as thematic vector legend"),
  229. description = "generated by v.colors using r.mapcalc")
  230. grass.run_command('r.support', map = vcolors,
  231. history = _("RGB saved into <%s> using <%s%s%s>") % (rgb_column, color, raster, rules))
  232. if __name__ == "__main__":
  233. options, flags = grass.parser()
  234. atexit.register(cleanup)
  235. main()