4
@brief Dialog for interactive management of raster color tables and vector
11
(C) 2008 by the GRASS Development Team
12
This program is free software under the GNU General Public License
13
(>=v2). Read the file COPYING that comes with GRASS for details.
15
@author Michael Barton (Arizona State University)
16
@author Martin Landa <landa.martin gmail.com> (various updates)
24
import wx.lib.colourselect as csel
25
import wx.lib.scrolledpanel as scrolled
27
from grass.script import core as grass
35
from debug import Debug as Debug
36
from preferences import globalSettings as UserSettings
38
class ColorTable(wx.Frame):
39
def __init__(self, parent, id=wx.ID_ANY, title='',
40
pos=wx.DefaultPosition, size=(-1, -1),
41
style=wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER,
43
wx.Frame.__init__(self, parent, id, title, pos, size, style)
45
Dialog for interactively entering rules for map management
48
@param cmd command (given as list)
50
self.parent = parent # GMFrame
52
self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
55
self.cmd = kwargs['cmd']
62
# min cat in raster map
64
# max cat in raster map
70
# list of database layers for vector (minimum of 1)
72
# list of database columns for vector
74
# vector layer for attribute table to use for setting color
76
# vector attribute table used for setting color
78
# vector attribute column for assigning colors
80
# vector attribute column to use for storing colors
84
# rules for creating colortable
87
# instance of render.Map to be associated with display
88
self.Map = render.Map()
90
# reference to layer with preview
93
if self.cmd == 'r.colors':
94
self.SetTitle(_('Create new color table for raster map'))
96
crlabel = _('Enter raster cat values or percents')
97
elif self.cmd == 'vcolors':
98
self.SetTitle(_('Create new color table for vector map'))
100
crlabel = _('Enter vector attribute values or ranges (n or n1 to n2)')
103
# Set the size & cursor
105
self.SetClientSize(size)
107
### self.panel = wx.Panel(parent=self, id=wx.ID_ANY)
110
if self.cmd == 'r.colors':
111
maplabel = _('Select raster map:')
112
elif self.cmd == 'vcolors':
113
maplabel = _('Select vector map:')
114
inputBox = wx.StaticBox(parent=self, id=wx.ID_ANY,
115
label=" %s " % maplabel)
116
self.inputSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL)
117
self.selectionInput = gselect.Select(parent=self, id=wx.ID_ANY,
118
size=globalvar.DIALOG_GSELECT_SIZE,
121
self.ovrwrtcheck = wx.CheckBox(parent=self, id=wx.ID_ANY,
122
label=_('replace existing color table'))
123
self.ovrwrtcheck.SetValue(UserSettings.Get(group='cmd', key='overwrite', subkey='enabled'))
124
self.helpbtn = wx.Button(parent=self, id=wx.ID_HELP)
126
if self.elem == 'vector':
127
self.cb_vl_label = wx.StaticText(parent=self, id=wx.ID_ANY,
129
self.cb_vc_label = wx.StaticText(parent=self, id=wx.ID_ANY,
130
label=_('Attribute column:'))
131
self.cb_vrgb_label = wx.StaticText(parent=self, id=wx.ID_ANY,
132
label=_('RGB color column:'))
133
self.cb_vlayer = gselect.LayerSelect(self)
134
self.cb_vcol = gselect.ColumnSelect(self)
135
self.cb_vrgb = gselect.ColumnSelect(self)
137
# color table and preview window
138
self.cr_label = wx.StaticText(parent=self, id=wx.ID_ANY,
140
self.cr_panel = self.__colorrulesPanel()
141
# add two rules as default
144
self.numRules = wx.SpinCtrl(parent=self, id=wx.ID_ANY,
147
# initialize preview display
149
self.preview = BufferedWindow(self, id=wx.ID_ANY, size=(400, 300),
151
self.preview.EraseMap()
153
self.btnCancel = wx.Button(parent=self, id=wx.ID_CANCEL)
154
self.btnApply = wx.Button(parent=self, id=wx.ID_APPLY)
155
self.btnOK = wx.Button(parent=self, id=wx.ID_OK)
156
self.btnOK.SetDefault()
157
self.btnOK.Enable(False)
158
self.btnApply.Enable(False)
160
self.btnPreview = wx.Button(parent=self, id=wx.ID_ANY,
162
self.btnPreview.Enable(False)
163
self.btnAdd = wx.Button(parent=self, id=wx.ID_ADD)
166
self.Bind(wx.EVT_BUTTON, self.OnHelp, self.helpbtn)
167
self.selectionInput.Bind(wx.EVT_TEXT, self.OnSelectionInput)
168
self.Bind(wx.EVT_BUTTON, self.OnCancel, self.btnCancel)
169
self.Bind(wx.EVT_BUTTON, self.OnApply, self.btnApply)
170
self.Bind(wx.EVT_BUTTON, self.OnOK, self.btnOK)
171
self.Bind(wx.EVT_BUTTON, self.OnPreview, self.btnPreview)
172
self.Bind(wx.EVT_BUTTON, self.OnAddRules, self.btnAdd)
173
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
175
# additional bindings for vector color management
176
if self.cmd == 'vcolors':
177
self.Bind(wx.EVT_COMBOBOX, self.OnLayerSelection, self.cb_vlayer)
178
self.Bind(wx.EVT_COMBOBOX, self.OnColumnSelection, self.cb_vcol)
179
self.Bind(wx.EVT_COMBOBOX, self.OnRGBColSelection, self.cb_vrgb)
181
# set map layer from layer tree
183
layer = self.parent.curr_page.maptree.layer_selected
187
mapLayer = self.parent.curr_page.maptree.GetPyData(layer)[0]['maplayer']
188
name = mapLayer.GetName()
189
type = mapLayer.GetType()
190
if (type == 'raster' and self.elem == 'cell') or \
191
(type == 'vector' and self.elem == 'vector'):
192
self.selectionInput.SetValue(name)
194
self.OnSelectionInput(None)
199
self.CentreOnScreen()
202
def __doLayout(self):
203
sizer = wx.BoxSizer(wx.VERTICAL)
208
self.inputSizer.Add(item=self.selectionInput,
209
flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border=5)
210
replaceSizer = wx.BoxSizer(wx.HORIZONTAL)
211
replaceSizer.Add(item=self.ovrwrtcheck, proportion=1,
212
flag=wx.ALL | wx.EXPAND, border=1)
213
replaceSizer.Add(item=self.helpbtn, proportion=0,
214
flag=wx.ALIGN_RIGHT | wx.ALL, border=1)
216
self.inputSizer.Add(item=replaceSizer, proportion=1,
217
flag=wx.ALL | wx.EXPAND, border=1)
222
bodySizer = wx.GridBagSizer(hgap=5, vgap=5)
225
bodySizer.Add(item=self.cr_label, pos=(row, 0), span=(1, 3),
226
flag=wx.ALL, border=5)
228
if self.cmd == 'vcolors':
229
vSizer = wx.GridBagSizer(hgap=5, vgap=5)
230
vSizer.Add(self.cb_vl_label, pos=(0, 0),
231
flag=wx.ALIGN_CENTER_VERTICAL)
232
vSizer.Add(self.cb_vlayer, pos=(0, 1),
233
flag=wx.ALIGN_CENTER_VERTICAL)
234
vSizer.Add(self.cb_vc_label, pos=(0, 2),
235
flag=wx.ALIGN_CENTER_VERTICAL)
236
vSizer.Add(self.cb_vcol, pos=(0, 3),
237
flag=wx.ALIGN_CENTER_VERTICAL)
238
vSizer.Add(self.cb_vrgb_label, pos=(1, 2),
239
flag=wx.ALIGN_CENTER_VERTICAL)
240
vSizer.Add(self.cb_vrgb, pos=(1, 3),
241
flag=wx.ALIGN_CENTER_VERTICAL)
243
bodySizer.Add(item=vSizer, pos=(row, 0), span=(1, 3))
246
bodySizer.Add(item=self.cr_panel, pos=(row, 0), span=(1, 2))
248
bodySizer.Add(item=self.preview, pos=(row, 2),
249
flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=10)
250
bodySizer.AddGrowableCol(2)
253
bodySizer.Add(item=self.numRules, pos=(row, 0),
254
flag=wx.ALIGN_CENTER_VERTICAL)
256
bodySizer.Add(item=self.btnAdd, pos=(row, 1))
257
bodySizer.Add(item=self.btnPreview, pos=(row, 2),
260
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
261
btnSizer.Add(self.btnCancel,
262
flag=wx.LEFT | wx.RIGHT, border=5)
263
btnSizer.Add(self.btnApply,
264
flag=wx.LEFT | wx.RIGHT, border=5)
265
btnSizer.Add(self.btnOK,
266
flag=wx.LEFT | wx.RIGHT, border=5)
268
sizer.Add(item=self.inputSizer, proportion=0,
269
flag=wx.ALL | wx.EXPAND, border=5)
271
sizer.Add(item=bodySizer, proportion=1,
272
flag=wx.ALL | wx.EXPAND, border=5)
274
sizer.Add(item=wx.StaticLine(parent=self, id=wx.ID_ANY,
275
style=wx.LI_HORIZONTAL),
277
flag=wx.EXPAND | wx.ALL, border=5)
279
sizer.Add(item=btnSizer, proportion=0,
280
flag=wx.ALL | wx.ALIGN_RIGHT, border=5)
286
def __colorrulesPanel(self):
287
cr_panel = scrolled.ScrolledPanel(parent=self, id=wx.ID_ANY,
289
style=wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER)
291
self.cr_sizer = wx.GridBagSizer(vgap=2, hgap=4)
293
cr_panel.SetSizer(self.cr_sizer)
294
cr_panel.SetAutoLayout(True)
298
def OnAddRules(self, event):
299
"""Add rules button pressed"""
300
nrules = self.numRules.GetValue()
301
self.AddRules(nrules)
303
def AddRules(self, nrules):
305
snum = len(self.ruleslines.keys())
306
for num in range(snum, snum+nrules):
308
enable = wx.CheckBox(parent=self.cr_panel, id=num)
309
enable.SetValue(True)
310
self.Bind(wx.EVT_CHECKBOX, self.OnRuleEnable, enable)
312
txt_ctrl = wx.TextCtrl(parent=self.cr_panel, id=1000+num, value='',
314
style=wx.TE_NOHIDESEL)
315
self.Bind(wx.EVT_TEXT, self.OnRuleValue, txt_ctrl)
317
color_ctrl = csel.ColourSelect(self.cr_panel, id=2000+num)
318
self.Bind(csel.EVT_COLOURSELECT, self.OnRuleColor, color_ctrl)
319
self.ruleslines[enable.GetId()] = { 'value' : '',
322
self.cr_sizer.Add(item=enable, pos=(num, 0),
323
flag=wx.ALIGN_CENTER_VERTICAL)
324
self.cr_sizer.Add(item=txt_ctrl, pos=(num, 1),
325
flag=wx.ALIGN_CENTER | wx.RIGHT, border=5)
326
self.cr_sizer.Add(item=color_ctrl, pos=(num, 2),
327
flag=wx.ALIGN_CENTER | wx.RIGHT, border=5)
329
self.cr_panel.Layout()
330
self.cr_panel.SetupScrolling()
332
def InitDisplay(self):
334
Initialize preview display, set dimensions and region
336
#self.width, self.height = self.GetClientSize()
337
self.width = self.Map.width = 400
338
self.height = self.Map.height = 300
339
self.Map.geom = self.width, self.height
341
def OnErase(self, event):
343
Erase the histogram display
345
self.PreviewWindow.Draw(self.HistWindow.pdc, pdctype='clear')
347
def OnCloseWindow(self, event):
350
Also remove associated rendered images
353
# self.propwin.Close(True)
359
def OnSelectionInput(self, event):
361
self.inmap = event.GetString()
364
self.btnPreview.Enable(False)
365
self.btnOK.Enable(False)
366
self.btnApply.Enable(False)
369
if self.elem == 'cell':
372
'map=%s' % self.inmap]
375
p = gcmd.Command(cmdlist)
377
for line in p.ReadStdOutput():
379
self.rast['min'] = float(line.split('=')[1])
381
self.rast['max'] = float(line.split('=')[1])
382
except gcmd.CmdError:
384
self.rast['min'] = self.rast['max'] = None
385
self.btnPreview.Enable(False)
386
self.btnOK.Enable(False)
387
self.btnApply.Enable(False)
388
self.preview.EraseMap()
389
self.cr_label.SetLabel(_('Enter raster cat values or percents'))
392
self.cr_label.SetLabel(_('Enter raster cat values or percents (range = %(min)d-%(max)d)') %
393
{ 'min' : self.rast['min'],
394
'max' : self.rast['max'] })
395
elif self.elem == 'vector':
396
# initialize layer selection combobox
397
self.cb_vlayer.InsertLayers(self.inmap)
398
# initialize attribute table for layer=1
399
layer = int(self.vect['layer'])
400
self.vect['table'] = gselect.VectorDBInfo(self.inmap).layers[layer]['table']
401
# initialize column selection comboboxes
402
self.cb_vcol.InsertColumns(vector=self.inmap, layer=layer)
403
self.cb_vrgb.InsertColumns(vector=self.inmap, layer=layer)
406
self.btnPreview.Enable(True)
407
self.btnOK.Enable(True)
408
self.btnApply.Enable(True)
410
def OnLayerSelection(self, event):
411
# reset choices in column selection comboboxes if layer changes
412
self.vlayer = int(event.GetString())
413
self.vtable = gselect.VectorDBInfo(self.inmap).layers[str(self.vlayer)]
414
self.cb_vcol.InsertColumns(vector=self.inmap, layer=self.vlayer)
415
self.cb_vrgb.InsertColumns(vector=self.inmap, layer=self.vlayer)
418
def OnColumnSelection(self, event):
419
self.vect['column'] = event.GetString()
421
def OnRGBColSelection(self, event):
422
self.vect['rgb'] = event.GetString()
424
def OnRuleEnable(self, event):
425
"""Rule enabled/disabled"""
428
if event.IsChecked():
429
value = self.FindWindowById(id+1000).GetValue()
430
color = self.FindWindowById(id+2000).GetValue()
431
color_str = str(color[0]) + ':' \
432
+ str(color[1]) + ':' + \
435
self.ruleslines[id] = {
437
'color' : color_str }
439
del self.ruleslines[id]
441
def OnRuleValue(self, event):
442
"""Rule value changed"""
444
vals = event.GetString().strip()
449
tc = self.FindWindowById(num)
451
if self.elem == 'cell':
457
except (IndexError, ValueError):
459
self.ruleslines[num-1000]['value'] = ''
462
self.ruleslines[num-1000]['value'] = vals
464
elif self.elem == 'vector':
465
if self.vect['column'] == '' or self.vect['rgb'] == '':
467
wx.MessageBox(parent=self,
468
message=_("Please select attribute column "
469
"and RGB color column first"),
473
self.ruleslines[num-1000]['value'] = self.SQLConvert(vals)
476
self.ruleslines[num-1000]['value'] = ''
479
def OnRuleColor(self, event):
480
"""Rule color changed"""
483
rgba_color = event.GetValue()
485
rgb_string = str(rgba_color[0]) + ':' \
486
+ str(rgba_color[1]) + ':' + \
489
self.ruleslines[num-2000]['color'] = rgb_string
491
def SQLConvert(self, vals):
493
valslist = vals.split('to')
494
if len(valslist) == 1:
495
sqlrule = '%s=%s' % (self.vect['column'], valslist[0])
496
elif len(valslist) > 1:
497
sqlrule = '%s>=%s AND %s<=%s' % (self.vect['column'], valslist[0],
498
self.vect['column'], valslist[1])
504
def OnApply(self, event):
505
self.CreateColorTable()
507
def OnOK(self, event):
511
def OnCancel(self, event):
514
def OnPreview(self, event):
517
if self.elem == 'cell':
519
'map=%s' % self.inmap]
522
# find existing color table and copy to temp file
524
name, mapset = self.inmap.split('@')
525
old_colrtable = grass.find_file(name=name, element='colr2/' + mapset)['file']
526
except (TypeError, ValueError):
530
colrtemp = utils.GetTempfile()
531
shutil.copyfile(old_colrtable, colrtemp)
533
elif self.elem == 'vector':
536
'map=%s' % self.inmap,
537
'rgb_column=%s' % self.vect["rgb"],
538
'type=point,line,boundary,area']
544
self.layer = self.Map.AddLayer(type=ltype, name='preview', command=cmdlist,
545
l_active=True, l_hidden=False, l_opacity=1.0,
548
self.layer.SetCmd(cmdlist)
550
# apply new color table and display preview
551
self.CreateColorTable(force=True)
552
self.preview.UpdatePreview()
554
# restore previous color table
555
if self.elem == 'cell':
557
shutil.copyfile(colrtemp, old_colrtable)
560
gcmd.Command(['r.colors',
562
'map=%s' % self.inmap])
564
def OnHelp(self, event):
565
"""Show GRASS manual page"""
566
gcmd.Command(['g.manual',
570
def CreateColorTable(self, force=False):
571
"""Creates color table"""
574
for rule in self.ruleslines.itervalues():
575
if not rule['value']: # skip empty rules
578
if self.elem == 'cell':
579
rulestxt += rule['value'] + ' ' + rule['color'] + '\n'
580
elif self.elem == 'vector':
581
rulestxt += "UPDATE %s SET %s='%s' WHERE %s ;\n" % (self.vect['table'],
588
gtemp = utils.GetTempfile()
589
output = open(gtemp, "w")
591
output.write(rulestxt)
595
if self.elem == 'cell':
596
cmdlist = ['r.colors',
597
'map=%s' % self.inmap,
601
not self.ovrwrtcheck.IsChecked():
604
elif self.elem == 'vector':
605
cmdlist = ['db.execute',
608
p = gcmd.Command(cmdlist)
610
class BufferedWindow(wx.Window):
611
"""A Buffered window class"""
612
def __init__(self, parent, id,
613
pos = wx.DefaultPosition,
614
size = wx.DefaultSize,
615
style=wx.NO_FULL_REPAINT_ON_RESIZE,
618
wx.Window.__init__(self, parent, id, pos, size, style)
623
# re-render the map from GRASS or just redraw image
625
# indicates whether or not a resize event has taken place
631
self.Bind(wx.EVT_PAINT, self.OnPaint)
632
self.Bind(wx.EVT_IDLE, self.OnIdle)
633
self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None)
636
# render output objects
638
# image file to be rendered
640
# wx.Image object (self.mapfile)
643
self.pdc = wx.PseudoDC()
644
# will store an off screen empty bitmap for saving to file
647
# make sure that extents are updated at init
648
self.Map.region = self.Map.GetRegion()
651
def Draw(self, pdc, img=None, pdctype='image'):
652
"""Draws preview or clears window"""
655
Debug.msg (3, "BufferedWindow.Draw(): pdctype=%s" % (pdctype))
657
if pdctype == 'clear': # erase the display
659
pdc.SetBackground(bg)
665
if pdctype == 'image' and img:
666
bg = wx.TRANSPARENT_BRUSH
667
pdc.SetBackground(bg)
668
bitmap = wx.BitmapFromImage(img)
669
w, h = bitmap.GetSize()
670
pdc.DrawBitmap(bitmap, 0, 0, True) # draw the composite map
675
def OnPaint(self, event):
676
"""Draw pseudo DC to buffer"""
677
self._Buffer = wx.EmptyBitmap(self.Map.width, self.Map.height)
678
dc = wx.BufferedPaintDC(self, self._Buffer)
680
# use PrepareDC to set position correctly
683
# we need to clear the dc BEFORE calling PrepareDC
684
bg = wx.Brush(self.GetBackgroundColour())
688
# create a clipping rect from our position and size
689
# and the Update Region
690
rgn = self.GetUpdateRegion()
693
# draw to the dc using the calculated clipping rect
694
self.pdc.DrawToDCClipped(dc, r)
696
def OnSize(self, event):
697
"""Init image size to match window size"""
698
# set size of the input image
699
self.Map.width, self.Map.height = self.GetClientSize()
701
# Make new off screen bitmap: this bitmap will always have the
702
# current drawing in it, so it can be used to save the image to
703
# a file, or whatever.
704
self._Buffer = wx.EmptyBitmap(self.Map.width, self.Map.height)
706
# get the image to be rendered
707
self.img = self.GetImage()
710
if self.img and self.Map.width + self.Map.height > 0: # scale image during resize
711
self.img = self.img.Scale(self.Map.width, self.Map.height)
715
# re-render image on idle
718
def OnIdle(self, event):
719
"""Only re-render a preview image from GRASS during
720
idle time instead of multiple times during resizing.
728
"""Converts files to wx.Image"""
729
if self.Map.mapfile and os.path.isfile(self.Map.mapfile) and \
730
os.path.getsize(self.Map.mapfile):
731
img = wx.Image(self.Map.mapfile, wx.BITMAP_TYPE_ANY)
737
def UpdatePreview(self, img=None):
738
"""Update canvas if window changes geometry"""
739
Debug.msg (2, "BufferedWindow.UpdatePreview(%s): render=%s" % (img, self.render))
744
# make sure that extents are updated
745
self.Map.region = self.Map.GetRegion()
748
# render new map images
749
self.mapfile = self.Map.Render(force=self.render)
750
self.img = self.GetImage()
756
# paint images to PseudoDC
759
# draw map image background
760
self.Draw(self.pdc, self.img, pdctype='image')
766
self.Draw(self.pdc, pdctype='clear')