v.colors.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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 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: Set color rules for features in a vector using a numeric attribute column.
  19. #% keywords: vector
  20. #% keywords: color table
  21. #%End
  22. #% option
  23. #% key: map
  24. #% type: string
  25. #% gisprompt: old,vector,vector
  26. #% key_desc: name
  27. #% description: Name of input vector map
  28. #% required: yes
  29. #%end
  30. #%option
  31. #% key: column
  32. #% type: string
  33. #% description: Name of column containing numeric data
  34. #% gisprompt: old_dbcolumn,dbcolumn,dbcolumn
  35. #% required : yes
  36. #%end
  37. #%option
  38. #% key: layer
  39. #% type: integer
  40. #% description: Layer number of data column
  41. #% gisprompt: old_layer,layer,layer
  42. #% answer: 1
  43. #% required: no
  44. #%end
  45. #%Option
  46. #% key: rgb_column
  47. #% type: string
  48. #% required: no
  49. #% description: Name of color column to populate
  50. #% gisprompt: old_dbcolumn,dbcolumn,dbcolumn
  51. #% answer: GRASSRGB
  52. #% guisection: Colors
  53. #%End
  54. #% option
  55. #% key: range
  56. #% type: double
  57. #% required: no
  58. #% multiple: no
  59. #% key_desc: min,max
  60. #% description: Manually set range (min,max)
  61. #%End
  62. #% option
  63. #% key: color
  64. #% type: string
  65. #% key_desc: style
  66. #% 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
  67. #% description: Type of color table
  68. #% required: no
  69. #% guisection: Colors
  70. #%end
  71. #%Option
  72. #% key: raster
  73. #% type: string
  74. #% required: no
  75. #% description: Raster map name from which to copy color table
  76. #% gisprompt: old,cell,raster
  77. #% guisection: Colors
  78. #%End
  79. #%Option
  80. #% key: rules
  81. #% type: string
  82. #% required: no
  83. #% description: Path to rules file
  84. #% gisprompt: old_file,file,input
  85. #% guisection: Colors
  86. #%End
  87. #%Flag
  88. #% key: s
  89. #% description: Save placeholder raster map for use with d.legend
  90. #%End
  91. #%Flag
  92. #% key: n
  93. #% description: Invert colors
  94. #% guisection: Colors
  95. #%End
  96. ## TODO: implement -e (equalized) and -g (logarithmic) methods in r.colors
  97. ## 'v.db.select column= | wc -l' to set region size (1xLength)
  98. ## then create r.in.ascii 1xLength matrix with data (WITHOUT uniq)
  99. ## and run r.colors on that raster map.
  100. ## or
  101. ## v.to.rast, r.colors -g, then parse colr/ file. But that's resolution dependent
  102. import sys
  103. import os
  104. import atexit
  105. import string
  106. import grass.script as grass
  107. def cleanup():
  108. if tmp:
  109. grass.try_remove(tmp)
  110. if tmp_vcol:
  111. grass.try_remove(tmp_vcol)
  112. if tmp_colr:
  113. grass.run_command('g.remove', rast = tmp_colr, quiet = True)
  114. def main():
  115. color = options['color']
  116. column = options['column']
  117. layer = options['layer']
  118. map = options['map']
  119. range = options['range']
  120. raster = options['raster']
  121. rgb_column = options['rgb_column']
  122. rules = options['rules']
  123. flip = flags['n']
  124. global tmp, tmp_colr, tmp_vcol
  125. pid = os.getpid()
  126. tmp = None
  127. tmp_colr = None
  128. tmp_vcol = None
  129. os.environ['GRASS_VERBOSE'] = '0'
  130. kv = grass.gisenv()
  131. mapset = kv['MAPSET']
  132. gisbase = os.getenv('GISBASE')
  133. #### does map exist in CURRENT mapset?
  134. kv = grass.find_file(map, element = 'vector', mapset = mapset)
  135. map_split = map.split('@')
  136. vect_mapset = map_split[1:]
  137. if vect_mapset == []:
  138. vect_mapset = mapset
  139. else:
  140. vect_mapset = vect_mapset[0]
  141. if not kv['file'] or vect_mapset != mapset:
  142. grass.fatal(_("Vector map <%s> not found in current mapset") % map)
  143. vector = map_split[0]
  144. # sanity check mutually exclusive color options
  145. ctest = 0
  146. for opt in ['color', 'raster', 'rules']:
  147. if options[opt]:
  148. ctest += 1
  149. if ctest != 1:
  150. grass.fatal(_("Pick one of color, rules, or raster options"))
  151. if color:
  152. #### check the color rule is valid
  153. color_opts = os.listdir(os.path.join(gisbase,'etc','colors'))
  154. color_opts += ['random', 'grey.eq', 'grey.log', 'rules']
  155. if color not in color_opts:
  156. grass.fatal(("Invalid color rule <%s>\n" % color) +
  157. ("Valid options are: %s" % ' '.join(color_opts)))
  158. elif raster:
  159. if not grass.find_file(raster)['name']:
  160. grass.fatal(_("Unable to open raster map <%s>") % raster)
  161. elif rules:
  162. if not os.access(rules, os.R_OK):
  163. grass.fatal(_("Unable to read color rules file <%s>") % rules)
  164. #### column checks
  165. # check input data column
  166. cols = grass.vector_columns(map, layer = layer)
  167. if column not in cols:
  168. grass.fatal(_("Column <%s> not found") % column)
  169. ncolumn_type = cols[column]
  170. if ncolumn_type not in ["INTEGER", "DOUBLE PRECISION"]:
  171. grass.fatal(_("Column <%s> is not numeric") % column)
  172. #g.message "column <$GIS_OPT_COLUMN> is type [$NCOLUMN_TYPE]"
  173. # check if GRASSRGB column exists, make it if it doesn't
  174. table = grass.vector_db(map)[layer]['table']
  175. if rgb_column not in cols:
  176. # RGB Column not found, create it
  177. grass.message(_("Creating column <%s> ...") % rgb_column)
  178. if 0 != grass.run_command('v.db.addcolumn', map = map, layer = layer, column = "%s varchar(11)" % rgb_column):
  179. grass.fatal(_("Creating color column"))
  180. else:
  181. column_type = cols[rgb_column]
  182. if column_type not in ["CHARACTER", "TEXT"]:
  183. grass.fatal(_("Column <%s> is not of compatible type (found %s)") % (rgb_column, column_type))
  184. else:
  185. num_chars = dict([(v[0], int(v[2])) for v in grass.db_describe(table)['cols']])[rgb_column]
  186. if num_chars < 11:
  187. grass.fatal(_("Color column <%s> is not wide enough (needs 11 characters)"), rgb_column)
  188. cvals = grass.vector_db_select(map, layer = layer, column = column)['values'].values()
  189. # find data range
  190. if range:
  191. #order doesn't matter
  192. vals = range.split(',')
  193. else:
  194. grass.message(_("Scanning values ..."))
  195. vals = [float(x[0]) for x in cvals]
  196. minval = min(vals)
  197. maxval = max(vals)
  198. grass.message(_(" min=[%s] max=[$%s]") % (minval, maxval))
  199. if not minval or not maxval:
  200. grass.fatal(_("Scanning data range"))
  201. # setup internal region
  202. use_temp_region()
  203. grass.run_command('g.region', rows = 2, cols = 2)
  204. tmp_colr = "tmp_colr_%d" % pid
  205. # create dummy raster map
  206. if ncolumn_type == "INTEGER":
  207. grass.mapcalc("$tmp_colr = if(row() == 1, $minval, $maxval)",
  208. tmp_colr = tmp_colr, minval = minval, maxval = maxval)
  209. else:
  210. grass.mapcalc("$tmp_colr = double(if(row() == 1, $minval, $maxval))",
  211. tmp_colr = tmp_colr, minval = minval, maxval = maxval)
  212. if color:
  213. color_cmd = {'color': color}
  214. elif raster:
  215. color_cmd = {'raster': raster}
  216. elif rules:
  217. color_cmd = {'rules': rules}
  218. if flip:
  219. flip_flag = 'n'
  220. else:
  221. flip_flag = ''
  222. grass.run_command('r.colors', map = tmp_colr, flags = flip_flag, **color_cmd)
  223. tmp = grass.tempfile()
  224. # calculate colors and write SQL command file
  225. grass.message(_("Looking up colors ..."))
  226. f = open(tmp, 'w')
  227. p = grass.feed_command('r.what.color', flags = 'i', input = tmp_colr, stdout = f)
  228. lastval = None
  229. for v in sorted(cvals):
  230. if v == lastval:
  231. continue
  232. p.stdin.write('%f\n' % v)
  233. p.stdin.close()
  234. p.wait()
  235. f.close()
  236. tmp_vcol = "%s_vcol.sql" % tmp
  237. fi = open(tmp, 'r')
  238. fo = open(tmp_vcol, 'w')
  239. t = string.Template("UPDATE $table SET $rgb_column = '$colr' WHERE $column = $value;\n")
  240. found = 0
  241. for line in fi:
  242. [value, colr] = line.split(':')
  243. colr = colr.strip()
  244. if len(colr.split(':')) != 3:
  245. continue
  246. #g.message message="LINE=[$LINE]"
  247. fo.write(t.substitute(table = table, rgb_column = rgb_column, colr = colr, value = value))
  248. found += 1
  249. fi.close()
  250. fo.close()
  251. if not found:
  252. grass.fatal(_("No values found in color range"))
  253. # apply SQL commands to update the table with values
  254. grass.message(_("Writing %s colors ...") % found)
  255. # less "$TMP"
  256. if 0 != grass.run_command('db.execute', input = tmp_vcol):
  257. grass.fatal(_("Processing SQL transaction"))
  258. if flags['s']:
  259. vcolors = "vcolors_%d" % pid
  260. grass.run_command('g.rename', rast = (tmp_colr, vcolors))
  261. grass.message(_("Raster map containing color rules saved to <%s>") % vcolors)
  262. # TODO save full v.colors command line history
  263. grass.run_command('r.support', map = vcolors,
  264. history = "",
  265. source1 = "vector map = %s" % map,
  266. source2 = "column = %s" % column,
  267. title = "Dummy raster to use as thematic vector legend",
  268. description = "generated by v.colors using r.mapcalc")
  269. grass.run_command('r.support', map = vcolors,
  270. history = "RGB saved into <%s> using <%s%s%s>" % (rgb_column, color, raster, rules))
  271. else:
  272. grass.run_command('g.remove', rast = tmp_colr)
  273. #v.db.dropcolumn map=vcol_test col=GRASSRGB
  274. #d.vect -a vcol_test icon=basic/circle color=none size=8
  275. if __name__ == "__main__":
  276. options, flags = grass.parser()
  277. atexit.register(cleanup)
  278. main()