|
GRASS Programmer's Manual
6.5.svn(2012)-r51648
|
00001 """ 00002 @package module.colorrules 00003 00004 @brief Dialog for interactive management of raster/vector color tables 00005 and color rules. 00006 00007 Classes: 00008 - colorrules::RulesPanel 00009 - colorrules::ColorTable 00010 - colorrules::RasterColorTable 00011 - colorrules::VectorColorTable 00012 - colorrules::BufferedWindow 00013 00014 (C) 2008, 2010-2011 by the GRASS Development Team 00015 00016 This program is free software under the GNU General Public License 00017 (>=v2). Read the file COPYING that comes with GRASS for details. 00018 00019 @author Michael Barton (Arizona State University) 00020 @author Martin Landa <landa.martin gmail.com> (various updates) 00021 @author Anna Kratochvilova <kratochanna gmail.com> (split to base and derived classes) 00022 """ 00023 00024 import os 00025 import shutil 00026 import copy 00027 import tempfile 00028 00029 import wx 00030 import wx.lib.colourselect as csel 00031 import wx.lib.scrolledpanel as scrolled 00032 import wx.lib.filebrowsebutton as filebrowse 00033 00034 import grass.script as grass 00035 00036 from core import globalvar 00037 from core import utils 00038 from core.gcmd import GMessage, RunCommand, GError 00039 from gui_core.gselect import Select, LayerSelect, ColumnSelect, VectorDBInfo 00040 from core.render import Map 00041 from gui_core.forms import GUI 00042 from core.debug import Debug as Debug 00043 from core.settings import UserSettings 00044 00045 class RulesPanel: 00046 def __init__(self, parent, mapType, attributeType, properties, panelWidth = 180): 00047 """!Create rules panel 00048 00049 @param mapType raster/vector 00050 @param attributeType color/size for choosing widget type 00051 @param properties properties of classes derived from ColorTable 00052 @param panelWidth width of scroll panel""" 00053 00054 self.ruleslines = {} 00055 self.mapType = mapType 00056 self.attributeType = attributeType 00057 self.properties = properties 00058 self.parent = parent 00059 self.panelWidth = panelWidth 00060 00061 self.mainSizer = wx.FlexGridSizer(cols = 3, vgap = 6, hgap = 4) 00062 # put small border at the top of panel 00063 for i in range(3): 00064 self.mainSizer.Add(item = wx.Size(3, 3)) 00065 00066 self.mainPanel = scrolled.ScrolledPanel(parent, id = wx.ID_ANY, 00067 size = (self.panelWidth, 300), 00068 style = wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER) 00069 00070 # (un)check all 00071 self.checkAll = wx.CheckBox(parent, id = wx.ID_ANY, label = _("Check all")) 00072 self.checkAll.SetValue(True) 00073 # clear button 00074 self.clearAll = wx.Button(parent, id = wx.ID_ANY, label = _("Clear all")) 00075 # determines how many rules should be added 00076 self.numRules = wx.SpinCtrl(parent, id = wx.ID_ANY, 00077 min = 1, max = 1e6, initial = 1) 00078 # add rules 00079 self.btnAdd = wx.Button(parent, id = wx.ID_ADD) 00080 00081 self.btnAdd.Bind(wx.EVT_BUTTON, self.OnAddRules) 00082 self.checkAll.Bind(wx.EVT_CHECKBOX, self.OnCheckAll) 00083 self.clearAll.Bind(wx.EVT_BUTTON, self.OnClearAll) 00084 00085 self.mainPanel.SetSizer(self.mainSizer) 00086 self.mainPanel.SetAutoLayout(True) 00087 self.mainPanel.SetupScrolling() 00088 00089 def Clear(self): 00090 """!Clear and widgets and delete information""" 00091 self.ruleslines.clear() 00092 self.mainSizer.Clear(deleteWindows=True) 00093 00094 def OnCheckAll(self, event): 00095 """!(Un)check all rules""" 00096 check = event.GetInt() 00097 for child in self.mainPanel.GetChildren(): 00098 if child.GetName() == 'enable': 00099 child.SetValue(check) 00100 else: 00101 child.Enable(check) 00102 00103 def OnClearAll(self, event): 00104 """!Delete all widgets in panel""" 00105 self.Clear() 00106 00107 def OnAddRules(self, event): 00108 """!Add rules button pressed""" 00109 nrules = self.numRules.GetValue() 00110 self.AddRules(nrules) 00111 00112 def AddRules(self, nrules, start = False): 00113 """!Add rules 00114 @param start set widgets (not append)""" 00115 00116 snum = len(self.ruleslines.keys()) 00117 if start: 00118 snum = 0 00119 for num in range(snum, snum + nrules): 00120 # enable 00121 enable = wx.CheckBox(parent = self.mainPanel, id = num) 00122 enable.SetValue(True) 00123 enable.SetName('enable') 00124 enable.Bind(wx.EVT_CHECKBOX, self.OnRuleEnable) 00125 # value 00126 txt_ctrl = wx.TextCtrl(parent = self.mainPanel, id = 1000 + num, 00127 size = (80, -1), 00128 style = wx.TE_NOHIDESEL) 00129 if self.mapType == 'vector': 00130 txt_ctrl.SetToolTipString(_("Enter vector attribute values")) 00131 txt_ctrl.Bind(wx.EVT_TEXT, self.OnRuleValue) 00132 txt_ctrl.SetName('source') 00133 if self.attributeType == 'color': 00134 # color 00135 columnCtrl = csel.ColourSelect(self.mainPanel, id = 2000 + num, 00136 size = globalvar.DIALOG_COLOR_SIZE) 00137 columnCtrl.Bind(csel.EVT_COLOURSELECT, self.OnRuleColor) 00138 columnCtrl.SetName('target') 00139 if not start: 00140 self.ruleslines[enable.GetId()] = { 'value' : '', 00141 'color': "0:0:0" } 00142 else: 00143 # size or width 00144 init = 2 00145 if self.attributeType == 'size': 00146 init = 100 00147 columnCtrl = wx.SpinCtrl(self.mainPanel, id = 2000 + num, 00148 size = (50, -1), min = 1, max = 1e4, 00149 initial = init) 00150 columnCtrl.Bind(wx.EVT_SPINCTRL, self.OnRuleSize) 00151 columnCtrl.Bind(wx.EVT_TEXT, self.OnRuleSize) 00152 columnCtrl.SetName('target') 00153 if not start: 00154 self.ruleslines[enable.GetId()] = { 'value' : '', 00155 self.attributeType: init } 00156 00157 self.mainSizer.Add(item = enable, proportion = 0, 00158 flag = wx.ALIGN_CENTER_VERTICAL) 00159 self.mainSizer.Add(item = txt_ctrl, proportion = 0, 00160 flag = wx.ALIGN_CENTER | wx.RIGHT, border = 5) 00161 self.mainSizer.Add(item = columnCtrl, proportion = 0, 00162 flag = wx.ALIGN_CENTER | wx.RIGHT, border = 10) 00163 00164 self.mainPanel.Layout() 00165 self.mainPanel.SetupScrolling(scroll_x = False) 00166 00167 def OnRuleEnable(self, event): 00168 """!Rule enabled/disabled""" 00169 id = event.GetId() 00170 00171 if event.IsChecked(): 00172 self.mainPanel.FindWindowById(id + 1000).Enable() 00173 self.mainPanel.FindWindowById(id + 2000).Enable() 00174 if self.mapType == 'vector' and not self.parent.GetParent().colorTable: 00175 vals = [] 00176 vals.append(self.mainPanel.FindWindowById(id + 1000).GetValue()) 00177 try: 00178 vals.append(self.mainPanel.FindWindowById(id + 1 + 1000).GetValue()) 00179 except AttributeError: 00180 vals.append(None) 00181 value = self.SQLConvert(vals) 00182 else: 00183 value = self.mainPanel.FindWindowById(id + 1000).GetValue() 00184 color = self.mainPanel.FindWindowById(id + 2000).GetValue() 00185 00186 if self.attributeType == 'color': 00187 # color 00188 color_str = str(color[0]) + ':' \ 00189 + str(color[1]) + ':' \ 00190 + str(color[2]) 00191 self.ruleslines[id] = {'value' : value, 00192 'color' : color_str } 00193 00194 else: 00195 # size or width 00196 self.ruleslines[id] = {'value' : value, 00197 self.attributeType : float(color) } 00198 00199 else: 00200 self.mainPanel.FindWindowById(id + 1000).Disable() 00201 self.mainPanel.FindWindowById(id + 2000).Disable() 00202 del self.ruleslines[id] 00203 00204 def OnRuleColor(self, event): 00205 """!Rule color changed""" 00206 num = event.GetId() 00207 00208 rgba_color = event.GetValue() 00209 00210 rgb_string = str(rgba_color[0]) + ':' \ 00211 + str(rgba_color[1]) + ':' \ 00212 + str(rgba_color[2]) 00213 00214 self.ruleslines[num-2000]['color'] = rgb_string 00215 00216 def OnRuleSize(self, event): 00217 """!Rule size changed""" 00218 num = event.GetId() 00219 size = event.GetInt() 00220 00221 self.ruleslines[num - 2000][self.attributeType] = size 00222 00223 def OnRuleValue(self, event): 00224 """!Rule value changed""" 00225 num = event.GetId() 00226 val = event.GetString().strip() 00227 00228 if val == '': 00229 return 00230 try: 00231 table = self.parent.colorTable 00232 except AttributeError: 00233 # due to panel/scrollpanel in vector dialog 00234 if isinstance(self.parent.GetParent(), RasterColorTable): 00235 table = self.parent.GetParent().colorTable 00236 else: 00237 table = self.parent.GetParent().GetParent().colorTable 00238 if table: 00239 self.SetRasterRule(num, val) 00240 else: 00241 self.SetVectorRule(num, val) 00242 00243 def SetRasterRule(self, num, val): 00244 """!Set raster rule""" 00245 self.ruleslines[num - 1000]['value'] = val 00246 00247 def SetVectorRule(self, num, val): 00248 """!Set vector rule""" 00249 vals = [] 00250 vals.append(val) 00251 try: 00252 vals.append(self.mainPanel.FindWindowById(num + 1).GetValue()) 00253 except AttributeError: 00254 vals.append(None) 00255 self.ruleslines[num - 1000]['value'] = self.SQLConvert(vals) 00256 00257 def Enable(self, enable = True): 00258 """!Enable/Disable all widgets""" 00259 for child in self.mainPanel.GetChildren(): 00260 child.Enable(enable) 00261 sql = True 00262 self.LoadRulesline(sql)# todo 00263 self.btnAdd.Enable(enable) 00264 self.numRules.Enable(enable) 00265 self.checkAll.Enable(enable) 00266 self.clearAll.Enable(enable) 00267 00268 00269 def LoadRules(self): 00270 message = "" 00271 for item in range(len(self.ruleslines)): 00272 try: 00273 self.mainPanel.FindWindowById(item + 1000).SetValue(self.ruleslines[item]['value']) 00274 r, g, b = (0, 0, 0) # default 00275 if not self.ruleslines[item][self.attributeType]: 00276 if self.attributeType == 'color': 00277 self.ruleslines[item][self.attributeType] = '%d:%d:%d' % (r, g, b) 00278 elif self.attributeType == 'size': 00279 self.ruleslines[item][self.attributeType] = 100 00280 elif self.attributeType == 'width': 00281 self.ruleslines[item][self.attributeType] = 2 00282 00283 if self.attributeType == 'color': 00284 try: 00285 r, g, b = map(int, self.ruleslines[item][self.attributeType].split(':')) 00286 except ValueError, e: 00287 message = _("Bad color format. Use color format '0:0:0'") 00288 self.mainPanel.FindWindowById(item + 2000).SetValue((r, g, b)) 00289 else: 00290 value = float(self.ruleslines[item][self.attributeType]) 00291 self.mainPanel.FindWindowById(item + 2000).SetValue(value) 00292 except: 00293 continue 00294 00295 if message: 00296 GMessage(parent = self.parent, message = message) 00297 return False 00298 00299 return True 00300 00301 def SQLConvert(self, vals): 00302 """!Prepare value for SQL query""" 00303 if vals[0].isdigit(): 00304 sqlrule = '%s=%s' % (self.properties['sourceColumn'], vals[0]) 00305 if vals[1]: 00306 sqlrule += ' AND %s<%s' % (self.properties['sourceColumn'], vals[1]) 00307 else: 00308 sqlrule = '%s=%s' % (self.properties['sourceColumn'], vals[0]) 00309 00310 return sqlrule 00311 00312 class ColorTable(wx.Frame): 00313 def __init__(self, parent, title, id = wx.ID_ANY, 00314 style = wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER, 00315 **kwargs): 00316 """!Dialog for interactively entering rules for map management 00317 commands 00318 """ 00319 self.parent = parent # GMFrame 00320 wx.Frame.__init__(self, parent, id, title, style = style, **kwargs) 00321 00322 self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO)) 00323 00324 self.panel = wx.Panel(parent = self, id = wx.ID_ANY) 00325 00326 # instance of render.Map to be associated with display 00327 self.Map = Map() 00328 00329 # input map to change 00330 self.inmap = '' 00331 # reference to layer with preview 00332 self.layer = None 00333 # layout 00334 self._doLayout() 00335 00336 # bindings 00337 self.Bind(wx.EVT_BUTTON, self.OnHelp, self.btnHelp) 00338 self.selectionInput.Bind(wx.EVT_TEXT, self.OnSelectionInput) 00339 self.Bind(wx.EVT_BUTTON, self.OnCancel, self.btnCancel) 00340 self.Bind(wx.EVT_BUTTON, self.OnApply, self.btnApply) 00341 self.Bind(wx.EVT_BUTTON, self.OnOK, self.btnOK) 00342 self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) 00343 00344 self.Bind(wx.EVT_BUTTON, self.OnPreview, self.btnPreview) 00345 00346 def _initLayer(self): 00347 """!Set initial layer when opening dialog""" 00348 # set map layer from layer tree, first selected, 00349 # if not the right type, than select another 00350 try: 00351 sel = self.parent.curr_page.maptree.layer_selected 00352 if sel and self.parent.curr_page.maptree.GetPyData(sel)[0]['type'] == self.mapType: 00353 layer = sel 00354 else: 00355 layer = self.parent.curr_page.maptree.FindItemByData(key = 'type', value = self.mapType) 00356 except: 00357 layer = None 00358 if layer: 00359 mapLayer = self.parent.curr_page.maptree.GetPyData(layer)[0]['maplayer'] 00360 name = mapLayer.GetName() 00361 type = mapLayer.GetType() 00362 self.selectionInput.SetValue(name) 00363 self.inmap = name 00364 00365 def _createMapSelection(self, parent): 00366 """!Create map selection part of dialog""" 00367 # top controls 00368 if self.mapType == 'raster': 00369 maplabel = _('Select raster map:') 00370 else: 00371 maplabel = _('Select vector map:') 00372 inputBox = wx.StaticBox(parent, id = wx.ID_ANY, 00373 label = " %s " % maplabel) 00374 inputSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL) 00375 00376 self.selectionInput = Select(parent = parent, id = wx.ID_ANY, 00377 size = globalvar.DIALOG_GSELECT_SIZE, 00378 type = self.mapType) 00379 # layout 00380 inputSizer.Add(item = self.selectionInput, 00381 flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border = 5) 00382 00383 return inputSizer 00384 00385 def _createFileSelection(self, parent): 00386 """!Create file (open/save rules) selection part of dialog""" 00387 inputBox = wx.StaticBox(parent, id = wx.ID_ANY, 00388 label = " %s " % _("Import or export color table:")) 00389 inputSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL) 00390 00391 self.loadRules = filebrowse.FileBrowseButton(parent = parent, id = wx.ID_ANY, fileMask = '*', 00392 size = globalvar.DIALOG_GSELECT_SIZE, 00393 labelText = _('Load color table from file:'), 00394 dialogTitle = _('Choose file to load color table'), 00395 buttonText = _('Load'), 00396 toolTip = _("Type filename or click to choose " 00397 "file and load color table"), 00398 startDirectory = os.getcwd(), fileMode = wx.OPEN, 00399 changeCallback = self.OnLoadRulesFile) 00400 self.saveRules = filebrowse.FileBrowseButton(parent = parent, id = wx.ID_ANY, fileMask = '*', 00401 size = globalvar.DIALOG_GSELECT_SIZE, 00402 labelText = _('Save color table to file:'), 00403 dialogTitle = _('Choose file to save color table'), 00404 toolTip = _("Type filename or click to choose " 00405 "file and save color table"), 00406 buttonText = _('Save'), 00407 startDirectory = os.getcwd(), fileMode = wx.SAVE, 00408 changeCallback = self.OnSaveRulesFile) 00409 00410 default = wx.Button(parent = parent, id = wx.ID_ANY, label = _("Reload default table")) 00411 # layout 00412 sizer = wx.BoxSizer(wx.HORIZONTAL) 00413 sizer.Add(item = self.loadRules, proportion = 1, 00414 flag = wx.RIGHT | wx.EXPAND, border = 10) 00415 sizer.Add(item = default, flag = wx.ALIGN_CENTER_VERTICAL) 00416 inputSizer.Add(item = sizer, 00417 flag = wx.TOP | wx.LEFT | wx.RIGHT | wx.EXPAND, border = 5) 00418 sizer = wx.BoxSizer(wx.HORIZONTAL) 00419 sizer.Add(item = self.saveRules, proportion = 1, 00420 flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND) 00421 inputSizer.Add(item = sizer, proportion = 1, 00422 flag = wx.ALL | wx.EXPAND, border = 5) 00423 00424 default.Bind(wx.EVT_BUTTON, self.OnLoadDefaultTable) 00425 00426 if self.mapType == 'vector': 00427 # parent is collapsible pane 00428 parent.SetSizer(inputSizer) 00429 00430 return inputSizer 00431 00432 def _createPreview(self, parent): 00433 """!Create preview""" 00434 # initialize preview display 00435 self.InitDisplay() 00436 self.preview = BufferedWindow(parent, id = wx.ID_ANY, size = (400, 300), 00437 Map = self.Map) 00438 self.preview.EraseMap() 00439 00440 def _createButtons(self, parent): 00441 """!Create buttons for leaving dialog""" 00442 self.btnHelp = wx.Button(parent, id = wx.ID_HELP) 00443 self.btnCancel = wx.Button(parent, id = wx.ID_CANCEL) 00444 self.btnApply = wx.Button(parent, id = wx.ID_APPLY) 00445 self.btnOK = wx.Button(parent, id = wx.ID_OK) 00446 00447 self.btnOK.SetDefault() 00448 self.btnOK.Enable(False) 00449 self.btnApply.Enable(False) 00450 00451 # layout 00452 btnSizer = wx.BoxSizer(wx.HORIZONTAL) 00453 btnSizer.Add(wx.Size(-1, -1), proportion = 1) 00454 btnSizer.Add(self.btnHelp, 00455 flag = wx.LEFT | wx.RIGHT, border = 5) 00456 btnSizer.Add(self.btnCancel, 00457 flag = wx.LEFT | wx.RIGHT, border = 5) 00458 btnSizer.Add(self.btnApply, 00459 flag = wx.LEFT | wx.RIGHT, border = 5) 00460 btnSizer.Add(self.btnOK, 00461 flag = wx.LEFT | wx.RIGHT, border = 5) 00462 00463 return btnSizer 00464 00465 def _createBody(self, parent): 00466 """!Create dialog body consisting of rules and preview""" 00467 bodySizer = wx.GridBagSizer(hgap = 5, vgap = 5) 00468 bodySizer.AddGrowableRow(1) 00469 bodySizer.AddGrowableCol(2) 00470 00471 row = 0 00472 # label with range 00473 self.cr_label = wx.StaticText(parent, id = wx.ID_ANY) 00474 bodySizer.Add(item = self.cr_label, pos = (row, 0), span = (1, 3), 00475 flag = wx.ALL, border = 5) 00476 00477 row += 1 00478 # color table 00479 self.rulesPanel = RulesPanel(parent = parent, mapType = self.mapType, 00480 attributeType = self.attributeType, properties = self.properties) 00481 00482 bodySizer.Add(item = self.rulesPanel.mainPanel, pos = (row, 0), 00483 span = (1, 2), flag = wx.EXPAND) 00484 # add two rules as default 00485 self.rulesPanel.AddRules(2) 00486 00487 # preview window 00488 self._createPreview(parent = parent) 00489 bodySizer.Add(item = self.preview, pos = (row, 2), 00490 flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER) 00491 00492 row += 1 00493 # add ckeck all and clear all 00494 bodySizer.Add(item = self.rulesPanel.checkAll, flag = wx.ALIGN_CENTER_VERTICAL, 00495 pos = (row, 0)) 00496 bodySizer.Add(item = self.rulesPanel.clearAll, pos = (row, 1)) 00497 00498 # preview button 00499 self.btnPreview = wx.Button(parent, id = wx.ID_ANY, 00500 label = _("Preview")) 00501 bodySizer.Add(item = self.btnPreview, pos = (row, 2), 00502 flag = wx.ALIGN_RIGHT) 00503 self.btnPreview.Enable(False) 00504 self.btnPreview.SetToolTipString(_("Show preview of map " 00505 "(current Map Display extent is used).")) 00506 00507 row +=1 00508 # add rules button and spin to sizer 00509 bodySizer.Add(item = self.rulesPanel.numRules, pos = (row, 0), 00510 flag = wx.ALIGN_CENTER_VERTICAL) 00511 bodySizer.Add(item = self.rulesPanel.btnAdd, pos = (row, 1)) 00512 00513 return bodySizer 00514 00515 def InitDisplay(self): 00516 """!Initialize preview display, set dimensions and region 00517 """ 00518 self.width = self.Map.width = 400 00519 self.height = self.Map.height = 300 00520 self.Map.geom = self.width, self.height 00521 00522 def OnCloseWindow(self, event): 00523 """!Window closed 00524 """ 00525 self.OnCancel(event) 00526 00527 def OnApply(self, event): 00528 """!Apply selected color table 00529 00530 @return True on success otherwise False 00531 """ 00532 ret = self.CreateColorTable() 00533 if not ret: 00534 GMessage(parent = self, message = _("No valid color rules given.")) 00535 else: 00536 # re-render preview and current map window 00537 self.OnPreview(None) 00538 display = self.parent.GetLayerTree().GetMapDisplay() 00539 if display and display.IsAutoRendered(): 00540 display.GetWindow().UpdateMap(render = True) 00541 00542 return ret 00543 00544 def OnOK(self, event): 00545 """!Apply selected color table and close the dialog""" 00546 if self.OnApply(event): 00547 self.OnCancel(event) 00548 00549 def OnCancel(self, event): 00550 """!Do not apply any changes, remove associated 00551 rendered images and close the dialog""" 00552 self.Map.Clean() 00553 self.Destroy() 00554 00555 def OnSaveRulesFile(self, event): 00556 """!Save color table to file""" 00557 path = event.GetString() 00558 if not os.path.exists(path): 00559 return 00560 00561 rulestxt = '' 00562 for rule in self.rulesPanel.ruleslines.itervalues(): 00563 if 'value' not in rule: 00564 continue 00565 rulestxt += rule['value'] + ' ' + rule['color'] + '\n' 00566 if not rulestxt: 00567 GMessage(message = _("Nothing to save."), 00568 parent = self) 00569 return 00570 00571 fd = open(path, 'w') 00572 fd.write(rulestxt) 00573 fd.close() 00574 00575 def OnLoadRulesFile(self, event): 00576 """!Load color table from file""" 00577 path = event.GetString() 00578 if not os.path.exists(path): 00579 return 00580 00581 self.rulesPanel.Clear() 00582 00583 file = open(path, 'r') 00584 ctable = file.read() 00585 self.ReadColorTable(ctable = ctable) 00586 00587 def ReadColorTable(self, ctable): 00588 """!Read color table 00589 00590 @param table color table in format coming from r.colors.out""" 00591 00592 rulesNumber = len(ctable.splitlines()) 00593 self.rulesPanel.AddRules(rulesNumber) 00594 00595 minim = maxim = count = 0 00596 for line in ctable.splitlines(): 00597 try: 00598 value, color = map(lambda x: x.strip(), line.split(' ')) 00599 except ValueError: 00600 GMessage(parent = self, message = _("Invalid color table format")) 00601 self.rulesPanel.Clear() 00602 return 00603 00604 self.rulesPanel.ruleslines[count]['value'] = value 00605 self.rulesPanel.ruleslines[count]['color'] = color 00606 self.rulesPanel.mainPanel.FindWindowById(count + 1000).SetValue(value) 00607 rgb = list() 00608 for c in color.split(':'): 00609 rgb.append(int(c)) 00610 self.rulesPanel.mainPanel.FindWindowById(count + 2000).SetColour(rgb) 00611 # range 00612 try: 00613 if float(value) < minim: 00614 minim = float(value) 00615 if float(value) > maxim: 00616 maxim = float(value) 00617 except ValueError: # nv, default 00618 pass 00619 count += 1 00620 00621 if self.mapType == 'vector': 00622 # raster min, max is known from r.info 00623 self.properties['min'], self.properties['max'] = minim, maxim 00624 self.SetRangeLabel() 00625 00626 self.OnPreview(tmp = True) 00627 00628 def OnLoadDefaultTable(self, event): 00629 """!Load internal color table""" 00630 self.LoadTable() 00631 00632 def LoadTable(self, mapType = 'raster'): 00633 """!Load current color table (using `r(v).colors.out`) 00634 00635 @param mapType map type (raster or vector)""" 00636 self.rulesPanel.Clear() 00637 00638 if mapType == 'raster': 00639 cmd = ['r.colors.out', 00640 'read=True', 00641 'map=%s' % self.inmap, 00642 'rules=-'] 00643 else: 00644 cmd = ['v.colors.out', 00645 'read=True', 00646 'map=%s' % self.inmap, 00647 'rules=-'] 00648 00649 if self.properties['sourceColumn'] and self.properties['sourceColumn'] != 'cat': 00650 cmd.append('column=%s' % self.properties['sourceColumn']) 00651 00652 cmd = utils.CmdToTuple(cmd) 00653 00654 if self.inmap: 00655 ctable = RunCommand(cmd[0], **cmd[1]) 00656 else: 00657 self.OnPreview() 00658 return 00659 00660 self.ReadColorTable(ctable = ctable) 00661 00662 def CreateColorTable(self, tmp = False): 00663 """!Creates color table 00664 00665 @return True on success 00666 @return False on failure 00667 """ 00668 rulestxt = '' 00669 00670 for rule in self.rulesPanel.ruleslines.itervalues(): 00671 if 'value' not in rule: # skip empty rules 00672 continue 00673 00674 if rule['value'] not in ('nv', 'default') and \ 00675 rule['value'][-1] != '%' and \ 00676 not self._IsNumber(rule['value']): 00677 GError(_("Invalid rule value '%s'. Unable to apply color table.") % rule['value'], 00678 parent = self) 00679 return False 00680 00681 rulestxt += rule['value'] + ' ' + rule['color'] + '\n' 00682 00683 if not rulestxt: 00684 return False 00685 00686 gtemp = utils.GetTempfile() 00687 output = open(gtemp, "w") 00688 try: 00689 output.write(rulestxt) 00690 finally: 00691 output.close() 00692 00693 cmd = ['%s.colors' % self.mapType[0], #r.colors/v.colors 00694 'map=%s' % self.inmap, 00695 'rules=%s' % gtemp] 00696 if self.mapType == 'vector' and self.properties['sourceColumn'] \ 00697 and self.properties['sourceColumn'] != 'cat': 00698 cmd.append('column=%s' % self.properties['sourceColumn']) 00699 cmd = utils.CmdToTuple(cmd) 00700 ret = RunCommand(cmd[0], **cmd[1]) 00701 if ret != 0: 00702 return False 00703 00704 return True 00705 00706 def DoPreview(self, ltype, cmdlist): 00707 """!Update preview (based on computational region)""" 00708 00709 if not self.layer: 00710 self.layer = self.Map.AddLayer(type = ltype, name = 'preview', command = cmdlist, 00711 l_active = True, l_hidden = False, l_opacity = 1.0, 00712 l_render = False) 00713 else: 00714 self.layer.SetCmd(cmdlist) 00715 00716 # apply new color table and display preview 00717 self.CreateColorTable(tmp = True) 00718 self.preview.UpdatePreview() 00719 00720 def RunHelp(self, cmd): 00721 """!Show GRASS manual page""" 00722 RunCommand('g.manual', 00723 quiet = True, 00724 parent = self, 00725 entry = cmd) 00726 00727 def _IsNumber(self, s): 00728 """!Check if 's' is a number""" 00729 try: 00730 float(s) 00731 return True 00732 except ValueError: 00733 return False 00734 00735 00736 class RasterColorTable(ColorTable): 00737 def __init__(self, parent, **kwargs): 00738 """!Dialog for interactively entering color rules for raster maps""" 00739 00740 self.mapType = 'raster' 00741 self.attributeType = 'color' 00742 self.colorTable = True 00743 # raster properties 00744 self.properties = { 00745 # min cat in raster map 00746 'min' : None, 00747 # max cat in raster map 00748 'max' : None, 00749 } 00750 00751 ColorTable.__init__(self, parent, 00752 title = _('Create new color table for raster map'), **kwargs) 00753 00754 self._initLayer() 00755 00756 # self.SetMinSize(self.GetSize()) 00757 self.SetMinSize((650, 700)) 00758 00759 self.CentreOnScreen() 00760 self.Show() 00761 00762 def _doLayout(self): 00763 """!Do main layout""" 00764 sizer = wx.BoxSizer(wx.VERTICAL) 00765 # 00766 # map selection 00767 # 00768 mapSelection = self._createMapSelection(parent = self.panel) 00769 sizer.Add(item = mapSelection, proportion = 0, 00770 flag = wx.ALL | wx.EXPAND, border = 5) 00771 # 00772 # manage extern tables 00773 # 00774 fileSelection = self._createFileSelection(parent = self.panel) 00775 sizer.Add(item = fileSelection, proportion = 0, 00776 flag = wx.ALL | wx.EXPAND, border = 5) 00777 # 00778 # body & preview 00779 # 00780 bodySizer = self._createBody(parent = self.panel) 00781 sizer.Add(item = bodySizer, proportion = 1, 00782 flag = wx.ALL | wx.EXPAND, border = 5) 00783 # 00784 # buttons 00785 # 00786 btnSizer = self._createButtons(parent = self.panel) 00787 sizer.Add(item = wx.StaticLine(parent = self.panel, id = wx.ID_ANY, 00788 style = wx.LI_HORIZONTAL), proportion = 0, 00789 flag = wx.EXPAND | wx.ALL, border = 5) 00790 00791 sizer.Add(item = btnSizer, proportion = 0, 00792 flag = wx.ALL | wx.ALIGN_RIGHT, border = 5) 00793 00794 self.panel.SetSizer(sizer) 00795 sizer.Layout() 00796 sizer.Fit(self.panel) 00797 self.Layout() 00798 00799 def OnSelectionInput(self, event): 00800 """!Raster map selected""" 00801 if event: 00802 self.inmap = event.GetString() 00803 00804 self.loadRules.SetValue('') 00805 self.saveRules.SetValue('') 00806 if self.inmap: 00807 if not grass.find_file(name = self.inmap, element = 'cell')['file']: 00808 self.inmap = None 00809 00810 if not self.inmap: 00811 self.btnPreview.Enable(False) 00812 self.btnOK.Enable(False) 00813 self.btnApply.Enable(False) 00814 self.LoadTable() 00815 return 00816 00817 info = grass.raster_info(map = self.inmap) 00818 00819 if info: 00820 self.properties['min'] = info['min'] 00821 self.properties['max'] = info['max'] 00822 self.LoadTable() 00823 else: 00824 self.inmap = '' 00825 self.properties['min'] = self.properties['max'] = None 00826 self.btnPreview.Enable(False) 00827 self.btnOK.Enable(False) 00828 self.btnApply.Enable(False) 00829 self.preview.EraseMap() 00830 self.cr_label.SetLabel(_('Enter raster category values or percents')) 00831 return 00832 00833 if info['datatype'] == 'CELL': 00834 mapRange = _('range') 00835 else: 00836 mapRange = _('fp range') 00837 self.cr_label.SetLabel(_('Enter raster category values or percents (%(range)s = %(min)d-%(max)d)') % 00838 { 'range' : mapRange, 00839 'min' : self.properties['min'], 00840 'max' : self.properties['max'] }) 00841 00842 self.btnPreview.Enable() 00843 self.btnOK.Enable() 00844 self.btnApply.Enable() 00845 00846 00847 def OnPreview(self, tmp = True): 00848 """!Update preview (based on computational region)""" 00849 if not self.inmap: 00850 self.preview.EraseMap() 00851 return 00852 00853 cmdlist = ['d.rast', 00854 'map=%s' % self.inmap] 00855 ltype = 'raster' 00856 00857 # find existing color table and copy to temp file 00858 try: 00859 name, mapset = self.inmap.split('@') 00860 except ValueError: 00861 name = self.inmap 00862 mapset = grass.find_file(self.inmap, element = 'cell')['mapset'] 00863 if not mapset: 00864 return 00865 old_colrtable = None 00866 if mapset == grass.gisenv()['MAPSET']: 00867 old_colrtable = grass.find_file(name = name, element = 'colr')['file'] 00868 else: 00869 old_colrtable = grass.find_file(name = name, element = 'colr2/' + mapset)['file'] 00870 00871 if old_colrtable: 00872 colrtemp = utils.GetTempfile() 00873 shutil.copyfile(old_colrtable, colrtemp) 00874 00875 ColorTable.DoPreview(self, ltype, cmdlist) 00876 00877 # restore previous color table 00878 if tmp: 00879 if old_colrtable: 00880 shutil.copyfile(colrtemp, old_colrtable) 00881 os.remove(colrtemp) 00882 else: 00883 RunCommand('r.colors', 00884 parent = self, 00885 flags = 'r', 00886 map = self.inmap) 00887 00888 def OnHelp(self, event): 00889 """!Show GRASS manual page""" 00890 cmd = 'r.colors' 00891 ColorTable.RunHelp(self, cmd = cmd) 00892 00893 class VectorColorTable(ColorTable): 00894 def __init__(self, parent, attributeType, **kwargs): 00895 """!Dialog for interactively entering color rules for vector maps""" 00896 # dialog attributes 00897 self.mapType = 'vector' 00898 self.attributeType = attributeType # color, size, width 00899 # in version 7 v.colors used, otherwise color column only 00900 self.version7 = int(grass.version()['version'].split('.')[0]) >= 7 00901 self.colorTable = False 00902 self.updateColumn = True 00903 # vector properties 00904 self.properties = { 00905 # vector layer for attribute table to use for setting color 00906 'layer' : 1, 00907 # vector attribute table used for setting color 00908 'table' : '', 00909 # vector attribute column for assigning colors 00910 'sourceColumn' : '', 00911 # vector attribute column to use for loading colors 00912 'loadColumn' : '', 00913 # vector attribute column to use for storing colors 00914 'storeColumn' : '', 00915 # vector attribute column for temporary storing colors 00916 'tmpColumn' : 'tmp_0', 00917 # min value of attribute column/vector color table 00918 'min': None, 00919 # max value of attribute column/vector color table 00920 'max': None 00921 } 00922 self.columnsProp = {'color': {'name': 'GRASSRGB', 'type1': 'varchar(11)', 'type2': ['character']}, 00923 'size' : {'name': 'GRASSSIZE', 'type1': 'integer', 'type2': ['integer']}, 00924 'width': {'name': 'GRASSWIDTH', 'type1': 'integer', 'type2': ['integer']}} 00925 ColorTable.__init__(self, parent = parent, 00926 title = _('Create new color rules for vector map'), **kwargs) 00927 00928 # additional bindings for vector color management 00929 self.Bind(wx.EVT_COMBOBOX, self.OnLayerSelection, self.layerSelect) 00930 self.Bind(wx.EVT_COMBOBOX, self.OnSourceColumnSelection, self.sourceColumn) 00931 self.Bind(wx.EVT_COMBOBOX, self.OnFromColSelection, self.fromColumn) 00932 self.Bind(wx.EVT_COMBOBOX, self.OnToColSelection, self.toColumn) 00933 self.Bind(wx.EVT_BUTTON, self.OnAddColumn, self.addColumn) 00934 00935 self._initLayer() 00936 if self.colorTable: 00937 self.cr_label.SetLabel(_("Enter vector attribute values or percents:")) 00938 else: 00939 self.cr_label.SetLabel(_("Enter vector attribute values:")) 00940 00941 #self.SetMinSize(self.GetSize()) 00942 self.SetMinSize((650, 700)) 00943 00944 self.CentreOnScreen() 00945 self.Show() 00946 00947 def _createVectorAttrb(self, parent): 00948 """!Create part of dialog with layer/column selection""" 00949 inputBox = wx.StaticBox(parent = parent, id = wx.ID_ANY, 00950 label = " %s " % _("Select vector columns")) 00951 cb_vl_label = wx.StaticText(parent, id = wx.ID_ANY, 00952 label = _('Layer:')) 00953 cb_vc_label = wx.StaticText(parent, id = wx.ID_ANY, 00954 label = _('Attribute column:')) 00955 00956 if self.attributeType == 'color': 00957 labels = [_("Load color from column:"), _("Save color to column:")] 00958 elif self.attributeType == 'size': 00959 labels = [_("Load size from column:"), _("Save size to column:")] 00960 elif self.attributeType == 'width': 00961 labels = [_("Load width from column:"), _("Save width to column:")] 00962 00963 if self.version7 and self.attributeType == 'color': 00964 self.useColumn = wx.CheckBox(parent, id = wx.ID_ANY, 00965 label = _("Use color column instead of color table:")) 00966 self.useColumn.Bind(wx.EVT_CHECKBOX, self.OnCheckColumn) 00967 00968 fromColumnLabel = wx.StaticText(parent, id = wx.ID_ANY, 00969 label = labels[0]) 00970 toColumnLabel = wx.StaticText(parent, id = wx.ID_ANY, 00971 label = labels[1]) 00972 00973 self.rgb_range_label = wx.StaticText(parent, id = wx.ID_ANY) 00974 self.layerSelect = LayerSelect(parent) 00975 self.sourceColumn = ColumnSelect(parent) 00976 self.fromColumn = ColumnSelect(parent) 00977 self.toColumn = ColumnSelect(parent) 00978 self.addColumn = wx.Button(parent, id = wx.ID_ANY, 00979 label = _('Add column')) 00980 self.addColumn.SetToolTipString(_("Add GRASSRGB column to current attribute table.")) 00981 00982 # layout 00983 inputSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL) 00984 vSizer = wx.GridBagSizer(hgap = 5, vgap = 5) 00985 row = 0 00986 vSizer.Add(cb_vl_label, pos = (row, 0), 00987 flag = wx.ALIGN_CENTER_VERTICAL) 00988 vSizer.Add(self.layerSelect, pos = (row, 1), 00989 flag = wx.ALIGN_CENTER_VERTICAL) 00990 row += 1 00991 vSizer.Add(cb_vc_label, pos = (row, 0), 00992 flag = wx.ALIGN_CENTER_VERTICAL) 00993 vSizer.Add(self.sourceColumn, pos = (row, 1), 00994 flag = wx.ALIGN_CENTER_VERTICAL) 00995 vSizer.Add(self.rgb_range_label, pos = (row, 2), 00996 flag = wx.ALIGN_CENTER_VERTICAL) 00997 row += 1 00998 if self.version7 and self.attributeType == 'color': 00999 vSizer.Add(self.useColumn, pos = (row, 0), span = (1, 2), 01000 flag = wx.ALIGN_CENTER_VERTICAL) 01001 row += 1 01002 01003 vSizer.Add(fromColumnLabel, pos = (row, 0), 01004 flag = wx.ALIGN_CENTER_VERTICAL) 01005 vSizer.Add(self.fromColumn, pos = (row, 1), 01006 flag = wx.ALIGN_CENTER_VERTICAL) 01007 row += 1 01008 vSizer.Add(toColumnLabel, pos = (row, 0), 01009 flag = wx.ALIGN_CENTER_VERTICAL) 01010 vSizer.Add(self.toColumn, pos = (row, 1), 01011 flag = wx.ALIGN_CENTER_VERTICAL) 01012 vSizer.Add(self.addColumn, pos = (row, 2), 01013 flag = wx.ALIGN_CENTER_VERTICAL) 01014 inputSizer.Add(item = vSizer, 01015 flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border = 5) 01016 self.colorColumnSizer = vSizer 01017 return inputSizer 01018 01019 def _doLayout(self): 01020 """!Do main layout""" 01021 scrollPanel = scrolled.ScrolledPanel(parent = self.panel, id = wx.ID_ANY, 01022 style = wx.TAB_TRAVERSAL) 01023 scrollPanel.SetupScrolling() 01024 sizer = wx.BoxSizer(wx.VERTICAL) 01025 # 01026 # map selection 01027 # 01028 mapSelection = self._createMapSelection(parent = scrollPanel) 01029 sizer.Add(item = mapSelection, proportion = 0, 01030 flag = wx.ALL | wx.EXPAND, border = 5) 01031 # 01032 # manage extern tables 01033 # 01034 if self.version7 and self.attributeType == 'color': 01035 self.cp = wx.CollapsiblePane(scrollPanel, label = _("Import or export color table"), 01036 winid = wx.ID_ANY, 01037 style = wx.CP_DEFAULT_STYLE|wx.CP_NO_TLW_RESIZE) 01038 self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnPaneChanged, self.cp) 01039 01040 self._createFileSelection(parent = self.cp.GetPane()) 01041 sizer.Add(item = self.cp, proportion = 0, 01042 flag = wx.ALL | wx.EXPAND, border = 5) 01043 # 01044 # set vector attributes 01045 # 01046 vectorAttrb = self._createVectorAttrb(parent = scrollPanel) 01047 sizer.Add(item = vectorAttrb, proportion = 0, 01048 flag = wx.ALL | wx.EXPAND, border = 5) 01049 # 01050 # body & preview 01051 # 01052 bodySizer = self._createBody(parent = scrollPanel) 01053 sizer.Add(item = bodySizer, proportion = 1, 01054 flag = wx.ALL | wx.EXPAND, border = 5) 01055 01056 scrollPanel.SetSizer(sizer) 01057 scrollPanel.Fit() 01058 01059 # 01060 # buttons 01061 # 01062 btnSizer = self._createButtons(self.panel) 01063 01064 mainsizer = wx.BoxSizer(wx.VERTICAL) 01065 mainsizer.Add(scrollPanel, proportion = 1, flag = wx.EXPAND | wx.ALL, border = 5) 01066 mainsizer.Add(item = wx.StaticLine(parent = self.panel, id = wx.ID_ANY, 01067 style = wx.LI_HORIZONTAL), proportion = 0, 01068 flag = wx.EXPAND | wx.ALL, border = 5) 01069 mainsizer.Add(item = btnSizer, proportion = 0, 01070 flag = wx.ALL | wx.ALIGN_RIGHT | wx.EXPAND, border = 5) 01071 01072 self.panel.SetSizer(mainsizer) 01073 mainsizer.Layout() 01074 mainsizer.Fit(self.panel) 01075 self.Layout() 01076 01077 def OnPaneChanged(self, event = None): 01078 # redo the layout 01079 self.Layout() 01080 # and also change the labels 01081 if self.cp.IsExpanded(): 01082 self.cp.SetLabel('') 01083 else: 01084 self.cp.SetLabel(_("Import or export color table")) 01085 01086 def CheckMapset(self): 01087 """!Check if current vector is in current mapset""" 01088 if grass.find_file(name = self.inmap, 01089 element = 'vector')['mapset'] == grass.gisenv()['MAPSET']: 01090 return True 01091 else: 01092 return False 01093 01094 def NoConnection(self, vectorName): 01095 dlg = wx.MessageDialog(parent = self, 01096 message = _("Database connection for vector map <%s> " 01097 "is not defined in DB file. Do you want to create and " 01098 "connect new attribute table?") % vectorName, 01099 caption = _("No database connection defined"), 01100 style = wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION | wx.CENTRE) 01101 if dlg.ShowModal() == wx.ID_YES: 01102 dlg.Destroy() 01103 GUI(parent = self).ParseCommand(['v.db.addtable', 'map=' + self.inmap], 01104 completed = (self.CreateAttrTable, self.inmap, '')) 01105 else: 01106 dlg.Destroy() 01107 01108 def OnCheckColumn(self, event): 01109 """!Use color column instead of color table""" 01110 if self.useColumn.GetValue(): 01111 self.properties['loadColumn'] = self.fromColumn.GetStringSelection() 01112 self.properties['storeColumn'] = self.toColumn.GetStringSelection() 01113 self.fromColumn.Enable(True) 01114 self.toColumn.Enable(True) 01115 self.colorTable = False 01116 01117 if self.properties['loadColumn']: 01118 self.LoadTable() 01119 else: 01120 self.rulesPanel.Clear() 01121 else: 01122 self.properties['loadColumn'] = '' 01123 self.properties['storeColumn'] = '' 01124 self.fromColumn.Enable(False) 01125 self.toColumn.Enable(False) 01126 self.colorTable = True 01127 self.LoadTable() 01128 01129 def EnableVectorAttributes(self, enable): 01130 """!Enable/disable part of dialog connected with db""" 01131 for child in self.colorColumnSizer.GetChildren(): 01132 child.GetWindow().Enable(enable) 01133 01134 def DisableClearAll(self): 01135 """!Enable, disable the whole dialog""" 01136 self.rulesPanel.Clear() 01137 self.EnableVectorAttributes(False) 01138 self.btnPreview.Enable(False) 01139 self.btnOK.Enable(False) 01140 self.btnApply.Enable(False) 01141 self.preview.EraseMap() 01142 01143 def OnSelectionInput(self, event): 01144 """!Vector map selected""" 01145 if event: 01146 if self.inmap: 01147 # switch to another map -> delete temporary column 01148 self.DeleteTemporaryColumn() 01149 self.inmap = event.GetString() 01150 01151 if self.version7 and self.attributeType == 'color': 01152 self.loadRules.SetValue('') 01153 self.saveRules.SetValue('') 01154 01155 if self.inmap: 01156 if not grass.find_file(name = self.inmap, element = 'vector')['file']: 01157 self.inmap = None 01158 01159 self.UpdateDialog() 01160 01161 def UpdateDialog(self): 01162 """!Update dialog after map selection""" 01163 01164 if not self.inmap: 01165 self.DisableClearAll() 01166 return 01167 01168 if self.inmap and not self.CheckMapset(): 01169 # currently v.colors need the map to be in current mapset 01170 if self.version7 and self.attributeType == 'color': 01171 message = _("Selected map <%(map)s> is not in current mapset <%(mapset)s>. " 01172 "Color rules cannot be edited.") % \ 01173 { 'map' : self.inmap, 01174 'mapset' : grass.gisenv()['MAPSET'] } 01175 else: 01176 message = _("Selected map <%(map)s> is not in current mapset <%(mapset)s>. " 01177 "Attribute table cannot be edited.") % \ 01178 { 'map' : self.inmap, 01179 'mapset' : grass.gisenv()['MAPSET'] } 01180 wx.CallAfter(GMessage, parent = self, message = message) 01181 self.DisableClearAll() 01182 return 01183 01184 # check for db connection 01185 self.dbInfo = VectorDBInfo(self.inmap) 01186 enable = True 01187 if not len(self.dbInfo.layers): # no connection 01188 if not (self.version7 and self.attributeType == 'color'): # otherwise it doesn't matter 01189 wx.CallAfter(self.NoConnection, self.inmap) 01190 enable = False 01191 for combo in (self.layerSelect, self.sourceColumn, self.fromColumn, self.toColumn): 01192 combo.SetValue("") 01193 combo.Clear() 01194 for prop in ('sourceColumn', 'loadColumn', 'storeColumn'): 01195 self.properties[prop] = '' 01196 self.EnableVectorAttributes(False) 01197 else: # db connection exist 01198 # initialize layer selection combobox 01199 self.EnableVectorAttributes(True) 01200 self.layerSelect.InsertLayers(self.inmap) 01201 # initialize attribute table for layer=1 01202 self.properties['layer'] = self.layerSelect.GetString(0) 01203 self.layerSelect.SetStringSelection(self.properties['layer']) 01204 layer = int(self.properties['layer']) 01205 self.properties['table'] = self.dbInfo.layers[layer]['table'] 01206 01207 if self.attributeType == 'color': 01208 self.AddTemporaryColumn(type = 'varchar(11)') 01209 else: 01210 self.AddTemporaryColumn(type = 'integer') 01211 01212 # initialize column selection comboboxes 01213 01214 self.OnLayerSelection(event = None) 01215 01216 if self.version7 and self.attributeType == 'color': 01217 self.useColumn.SetValue(False) 01218 self.OnCheckColumn(event = None) 01219 01220 self.LoadTable() 01221 01222 self.btnPreview.Enable(enable) 01223 self.btnOK.Enable(enable) 01224 self.btnApply.Enable(enable) 01225 01226 def AddTemporaryColumn(self, type): 01227 """!Add temporary column to not overwrite the original values, 01228 need to be deleted when closing dialog and unloading map 01229 01230 @param type type of column (e.g. vachar(11))""" 01231 # because more than one dialog with the same map can be opened we must test column name and 01232 # create another one 01233 while self.properties['tmpColumn'] in self.dbInfo.GetTableDesc(self.properties['table']).keys(): 01234 name, idx = self.properties['tmpColumn'].split('_') 01235 idx = int(idx) 01236 idx += 1 01237 self.properties['tmpColumn'] = name + '_' + str(idx) 01238 01239 if self.version7: 01240 modul = 'v.db.addcolumn' 01241 else: 01242 modul = 'v.db.addcol' 01243 ret = RunCommand(modul, 01244 parent = self, 01245 map = self.inmap, 01246 layer = self.properties['layer'], 01247 column = '%s %s' % (self.properties['tmpColumn'], type)) 01248 01249 def DeleteTemporaryColumn(self): 01250 """!Delete temporary column""" 01251 if self.inmap: 01252 if self.version7: 01253 modul = 'v.db.dropcolumn' 01254 else: 01255 modul = 'v.db.dropcol' 01256 ret = RunCommand(modul, 01257 map = self.inmap, 01258 layer = self.properties['layer'], 01259 column = self.properties['tmpColumn']) 01260 01261 def OnLayerSelection(self, event): 01262 # reset choices in column selection comboboxes if layer changes 01263 vlayer = int(self.layerSelect.GetStringSelection()) 01264 self.sourceColumn.InsertColumns(vector = self.inmap, layer = vlayer, 01265 type = ['integer', 'double precision'], dbInfo = self.dbInfo, 01266 excludeCols = ['tmpColumn']) 01267 self.sourceColumn.SetStringSelection('cat') 01268 self.properties['sourceColumn'] = self.sourceColumn.GetString(0) 01269 01270 if self.attributeType == 'color': 01271 type = ['character'] 01272 else: 01273 type = ['integer'] 01274 self.fromColumn.InsertColumns(vector = self.inmap, layer = vlayer, type = type, 01275 dbInfo = self.dbInfo, excludeCols = ['tmpColumn']) 01276 self.toColumn.InsertColumns(vector = self.inmap, layer = vlayer, type = type, 01277 dbInfo = self.dbInfo, excludeCols = ['tmpColumn']) 01278 01279 found = self.fromColumn.FindString(self.columnsProp[self.attributeType]['name']) 01280 if found != wx.NOT_FOUND: 01281 self.fromColumn.SetSelection(found) 01282 self.toColumn.SetSelection(found) 01283 self.properties['loadColumn'] = self.fromColumn.GetString(found) 01284 self.properties['storeColumn'] = self.toColumn.GetString(found) 01285 else: 01286 self.properties['loadColumn'] = '' 01287 self.properties['storeColumn'] = '' 01288 01289 if event: 01290 self.LoadTable() 01291 self.Update() 01292 01293 def OnSourceColumnSelection(self, event): 01294 self.properties['sourceColumn'] = event.GetString() 01295 01296 self.LoadTable() 01297 01298 def OnAddColumn(self, event): 01299 """!Add GRASS(RGB,SIZE,WIDTH) column if it doesn't exist""" 01300 if self.columnsProp[self.attributeType]['name'] not in self.fromColumn.GetItems(): 01301 if self.version7: 01302 modul = 'v.db.addcolumn' 01303 else: 01304 modul = 'v.db.addcol' 01305 ret = RunCommand(modul, 01306 map = self.inmap, 01307 layer = self.properties['layer'], 01308 columns = '%s %s' % (self.columnsProp[self.attributeType]['name'], 01309 self.columnsProp[self.attributeType]['type1'])) 01310 self.toColumn.InsertColumns(self.inmap, self.properties['layer'], 01311 type = self.columnsProp[self.attributeType]['type2']) 01312 self.toColumn.SetStringSelection(self.columnsProp[self.attributeType]['name']) 01313 self.properties['storeColumn'] = self.toColumn.GetStringSelection() 01314 01315 self.LoadTable() 01316 else: 01317 GMessage(parent = self, 01318 message = _("%s column already exists.") % \ 01319 self.columnsProp[self.attributeType]['name']) 01320 01321 def CreateAttrTable(self, dcmd, layer, params, propwin): 01322 """!Create attribute table""" 01323 if dcmd: 01324 cmd = utils.CmdToTuple(dcmd) 01325 ret = RunCommand(cmd[0], **cmd[1]) 01326 if ret == 0: 01327 self.OnSelectionInput(None) 01328 return True 01329 01330 for combo in (self.layerSelect, self.sourceColumn, self.fromColumn, self.toColumn): 01331 combo.SetValue("") 01332 combo.Disable() 01333 return False 01334 01335 def LoadTable(self): 01336 """!Load table""" 01337 if self.colorTable: 01338 ColorTable.LoadTable(self, mapType = 'vector') 01339 else: 01340 self.LoadRulesFromColumn() 01341 01342 def LoadRulesFromColumn(self): 01343 """!Load current column (GRASSRGB, size column)""" 01344 01345 self.rulesPanel.Clear() 01346 if not self.properties['sourceColumn']: 01347 self.preview.EraseMap() 01348 return 01349 01350 busy = wx.BusyInfo(message = _("Please wait, loading data from attribute table..."), 01351 parent = self) 01352 wx.Yield() 01353 01354 columns = self.properties['sourceColumn'] 01355 if self.properties['loadColumn']: 01356 columns += ',' + self.properties['loadColumn'] 01357 01358 sep = ';' 01359 if self.inmap: 01360 outFile = tempfile.NamedTemporaryFile(mode = 'w+b') 01361 ret = RunCommand('v.db.select', 01362 quiet = True, 01363 flags = 'c', 01364 map = self.inmap, 01365 layer = self.properties['layer'], 01366 columns = columns, 01367 fs = sep, 01368 stdout = outFile) 01369 else: 01370 self.preview.EraseMap() 01371 busy.Destroy() 01372 return 01373 01374 outFile.seek(0) 01375 i = 0 01376 minim = maxim = 0.0 01377 limit = 1000 01378 01379 colvallist = [] 01380 readvals = False 01381 01382 while True: 01383 # os.linesep doesn't work here (MSYS) 01384 record = outFile.readline().replace('\n', '') 01385 if not record: 01386 break 01387 self.rulesPanel.ruleslines[i] = {} 01388 01389 if not self.properties['loadColumn']: 01390 col1 = record 01391 col2 = None 01392 else: 01393 col1, col2 = record.split(sep) 01394 01395 if float(col1) < minim: 01396 minim = float(col1) 01397 if float(col1) > maxim: 01398 maxim = float(col1) 01399 01400 01401 # color rules list should only have unique values of col1, not all records 01402 if col1 not in colvallist: 01403 self.rulesPanel.ruleslines[i]['value'] = col1 01404 self.rulesPanel.ruleslines[i][self.attributeType] = col2 01405 01406 colvallist.append(col1) 01407 i += 1 01408 01409 if i > limit and readvals == False: 01410 dlg = wx.MessageDialog (parent = self, message = _( 01411 "Number of loaded records reached %d, " 01412 "displaying all the records will be time-consuming " 01413 "and may lead to computer freezing, " 01414 "do you still want to continue?") % i, 01415 caption = _("Too many records"), 01416 style = wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) 01417 if dlg.ShowModal() == wx.ID_YES: 01418 readvals = True 01419 dlg.Destroy() 01420 else: 01421 busy.Destroy() 01422 dlg.Destroy() 01423 self.updateColumn = False 01424 return 01425 01426 self.rulesPanel.AddRules(i, start = True) 01427 ret = self.rulesPanel.LoadRules() 01428 01429 self.properties['min'], self.properties['max'] = minim, maxim 01430 self.SetRangeLabel() 01431 01432 if ret: 01433 self.OnPreview() 01434 else: 01435 self.rulesPanel.Clear() 01436 01437 busy.Destroy() 01438 01439 def SetRangeLabel(self): 01440 """!Set labels with info about attribute column range""" 01441 01442 if self.properties['sourceColumn']: 01443 ctype = self.dbInfo.GetTableDesc(self.properties['table'])[self.properties['sourceColumn']]['ctype'] 01444 else: 01445 ctype = int 01446 01447 range = '' 01448 if self.properties['min'] or self.properties['max']: 01449 if ctype == float: 01450 range = "%s: %.1f - %.1f)" % (_("range"), 01451 self.properties['min'], self.properties['max']) 01452 elif ctype == int: 01453 range = "%s: %d - %d)" % (_("range"), 01454 self.properties['min'], self.properties['max']) 01455 if range: 01456 if self.colorTable: 01457 self.cr_label.SetLabel(_("Enter vector attribute values or percents %s:") % range) 01458 else: 01459 self.cr_label.SetLabel(_("Enter vector attribute values %s:") % range) 01460 else: 01461 if self.colorTable: 01462 self.cr_label.SetLabel(_("Enter vector attribute values or percents:")) 01463 else: 01464 self.cr_label.SetLabel(_("Enter vector attribute values:")) 01465 01466 def OnFromColSelection(self, event): 01467 """!Selection in combobox (for loading values) changed""" 01468 self.properties['loadColumn'] = event.GetString() 01469 01470 self.LoadTable() 01471 01472 def OnToColSelection(self, event): 01473 """!Selection in combobox (for storing values) changed""" 01474 self.properties['storeColumn'] = event.GetString() 01475 01476 def OnPreview(self, event = None, tmp = True): 01477 """!Update preview (based on computational region)""" 01478 if self.colorTable: 01479 self.OnTablePreview(tmp) 01480 else: 01481 self.OnColumnPreview() 01482 01483 def OnTablePreview(self, tmp): 01484 """!Update preview (based on computational region)""" 01485 if not self.inmap: 01486 self.preview.EraseMap() 01487 return 01488 01489 ltype = 'vector' 01490 cmdlist = ['d.vect', 01491 'map=%s' % self.inmap] 01492 01493 # find existing color table and copy to temp file 01494 old_colrtable = None 01495 path = grass.find_file(name = self.inmap, element = 'vector')['file'] 01496 01497 if os.path.exists(os.path.join(path, 'colr')): 01498 old_colrtable = os.path.join(path, 'colr') 01499 colrtemp = utils.GetTempfile() 01500 shutil.copyfile(old_colrtable, colrtemp) 01501 01502 ColorTable.DoPreview(self, ltype, cmdlist) 01503 01504 # restore previous color table 01505 if tmp: 01506 if old_colrtable: 01507 shutil.copyfile(colrtemp, old_colrtable) 01508 os.remove(colrtemp) 01509 else: 01510 RunCommand('v.colors', 01511 parent = self, 01512 flags = 'r', 01513 map = self.inmap) 01514 01515 def OnColumnPreview(self): 01516 """!Update preview (based on computational region)""" 01517 if not self.inmap or not self.properties['tmpColumn']: 01518 self.preview.EraseMap() 01519 return 01520 01521 cmdlist = ['d.vect', 01522 'map=%s' % self.inmap, 01523 'type=point,line,boundary,area'] 01524 01525 if self.attributeType == 'color': 01526 cmdlist.append('flags=a') 01527 cmdlist.append('rgb_column=%s' % self.properties['tmpColumn']) 01528 elif self.attributeType == 'size': 01529 cmdlist.append('size_column=%s' % self.properties['tmpColumn']) 01530 elif self.attributeType == 'width': 01531 cmdlist.append('width_column=%s' % self.properties['tmpColumn']) 01532 01533 ltype = 'vector' 01534 01535 ColorTable.DoPreview(self, ltype, cmdlist) 01536 01537 def OnHelp(self, event): 01538 """!Show GRASS manual page""" 01539 cmd = 'v.colors' 01540 ColorTable.RunHelp(self, cmd = cmd) 01541 01542 def UseAttrColumn(self, useAttrColumn): 01543 """!Find layers and apply the changes in d.vect command""" 01544 layers = self.parent.curr_page.maptree.FindItemByData(key = 'name', value = self.inmap) 01545 if not layers: 01546 return 01547 for layer in layers: 01548 if self.parent.curr_page.maptree.GetPyData(layer)[0]['type'] != 'vector': 01549 continue 01550 cmdlist = self.parent.curr_page.maptree.GetPyData(layer)[0]['maplayer'].GetCmd() 01551 01552 if self.attributeType == 'color': 01553 if useAttrColumn: 01554 cmdlist[1].update({'flags': 'a'}) 01555 cmdlist[1].update({'rgb_column': self.properties['storeColumn']}) 01556 else: 01557 if 'flags' in cmdlist[1]: 01558 cmdlist[1]['flags'] = cmdlist[1]['flags'].replace('a', '') 01559 cmdlist[1].pop('rgb_column', None) 01560 elif self.attributeType == 'size': 01561 cmdlist[1].update({'size_column': self.properties['storeColumn']}) 01562 elif self.attributeType == 'width': 01563 cmdlist[1].update({'width_column' :self.properties['storeColumn']}) 01564 self.parent.curr_page.maptree.GetPyData(layer)[0]['cmd'] = cmdlist 01565 01566 def CreateColorTable(self, tmp = False): 01567 """!Create color rules (color table or color column)""" 01568 if self.colorTable: 01569 ret = ColorTable.CreateColorTable(self) 01570 else: 01571 if self.updateColumn: 01572 ret = self.UpdateColorColumn(tmp) 01573 else: 01574 ret = True 01575 01576 return ret 01577 01578 def UpdateColorColumn(self, tmp): 01579 """!Creates color table 01580 01581 @return True on success 01582 @return False on failure 01583 """ 01584 rulestxt = '' 01585 01586 for rule in self.rulesPanel.ruleslines.itervalues(): 01587 if 'value' not in rule: # skip empty rules 01588 break 01589 01590 if tmp: 01591 rgb_col = self.properties['tmpColumn'] 01592 else: 01593 rgb_col = self.properties['storeColumn'] 01594 if not self.properties['storeColumn']: 01595 GMessage(parent = self.parent, 01596 message = _("Please select column to save values to.")) 01597 01598 rulestxt += "UPDATE %s SET %s='%s' WHERE %s ;\n" % (self.properties['table'], 01599 rgb_col, 01600 rule[self.attributeType], 01601 rule['value']) 01602 if not rulestxt: 01603 return False 01604 01605 gtemp = utils.GetTempfile() 01606 output = open(gtemp, "w") 01607 try: 01608 output.write(rulestxt) 01609 finally: 01610 output.close() 01611 01612 RunCommand('db.execute', 01613 parent = self, 01614 input = gtemp) 01615 01616 return True 01617 01618 def OnCancel(self, event): 01619 """!Do not apply any changes and close the dialog""" 01620 self.DeleteTemporaryColumn() 01621 self.Map.Clean() 01622 self.Destroy() 01623 01624 def OnApply(self, event): 01625 """!Apply selected color table 01626 01627 @return True on success otherwise False 01628 """ 01629 if self.colorTable: 01630 self.UseAttrColumn(False) 01631 else: 01632 if not self.properties['storeColumn']: 01633 GError(_("No color column defined. Operation canceled."), 01634 parent = self) 01635 return 01636 01637 self.UseAttrColumn(True) 01638 01639 return ColorTable.OnApply(self, event) 01640 01641 class BufferedWindow(wx.Window): 01642 """!A Buffered window class""" 01643 def __init__(self, parent, id, 01644 style = wx.NO_FULL_REPAINT_ON_RESIZE, 01645 Map = None, **kwargs): 01646 01647 wx.Window.__init__(self, parent, id, style = style, **kwargs) 01648 01649 self.parent = parent 01650 self.Map = Map 01651 01652 # re-render the map from GRASS or just redraw image 01653 self.render = True 01654 # indicates whether or not a resize event has taken place 01655 self.resize = False 01656 01657 # 01658 # event bindings 01659 # 01660 self.Bind(wx.EVT_PAINT, self.OnPaint) 01661 self.Bind(wx.EVT_IDLE, self.OnIdle) 01662 self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None) 01663 01664 # 01665 # render output objects 01666 # 01667 # image file to be rendered 01668 self.mapfile = None 01669 # wx.Image object (self.mapfile) 01670 self.img = None 01671 01672 self.pdc = wx.PseudoDC() 01673 # will store an off screen empty bitmap for saving to file 01674 self._Buffer = None 01675 01676 # make sure that extents are updated at init 01677 self.Map.region = self.Map.GetRegion() 01678 self.Map.SetRegion() 01679 01680 def Draw(self, pdc, img = None, pdctype = 'image'): 01681 """!Draws preview or clears window""" 01682 pdc.BeginDrawing() 01683 01684 Debug.msg (3, "BufferedWindow.Draw(): pdctype=%s" % (pdctype)) 01685 01686 if pdctype == 'clear': # erase the display 01687 bg = wx.WHITE_BRUSH 01688 pdc.SetBackground(bg) 01689 pdc.Clear() 01690 self.Refresh() 01691 pdc.EndDrawing() 01692 return 01693 01694 if pdctype == 'image' and img: 01695 bg = wx.TRANSPARENT_BRUSH 01696 pdc.SetBackground(bg) 01697 bitmap = wx.BitmapFromImage(img) 01698 w, h = bitmap.GetSize() 01699 pdc.DrawBitmap(bitmap, 0, 0, True) # draw the composite map 01700 01701 pdc.EndDrawing() 01702 self.Refresh() 01703 01704 def OnPaint(self, event): 01705 """!Draw pseudo DC to buffer""" 01706 self._Buffer = wx.EmptyBitmap(self.Map.width, self.Map.height) 01707 dc = wx.BufferedPaintDC(self, self._Buffer) 01708 01709 # use PrepareDC to set position correctly 01710 self.PrepareDC(dc) 01711 01712 # we need to clear the dc BEFORE calling PrepareDC 01713 bg = wx.Brush(self.GetBackgroundColour()) 01714 dc.SetBackground(bg) 01715 dc.Clear() 01716 01717 # create a clipping rect from our position and size 01718 # and the Update Region 01719 rgn = self.GetUpdateRegion() 01720 r = rgn.GetBox() 01721 01722 # draw to the dc using the calculated clipping rect 01723 self.pdc.DrawToDCClipped(dc, r) 01724 01725 def OnSize(self, event): 01726 """!Init image size to match window size""" 01727 # set size of the input image 01728 self.Map.width, self.Map.height = self.GetClientSize() 01729 01730 # Make new off screen bitmap: this bitmap will always have the 01731 # current drawing in it, so it can be used to save the image to 01732 # a file, or whatever. 01733 self._Buffer = wx.EmptyBitmap(self.Map.width, self.Map.height) 01734 01735 # get the image to be rendered 01736 self.img = self.GetImage() 01737 01738 # update map display 01739 if self.img and self.Map.width + self.Map.height > 0: # scale image during resize 01740 self.img = self.img.Scale(self.Map.width, self.Map.height) 01741 self.render = False 01742 self.UpdatePreview() 01743 01744 # re-render image on idle 01745 self.resize = True 01746 01747 def OnIdle(self, event): 01748 """!Only re-render a preview image from GRASS during 01749 idle time instead of multiple times during resizing. 01750 """ 01751 if self.resize: 01752 self.render = True 01753 self.UpdatePreview() 01754 event.Skip() 01755 01756 def GetImage(self): 01757 """!Converts files to wx.Image""" 01758 if self.Map.mapfile and os.path.isfile(self.Map.mapfile) and \ 01759 os.path.getsize(self.Map.mapfile): 01760 img = wx.Image(self.Map.mapfile, wx.BITMAP_TYPE_ANY) 01761 else: 01762 img = None 01763 01764 return img 01765 01766 def UpdatePreview(self, img = None): 01767 """!Update canvas if window changes geometry""" 01768 Debug.msg (2, "BufferedWindow.UpdatePreview(%s): render=%s" % (img, self.render)) 01769 oldfont = "" 01770 oldencoding = "" 01771 01772 if self.render: 01773 # extent is taken from current map display 01774 try: 01775 self.Map.region = copy.deepcopy(self.parent.parent.curr_page.maptree.Map.region) 01776 except AttributeError: 01777 self.Map.region = self.Map.GetRegion() 01778 # render new map images 01779 self.mapfile = self.Map.Render(force = self.render) 01780 self.img = self.GetImage() 01781 self.resize = False 01782 01783 if not self.img: 01784 return 01785 01786 # paint images to PseudoDC 01787 self.pdc.Clear() 01788 self.pdc.RemoveAll() 01789 # draw map image background 01790 self.Draw(self.pdc, self.img, pdctype = 'image') 01791 01792 self.resize = False 01793 01794 def EraseMap(self): 01795 """!Erase preview""" 01796 self.Draw(self.pdc, pdctype = 'clear')