1""" 2@package module.colorrules 3 4@brief Dialog for interactive management of raster/vector color tables 5and color rules. 6 7Classes: 8 - colorrules::RulesPanel 9 - colorrules::ColorTable 10 - colorrules::RasterColorTable 11 - colorrules::VectorColorTable 12 - colorrules::ThematicVectorTable 13 - colorrules::BufferedWindow 14 15(C) 2008-2015 by the GRASS Development Team 16 17This program is free software under the GNU General Public License 18(>=v2). Read the file COPYING that comes with GRASS for details. 19 20@author Michael Barton (Arizona State University) 21@author Martin Landa <landa.martin gmail.com> (various updates, pre-defined color table) 22@author Anna Kratochvilova <kratochanna gmail.com> (split to base and derived classes) 23""" 24 25import os 26import shutil 27import copy 28import tempfile 29import six 30 31import wx 32import wx.lib.colourselect as csel 33import wx.lib.scrolledpanel as scrolled 34import wx.lib.filebrowsebutton as filebrowse 35 36import grass.script as grass 37from grass.script.task import cmdlist_to_tuple 38 39from core import globalvar 40from core import utils 41from core.gcmd import GMessage, RunCommand, GError 42from gui_core.gselect import Select, LayerSelect, ColumnSelect, VectorDBInfo 43from core.render import Map 44from gui_core.forms import GUI 45from core.debug import Debug as Debug 46from core.settings import UserSettings 47from gui_core.widgets import ColorTablesComboBox 48from gui_core.wrap import SpinCtrl, PseudoDC, TextCtrl, Button, CancelButton, \ 49 StaticText, StaticBox, EmptyBitmap, BitmapFromImage 50 51 52class RulesPanel: 53 54 def __init__(self, parent, mapType, attributeType, 55 properties, panelWidth=180): 56 """Create rules panel 57 58 :param mapType: raster/vector 59 :param attributeType: color/size for choosing widget type 60 :param properties: properties of classes derived from ColorTable 61 :param panelWidth: width of scroll panel""" 62 63 self.ruleslines = {} 64 self.mapType = mapType 65 self.attributeType = attributeType 66 self.properties = properties 67 self.parent = parent 68 self.panelWidth = panelWidth 69 70 self.mainSizer = wx.FlexGridSizer(cols=3, vgap=6, hgap=4) 71 # put small border at the top of panel 72 for i in range(3): 73 self.mainSizer.Add(wx.Size(3, 3)) 74 75 self.mainPanel = scrolled.ScrolledPanel( 76 parent, id=wx.ID_ANY, size=(self.panelWidth, 300), 77 style=wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER) 78 79 # (un)check all 80 self.checkAll = wx.CheckBox(parent, id=wx.ID_ANY, label=_("Check all")) 81 self.checkAll.SetValue(True) 82 # clear button 83 self.clearAll = Button(parent, id=wx.ID_ANY, label=_("Clear all")) 84 # determines how many rules should be added 85 self.numRules = SpinCtrl(parent, id=wx.ID_ANY, min=1, max=1e6, 86 initial=1, size=(150, -1)) 87 # add rules 88 self.btnAdd = Button(parent, id=wx.ID_ADD) 89 90 self.btnAdd.Bind(wx.EVT_BUTTON, self.OnAddRules) 91 self.checkAll.Bind(wx.EVT_CHECKBOX, self.OnCheckAll) 92 self.clearAll.Bind(wx.EVT_BUTTON, self.OnClearAll) 93 94 self.mainPanel.SetSizer(self.mainSizer) 95 self.mainPanel.SetAutoLayout(True) 96 self.mainPanel.SetupScrolling() 97 98 def Clear(self): 99 """Clear and widgets and delete information""" 100 self.ruleslines.clear() 101 self.mainSizer.Clear(True) 102 103 def OnCheckAll(self, event): 104 """(Un)check all rules""" 105 check = event.GetInt() 106 for child in self.mainPanel.GetChildren(): 107 if child.GetName() == 'enable': 108 child.SetValue(check) 109 else: 110 child.Enable(check) 111 112 def OnClearAll(self, event): 113 """Delete all widgets in panel""" 114 self.Clear() 115 116 def OnAddRules(self, event): 117 """Add rules button pressed""" 118 nrules = self.numRules.GetValue() 119 self.AddRules(nrules) 120 121 def AddRules(self, nrules, start=False): 122 """Add rules 123 124 :param start: set widgets (not append) 125 """ 126 127 snum = len(self.ruleslines.keys()) 128 if start: 129 snum = 0 130 for num in range(snum, snum + nrules): 131 # enable 132 enable = wx.CheckBox(parent=self.mainPanel, id=num) 133 enable.SetValue(True) 134 enable.SetName('enable') 135 enable.Bind(wx.EVT_CHECKBOX, self.OnRuleEnable) 136 # value 137 txt_ctrl = TextCtrl(parent=self.mainPanel, id=1000 + num, 138 size=(80, -1), 139 style=wx.TE_NOHIDESEL) 140 if self.mapType == 'vector': 141 txt_ctrl.SetToolTip(_("Enter vector attribute values")) 142 txt_ctrl.Bind(wx.EVT_TEXT, self.OnRuleValue) 143 txt_ctrl.SetName('source') 144 if self.attributeType == 'color': 145 # color 146 columnCtrl = csel.ColourSelect( 147 self.mainPanel, id=2000 + num, 148 size=globalvar.DIALOG_COLOR_SIZE) 149 columnCtrl.Bind(csel.EVT_COLOURSELECT, self.OnRuleColor) 150 columnCtrl.SetName('target') 151 if not start: 152 self.ruleslines[enable.GetId()] = {'value': '', 153 'color': "0:0:0"} 154 else: 155 # size or width 156 init = 2 157 if self.attributeType == 'size': 158 init = 100 159 columnCtrl = SpinCtrl(self.mainPanel, id=2000 + num, 160 size=(50, -1), min=1, max=1e4, 161 initial=init) 162 columnCtrl.Bind(wx.EVT_SPINCTRL, self.OnRuleSize) 163 columnCtrl.Bind(wx.EVT_TEXT, self.OnRuleSize) 164 columnCtrl.SetName('target') 165 if not start: 166 self.ruleslines[ 167 enable.GetId()] = { 168 'value': '', 169 self.attributeType: init} 170 171 self.mainSizer.Add(enable, proportion=0, 172 flag=wx.ALIGN_CENTER_VERTICAL) 173 self.mainSizer.Add(txt_ctrl, proportion=0, 174 flag=wx.ALIGN_CENTER | wx.RIGHT, border=5) 175 self.mainSizer.Add(columnCtrl, proportion=0, 176 flag=wx.ALIGN_CENTER | wx.RIGHT, border=10) 177 178 self.mainPanel.Layout() 179 self.mainPanel.SetupScrolling(scroll_x=False) 180 181 def OnRuleEnable(self, event): 182 """Rule enabled/disabled""" 183 id = event.GetId() 184 185 if event.IsChecked(): 186 self.mainPanel.FindWindowById(id + 1000).Enable() 187 self.mainPanel.FindWindowById(id + 2000).Enable() 188 if self.mapType == 'vector' and not self.parent.GetParent().colorTable: 189 vals = [] 190 vals.append( 191 self.mainPanel.FindWindowById( 192 id + 1000).GetValue()) 193 try: 194 vals.append( 195 self.mainPanel.FindWindowById( 196 id + 1 + 1000).GetValue()) 197 except AttributeError: 198 vals.append(None) 199 value = self.SQLConvert(vals) 200 else: 201 value = self.mainPanel.FindWindowById(id + 1000).GetValue() 202 color = self.mainPanel.FindWindowById(id + 2000).GetValue() 203 204 if self.attributeType == 'color': 205 # color 206 color_str = str(color[0]) + ':' \ 207 + str(color[1]) + ':' \ 208 + str(color[2]) 209 self.ruleslines[id] = {'value': value, 210 'color': color_str} 211 212 else: 213 # size or width 214 self.ruleslines[id] = {'value': value, 215 self.attributeType: float(color)} 216 217 else: 218 self.mainPanel.FindWindowById(id + 1000).Disable() 219 self.mainPanel.FindWindowById(id + 2000).Disable() 220 del self.ruleslines[id] 221 222 def OnRuleColor(self, event): 223 """Rule color changed""" 224 num = event.GetId() 225 226 rgba_color = event.GetValue() 227 228 rgb_string = str(rgba_color[0]) + ':' \ 229 + str(rgba_color[1]) + ':' \ 230 + str(rgba_color[2]) 231 232 self.ruleslines[num - 2000]['color'] = rgb_string 233 234 def OnRuleSize(self, event): 235 """Rule size changed""" 236 num = event.GetId() 237 size = event.GetInt() 238 239 self.ruleslines[num - 2000][self.attributeType] = size 240 241 def OnRuleValue(self, event): 242 """Rule value changed""" 243 num = event.GetId() 244 val = event.GetString().strip() 245 246 if val == '': 247 return 248 try: 249 table = self.parent.colorTable 250 except AttributeError: 251 # due to panel/scrollpanel in vector dialog 252 if isinstance(self.parent.GetParent(), RasterColorTable): 253 table = self.parent.GetParent().colorTable 254 else: 255 table = self.parent.GetParent().GetParent().colorTable 256 if table: 257 self.SetRasterRule(num, val) 258 else: 259 self.SetVectorRule(num, val) 260 261 def SetRasterRule(self, num, val): 262 """Set raster rule""" 263 self.ruleslines[num - 1000]['value'] = val 264 265 def SetVectorRule(self, num, val): 266 """Set vector rule""" 267 vals = [] 268 vals.append(val) 269 try: 270 vals.append(self.mainPanel.FindWindowById(num + 1).GetValue()) 271 except AttributeError: 272 vals.append(None) 273 self.ruleslines[num - 1000]['value'] = self.SQLConvert(vals) 274 275 def Enable(self, enable=True): 276 """Enable/Disable all widgets""" 277 for child in self.mainPanel.GetChildren(): 278 child.Enable(enable) 279 sql = True 280 self.LoadRulesline(sql) # todo 281 self.btnAdd.Enable(enable) 282 self.numRules.Enable(enable) 283 self.checkAll.Enable(enable) 284 self.clearAll.Enable(enable) 285 286 def LoadRules(self): 287 message = "" 288 for item in range(len(self.ruleslines)): 289 try: 290 self.mainPanel.FindWindowById( 291 item + 292 1000).SetValue( 293 self.ruleslines[item]['value']) 294 r, g, b = (0, 0, 0) # default 295 if not self.ruleslines[item][self.attributeType]: 296 if self.attributeType == 'color': 297 self.ruleslines[item][ 298 self.attributeType] = '%d:%d:%d' % ( 299 r, g, b) 300 elif self.attributeType == 'size': 301 self.ruleslines[item][self.attributeType] = 100 302 elif self.attributeType == 'width': 303 self.ruleslines[item][self.attributeType] = 2 304 305 if self.attributeType == 'color': 306 try: 307 r, g, b = map( 308 int, self.ruleslines[item][ 309 self.attributeType].split(':')) 310 except ValueError as e: 311 message = _( 312 "Bad color format. Use color format '0:0:0'") 313 self.mainPanel.FindWindowById( 314 item + 2000).SetValue((r, g, b)) 315 else: 316 value = float(self.ruleslines[item][self.attributeType]) 317 self.mainPanel.FindWindowById(item + 2000).SetValue(value) 318 except: 319 continue 320 321 if message: 322 GMessage(parent=self.parent, message=message) 323 return False 324 325 return True 326 327 def SQLConvert(self, vals): 328 """Prepare value for SQL query""" 329 if vals[0].isdigit(): 330 sqlrule = '%s=%s' % (self.properties['sourceColumn'], vals[0]) 331 if vals[1]: 332 sqlrule += ' AND %s<%s' % ( 333 self.properties['sourceColumn'], vals[1]) 334 else: 335 sqlrule = '%s=%s' % (self.properties['sourceColumn'], vals[0]) 336 337 return sqlrule 338 339 340class ColorTable(wx.Frame): 341 342 def __init__(self, parent, title, layerTree=None, id=wx.ID_ANY, 343 style=wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER, 344 **kwargs): 345 """Dialog for interactively entering rules for map management 346 commands 347 348 :param raster: True to raster otherwise vector 349 :param nviz: True if ColorTable is called from nviz thematic mapping 350 """ 351 self.parent = parent # GMFrame ? 352 self.layerTree = layerTree # LayerTree or None 353 354 wx.Frame.__init__(self, parent, id, title, style=style, **kwargs) 355 356 self.SetIcon( 357 wx.Icon( 358 os.path.join( 359 globalvar.ICONDIR, 360 'grass.ico'), 361 wx.BITMAP_TYPE_ICO)) 362 363 self.panel = wx.Panel(parent=self, id=wx.ID_ANY) 364 365 # instance of render.Map to be associated with display 366 self.Map = Map() 367 368 # input map to change 369 self.inmap = '' 370 371 # reference to layer with preview 372 self.layer = None 373 374 # layout 375 self._doLayout() 376 377 # bindings 378 self.Bind(wx.EVT_BUTTON, self.OnHelp, self.btnHelp) 379 self.selectionInput.Bind(wx.EVT_TEXT, self.OnSelectionInput) 380 self.Bind(wx.EVT_BUTTON, self.OnCancel, self.btnCancel) 381 self.Bind(wx.EVT_BUTTON, self.OnApply, self.btnApply) 382 self.Bind(wx.EVT_BUTTON, self.OnOK, self.btnOK) 383 self.Bind(wx.EVT_BUTTON, self.OnLoadDefaultTable, self.btnDefault) 384 385 self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) 386 387 self.Bind(wx.EVT_BUTTON, self.OnPreview, self.btnPreview) 388 389 def _initLayer(self): 390 """Set initial layer when opening dialog""" 391 # set map layer from layer tree, first selected, 392 # if not the right type, than select another 393 try: 394 sel = self.layerTree.layer_selected 395 if sel and self.layerTree.GetLayerInfo( 396 sel, key='type') == self.mapType: 397 layer = sel 398 else: 399 layer = self.layerTree.FindItemByData( 400 key='type', value=self.mapType) 401 except: 402 layer = None 403 if layer: 404 mapLayer = self.layerTree.GetLayerInfo(layer, key='maplayer') 405 name = mapLayer.GetName() 406 type = mapLayer.GetType() 407 self.selectionInput.SetValue(name) 408 self.inmap = name 409 410 def _createMapSelection(self, parent): 411 """Create map selection part of dialog""" 412 # top controls 413 if self.mapType == 'raster': 414 maplabel = _('Select raster map:') 415 else: 416 maplabel = _('Select vector map:') 417 inputBox = StaticBox(parent, id=wx.ID_ANY, 418 label=" %s " % maplabel) 419 inputSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL) 420 421 self.selectionInput = Select(parent=parent, id=wx.ID_ANY, 422 size=globalvar.DIALOG_GSELECT_SIZE, 423 type=self.mapType) 424 # layout 425 inputSizer.Add( 426 self.selectionInput, 427 flag=wx.ALL | wx.EXPAND, 428 border=5) 429 430 return inputSizer 431 432 def _createFileSelection(self, parent): 433 """Create file (open/save rules) selection part of dialog""" 434 inputBox = StaticBox( 435 parent, id=wx.ID_ANY, label=" %s " % 436 _("Import or export color table:")) 437 inputSizer = wx.StaticBoxSizer(inputBox, wx.HORIZONTAL) 438 439 self.loadRules = filebrowse.FileBrowseButton( 440 parent=parent, id=wx.ID_ANY, fileMask='*', labelText='', 441 dialogTitle=_('Choose file to load color table'), 442 buttonText=_('Load'), 443 toolTip=_( 444 "Type filename or click to choose " 445 "file and load color table"), 446 startDirectory=os.getcwd(), 447 fileMode=wx.FD_OPEN, changeCallback=self.OnLoadRulesFile) 448 self.saveRules = filebrowse.FileBrowseButton( 449 parent=parent, id=wx.ID_ANY, fileMask='*', labelText='', 450 dialogTitle=_('Choose file to save color table'), 451 toolTip=_( 452 "Type filename or click to choose " 453 "file and save color table"), 454 buttonText=_('Save'), 455 startDirectory=os.getcwd(), 456 fileMode=wx.FD_SAVE, changeCallback=self.OnSaveRulesFile) 457 458 colorTable = ColorTablesComboBox( 459 parent=parent, 460 size=globalvar.DIALOG_COMBOBOX_SIZE, 461 choices=utils.GetColorTables(), 462 name="colorTableChoice") 463 self.btnSet = Button( 464 parent=parent, 465 id=wx.ID_ANY, 466 label=_("&Set"), 467 name='btnSet') 468 self.btnSet.Bind(wx.EVT_BUTTON, self.OnSetTable) 469 self.btnSet.Enable(False) 470 471 # layout 472 gridSizer = wx.GridBagSizer(hgap=2, vgap=2) 473 474 gridSizer.Add(StaticText(parent, label=_("Load color table:")), 475 pos=(0, 0), flag=wx.ALIGN_CENTER_VERTICAL) 476 gridSizer.Add(colorTable, pos=(0, 1)) 477 gridSizer.Add(self.btnSet, pos=(0, 2), flag=wx.ALIGN_RIGHT) 478 gridSizer.Add( 479 StaticText( 480 parent, label=_('Load color table from file:')), pos=( 481 1, 0), flag=wx.ALIGN_CENTER_VERTICAL) 482 gridSizer.Add( 483 self.loadRules, pos=( 484 1, 1), span=( 485 1, 2), flag=wx.EXPAND) 486 gridSizer.Add( 487 StaticText( 488 parent, label=_('Save color table to file:')), pos=( 489 2, 0), flag=wx.ALIGN_CENTER_VERTICAL) 490 gridSizer.Add( 491 self.saveRules, pos=( 492 2, 1), span=( 493 1, 2), flag=wx.EXPAND) 494 495 gridSizer.AddGrowableCol(1) 496 inputSizer.Add(gridSizer, proportion=1, flag=wx.EXPAND | wx.ALL, 497 border=5) 498 499 if self.mapType == 'vector': 500 # parent is collapsible pane 501 parent.SetSizer(inputSizer) 502 503 return inputSizer 504 505 def _createPreview(self, parent): 506 """Create preview""" 507 # initialize preview display 508 self.InitDisplay() 509 self.preview = BufferedWindow(parent, id=wx.ID_ANY, size=(400, 300), 510 Map=self.Map) 511 self.preview.EraseMap() 512 513 def _createButtons(self, parent): 514 """Create buttons for leaving dialog""" 515 self.btnHelp = Button(parent, id=wx.ID_HELP) 516 self.btnCancel = CancelButton(parent) 517 self.btnApply = Button(parent, id=wx.ID_APPLY) 518 self.btnOK = Button(parent, id=wx.ID_OK) 519 self.btnDefault = Button(parent, id=wx.ID_ANY, 520 label=_("Reload default table")) 521 522 self.btnOK.SetDefault() 523 self.btnOK.Enable(False) 524 self.btnApply.Enable(False) 525 self.btnDefault.Enable(False) 526 527 # layout 528 btnSizer = wx.BoxSizer(wx.HORIZONTAL) 529 btnSizer.Add(wx.Size(-1, -1), proportion=1) 530 btnSizer.Add(self.btnDefault, 531 flag=wx.LEFT | wx.RIGHT, border=5) 532 btnSizer.Add(self.btnHelp, 533 flag=wx.LEFT | wx.RIGHT, border=5) 534 btnSizer.Add(self.btnCancel, 535 flag=wx.LEFT | wx.RIGHT, border=5) 536 btnSizer.Add(self.btnApply, 537 flag=wx.LEFT | wx.RIGHT, border=5) 538 btnSizer.Add(self.btnOK, 539 flag=wx.LEFT | wx.RIGHT, border=5) 540 541 return btnSizer 542 543 def _createBody(self, parent): 544 """Create dialog body consisting of rules and preview""" 545 bodySizer = wx.GridBagSizer(hgap=5, vgap=5) 546 547 row = 0 548 # label with range 549 self.cr_label = StaticText(parent, id=wx.ID_ANY) 550 bodySizer.Add(self.cr_label, pos=(row, 0), span=(1, 3), 551 flag=wx.ALL, border=5) 552 553 row += 1 554 # color table 555 self.rulesPanel = RulesPanel( 556 parent=parent, 557 mapType=self.mapType, 558 attributeType=self.attributeType, 559 properties=self.properties) 560 561 bodySizer.Add(self.rulesPanel.mainPanel, pos=(row, 0), 562 span=(1, 2), flag=wx.EXPAND) 563 # add two rules as default 564 self.rulesPanel.AddRules(2) 565 566 # preview window 567 self._createPreview(parent=parent) 568 bodySizer.Add(self.preview, pos=(row, 2), 569 flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER) 570 571 row += 1 572 # add ckeck all and clear all 573 bodySizer.Add( 574 self.rulesPanel.checkAll, 575 flag=wx.ALIGN_CENTER_VERTICAL, 576 pos=( 577 row, 578 0)) 579 bodySizer.Add(self.rulesPanel.clearAll, pos=(row, 1)) 580 581 # preview button 582 self.btnPreview = Button(parent, id=wx.ID_ANY, 583 label=_("Preview")) 584 bodySizer.Add(self.btnPreview, pos=(row, 2), 585 flag=wx.ALIGN_RIGHT) 586 self.btnPreview.Enable(False) 587 self.btnPreview.SetToolTip( 588 _("Show preview of map " "(current Map Display extent is used).")) 589 590 row += 1 591 # add rules button and spin to sizer 592 bodySizer.Add(self.rulesPanel.numRules, pos=(row, 0), 593 flag=wx.ALIGN_CENTER_VERTICAL) 594 bodySizer.Add(self.rulesPanel.btnAdd, pos=(row, 1)) 595 596 bodySizer.AddGrowableRow(1) 597 bodySizer.AddGrowableCol(2) 598 599 return bodySizer 600 601 def InitDisplay(self): 602 """Initialize preview display, set dimensions and region 603 """ 604 self.width = self.Map.width = 400 605 self.height = self.Map.height = 300 606 self.Map.geom = self.width, self.height 607 608 def OnCloseWindow(self, event): 609 """Window closed 610 """ 611 self.OnCancel(event) 612 613 def OnApply(self, event): 614 return self._apply() 615 616 def _apply(self, updatePreview=True): 617 """Apply selected color table 618 619 :return: True on success otherwise False 620 """ 621 ret = self.CreateColorTable() 622 if not ret: 623 GMessage(parent=self, message=_("No valid color rules given.")) 624 else: 625 # re-render preview and current map window 626 if updatePreview: 627 self.OnPreview(None) 628 display = self.layerTree.GetMapDisplay() 629 if display and display.IsAutoRendered(): 630 display.GetWindow().UpdateMap(render=True) 631 632 return ret 633 634 def OnOK(self, event): 635 """Apply selected color table and close the dialog""" 636 if self._apply(updatePreview=False): 637 self.OnCancel(event) 638 639 def OnCancel(self, event): 640 """Do not apply any changes, remove associated 641 rendered images and close the dialog""" 642 self.Map.Clean() 643 self.Destroy() 644 645 def OnSetTable(self, event): 646 """Load pre-defined color table""" 647 ct = self.FindWindowByName("colorTableChoice").GetValue() 648 # save original color table 649 ctOriginal = RunCommand( 650 'r.colors.out', 651 read=True, 652 map=self.inmap, 653 rules='-') 654 # set new color table 655 ret, err = RunCommand('r.colors', map=self.inmap, 656 color=ct, getErrorMsg=True) 657 if ret != 0: 658 GError(err, parent=self) 659 return 660 ctNew = RunCommand( 661 'r.colors.out', 662 read=True, 663 map=self.inmap, 664 rules='-') 665 # restore original table 666 RunCommand('r.colors', map=self.inmap, rules='-', stdin=ctOriginal) 667 # load color table 668 self.rulesPanel.Clear() 669 self.ReadColorTable(ctable=ctNew) 670 671 def OnSaveRulesFile(self, event): 672 """Save color table to file""" 673 path = event.GetString() 674 if not path: 675 return 676 677 if os.path.exists(path): 678 dlgOw = wx.MessageDialog( 679 parent, 680 message=_( 681 "File <%s> already already exists. " 682 "Do you want to overwrite it?") % 683 path, 684 caption=_("Overwrite?"), 685 style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION) 686 if dlgOw.ShowModal() != wx.ID_YES: 687 return 688 689 rulestxt = '' 690 for rule in six.itervalues(self.rulesPanel.ruleslines): 691 if 'value' not in rule: 692 continue 693 rulestxt += rule['value'] + ' ' + rule['color'] + '\n' 694 if not rulestxt: 695 GMessage(message=_("Nothing to save."), 696 parent=self) 697 return 698 699 fd = open(path, 'w') 700 fd.write(rulestxt) 701 fd.close() 702 703 def OnLoadRulesFile(self, event): 704 """Load color table from file""" 705 path = event.GetString() 706 if not os.path.exists(path): 707 return 708 709 self.rulesPanel.Clear() 710 711 fd = open(path, 'r') 712 self.ReadColorTable(ctable=fd.read()) 713 fd.close() 714 715 def ReadColorTable(self, ctable): 716 """Read color table 717 718 :param table: color table in format coming from r.colors.out""" 719 720 rulesNumber = len(ctable.splitlines()) 721 self.rulesPanel.AddRules(rulesNumber) 722 723 minim = maxim = count = 0 724 for line in ctable.splitlines(): 725 try: 726 value, color = map(lambda x: x.strip(), line.split(' ')) 727 except ValueError: 728 GMessage(parent=self, message=_("Invalid color table format")) 729 self.rulesPanel.Clear() 730 return 731 732 self.rulesPanel.ruleslines[count]['value'] = value 733 self.rulesPanel.ruleslines[count]['color'] = color 734 self.rulesPanel.mainPanel.FindWindowById( 735 count + 1000).SetValue(value) 736 rgb = list() 737 for c in color.split(':'): 738 rgb.append(int(c)) 739 self.rulesPanel.mainPanel.FindWindowById( 740 count + 2000).SetColour(rgb) 741 # range 742 try: 743 if float(value) < minim: 744 minim = float(value) 745 if float(value) > maxim: 746 maxim = float(value) 747 except ValueError: # nv, default 748 pass 749 count += 1 750 751 if self.mapType == 'vector': 752 # raster min, max is known from r.info 753 self.properties['min'], self.properties['max'] = minim, maxim 754 self.SetRangeLabel() 755 756 self.OnPreview(tmp=True) 757 758 def OnLoadDefaultTable(self, event): 759 """Load internal color table""" 760 self.LoadTable() 761 762 def LoadTable(self, mapType='raster'): 763 """Load current color table (using `r(v).colors.out`) 764 765 :param mapType: map type (raster or vector)""" 766 self.rulesPanel.Clear() 767 768 if mapType == 'raster': 769 cmd = ['r.colors.out', 770 'read=True', 771 'map=%s' % self.inmap, 772 'rules=-'] 773 else: 774 cmd = ['v.colors.out', 775 'read=True', 776 'map=%s' % self.inmap, 777 'rules=-'] 778 779 if self.properties['sourceColumn'] and self.properties[ 780 'sourceColumn'] != 'cat': 781 cmd.append('column=%s' % self.properties['sourceColumn']) 782 783 cmd = cmdlist_to_tuple(cmd) 784 785 if self.inmap: 786 ctable = RunCommand(cmd[0], **cmd[1]) 787 else: 788 self.OnPreview() 789 return 790 791 self.ReadColorTable(ctable=ctable) 792 793 def CreateColorTable(self, tmp=False): 794 """Creates color table 795 796 :return: True on success 797 :return: False on failure 798 """ 799 rulestxt = '' 800 801 for rule in six.itervalues(self.rulesPanel.ruleslines): 802 if 'value' not in rule: # skip empty rules 803 continue 804 805 if rule['value'] not in ('nv', 'default') and \ 806 rule['value'][-1] != '%' and \ 807 not self._IsNumber(rule['value']): 808 GError( 809 _("Invalid rule value '%s'. Unable to apply color table.") % 810 rule['value'], parent=self) 811 return False 812 813 rulestxt += rule['value'] + ' ' + rule['color'] + '\n' 814 815 if not rulestxt: 816 return False 817 818 gtemp = utils.GetTempfile() 819 output = open(gtemp, "w") 820 try: 821 output.write(rulestxt) 822 finally: 823 output.close() 824 825 cmd = ['%s.colors' % self.mapType[0], # r.colors/v.colors 826 'map=%s' % self.inmap, 827 'rules=%s' % gtemp] 828 if self.mapType == 'vector' and self.properties['sourceColumn'] \ 829 and self.properties['sourceColumn'] != 'cat': 830 cmd.append('column=%s' % self.properties['sourceColumn']) 831 832 cmd = cmdlist_to_tuple(cmd) 833 ret = RunCommand(cmd[0], **cmd[1]) 834 if ret != 0: 835 return False 836 837 return True 838 839 def DoPreview(self, ltype, cmdlist): 840 """Update preview (based on computational region)""" 841 842 if not self.layer: 843 self.layer = self.Map.AddLayer( 844 ltype=ltype, 845 name='preview', 846 command=cmdlist, 847 active=True, 848 hidden=False, 849 opacity=1.0, 850 render=False) 851 else: 852 self.layer.SetCmd(cmdlist) 853 854 # apply new color table and display preview 855 self.CreateColorTable(tmp=True) 856 self.preview.UpdatePreview() 857 858 def RunHelp(self, cmd): 859 """Show GRASS manual page""" 860 RunCommand('g.manual', 861 quiet=True, 862 parent=self, 863 entry=cmd) 864 865 def SetMap(self, name): 866 """Set map name and update dialog""" 867 self.selectionInput.SetValue(name) 868 869 def _IsNumber(self, s): 870 """Check if 's' is a number""" 871 try: 872 float(s) 873 return True 874 except ValueError: 875 return False 876 877 878class RasterColorTable(ColorTable): 879 880 def __init__(self, parent, **kwargs): 881 """Dialog for interactively entering color rules for raster maps""" 882 883 self.mapType = 'raster' 884 self.attributeType = 'color' 885 self.colorTable = True 886 # raster properties 887 self.properties = { 888 # min cat in raster map 889 'min': None, 890 # max cat in raster map 891 'max': None, 892 } 893 894 ColorTable.__init__(self, parent, title=_( 895 'Create new color table for raster map'), **kwargs) 896 897 self._initLayer() 898 self.Map.GetRenderMgr().renderDone.connect(self._restoreColorTable) 899 900 # self.SetMinSize(self.GetSize()) 901 self.SetMinSize((650, 700)) 902 903 def _doLayout(self): 904 """Do main layout""" 905 sizer = wx.BoxSizer(wx.VERTICAL) 906 # 907 # map selection 908 # 909 mapSelection = self._createMapSelection(parent=self.panel) 910 sizer.Add(mapSelection, proportion=0, 911 flag=wx.ALL | wx.EXPAND, border=5) 912 # 913 # manage extern tables 914 # 915 fileSelection = self._createFileSelection(parent=self.panel) 916 sizer.Add(fileSelection, proportion=0, 917 flag=wx.LEFT | wx.RIGHT | wx.EXPAND, border=5) 918 # 919 # body & preview 920 # 921 bodySizer = self._createBody(parent=self.panel) 922 sizer.Add(bodySizer, proportion=1, 923 flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=5) 924 # 925 # buttons 926 # 927 btnSizer = self._createButtons(parent=self.panel) 928 sizer.Add(wx.StaticLine(parent=self.panel, id=wx.ID_ANY, 929 style=wx.LI_HORIZONTAL), proportion=0, 930 flag=wx.EXPAND | wx.ALL, border=5) 931 932 sizer.Add(btnSizer, proportion=0, 933 flag=wx.ALL | wx.ALIGN_RIGHT, border=5) 934 935 self.panel.SetSizer(sizer) 936 sizer.Layout() 937 sizer.Fit(self.panel) 938 self.Layout() 939 940 def OnSelectionInput(self, event): 941 """Raster map selected""" 942 if event: 943 self.inmap = event.GetString() 944 945 self.loadRules.SetValue('') 946 self.saveRules.SetValue('') 947 948 if self.inmap: 949 if not grass.find_file(name=self.inmap, element='cell')['file']: 950 self.inmap = None 951 952 if not self.inmap: 953 for btn in (self.btnPreview, self.btnOK, 954 self.btnApply, self.btnDefault, self.btnSet): 955 btn.Enable(False) 956 self.LoadTable() 957 return 958 959 info = grass.raster_info(map=self.inmap) 960 961 if info: 962 self.properties['min'] = info['min'] 963 self.properties['max'] = info['max'] 964 self.LoadTable() 965 else: 966 self.inmap = '' 967 self.properties['min'] = self.properties['max'] = None 968 for btn in (self.btnPreview, self.btnOK, 969 self.btnApply, self.btnDefault, self.btnSet): 970 btn.Enable(False) 971 self.preview.EraseMap() 972 self.cr_label.SetLabel( 973 _('Enter raster category values or percents')) 974 return 975 976 if info['datatype'] == 'CELL': 977 mapRange = _('range') 978 else: 979 mapRange = _('fp range') 980 self.cr_label.SetLabel( 981 _('Enter raster category values or percents (%(range)s = %(min)d-%(max)d)') % 982 { 983 'range': mapRange, 984 'min': self.properties['min'], 985 'max': self.properties['max']}) 986 987 for btn in (self.btnPreview, self.btnOK, 988 self.btnApply, self.btnDefault, self.btnSet): 989 btn.Enable() 990 991 def OnPreview(self, tmp=True): 992 """Update preview (based on computational region)""" 993 if not self.inmap: 994 self.preview.EraseMap() 995 return 996 997 cmdlist = ['d.rast', 998 'map=%s' % self.inmap] 999 ltype = 'raster' 1000 1001 # find existing color table and copy to temp file 1002 try: 1003 name, mapset = self.inmap.split('@') 1004 except ValueError: 1005 name = self.inmap 1006 mapset = grass.find_file(self.inmap, element='cell')['mapset'] 1007 if not mapset: 1008 return 1009 self._tmp = tmp 1010 self._old_colrtable = None 1011 if mapset == grass.gisenv()['MAPSET']: 1012 self._old_colrtable = grass.find_file( 1013 name=name, element='colr')['file'] 1014 else: 1015 self._old_colrtable = grass.find_file( 1016 name=name, element='colr2/' + mapset)['file'] 1017 1018 if self._old_colrtable: 1019 self._colrtemp = utils.GetTempfile() 1020 shutil.copyfile(self._old_colrtable, self._colrtemp) 1021 1022 ColorTable.DoPreview(self, ltype, cmdlist) 1023 1024 def _restoreColorTable(self): 1025 # restore previous color table 1026 if self._tmp: 1027 if self._old_colrtable: 1028 shutil.copyfile(self._colrtemp, self._old_colrtable) 1029 os.remove(self._colrtemp) 1030 del self._colrtemp, self._old_colrtable 1031 else: 1032 RunCommand('r.colors', 1033 parent=self, 1034 flags='r', 1035 map=self.inmap) 1036 del self._tmp 1037 1038 def OnHelp(self, event): 1039 """Show GRASS manual page""" 1040 cmd = 'r.colors' 1041 ColorTable.RunHelp(self, cmd=cmd) 1042 1043 1044class VectorColorTable(ColorTable): 1045 1046 def __init__(self, parent, attributeType, **kwargs): 1047 """Dialog for interactively entering color rules for vector maps""" 1048 # dialog attributes 1049 self.mapType = 'vector' 1050 self.attributeType = attributeType # color, size, width 1051 # in version 7 v.colors used, otherwise color column only 1052 self.version7 = int(grass.version()['version'].split('.')[0]) >= 7 1053 self.colorTable = False 1054 self.updateColumn = True 1055 # vector properties 1056 self.properties = { 1057 # vector layer for attribute table to use for setting color 1058 'layer': 1, 1059 # vector attribute table used for setting color 1060 'table': '', 1061 # vector attribute column for assigning colors 1062 'sourceColumn': '', 1063 # vector attribute column to use for loading colors 1064 'loadColumn': '', 1065 # vector attribute column to use for storing colors 1066 'storeColumn': '', 1067 # vector attribute column for temporary storing colors 1068 'tmpColumn': 'tmp_0', 1069 # min value of attribute column/vector color table 1070 'min': None, 1071 # max value of attribute column/vector color table 1072 'max': None 1073 } 1074 self.columnsProp = { 1075 'color': { 1076 'name': 'GRASSRGB', 1077 'type1': 'varchar(11)', 1078 'type2': ['character']}, 1079 'size': { 1080 'name': 'GRASSSIZE', 1081 'type1': 'integer', 1082 'type2': ['integer']}, 1083 'width': { 1084 'name': 'GRASSWIDTH', 1085 'type1': 'integer', 1086 'type2': ['integer']}} 1087 ColorTable.__init__(self, parent=parent, title=_( 1088 'Create new color rules for vector map'), **kwargs) 1089 1090 # additional bindings for vector color management 1091 self.Bind(wx.EVT_COMBOBOX, self.OnLayerSelection, self.layerSelect) 1092 1093 self._columnWidgetEvtHandler() 1094 self.Bind(wx.EVT_BUTTON, self.OnAddColumn, self.addColumn) 1095 1096 self._initLayer() 1097 if self.colorTable: 1098 self.cr_label.SetLabel( 1099 _("Enter vector attribute values or percents:")) 1100 else: 1101 self.cr_label.SetLabel(_("Enter vector attribute values:")) 1102 1103 # self.SetMinSize(self.GetSize()) 1104 self.SetMinSize((650, 700)) 1105 1106 self.CentreOnScreen() 1107 self.Show() 1108 1109 def _createVectorAttrb(self, parent): 1110 """Create part of dialog with layer/column selection""" 1111 inputBox = StaticBox(parent=parent, id=wx.ID_ANY, 1112 label=" %s " % _("Select vector columns")) 1113 cb_vl_label = StaticText(parent, id=wx.ID_ANY, 1114 label=_('Layer:')) 1115 cb_vc_label = StaticText(parent, id=wx.ID_ANY, 1116 label=_('Attribute column:')) 1117 1118 if self.attributeType == 'color': 1119 labels = [_("Load color from column:"), _("Save color to column:")] 1120 elif self.attributeType == 'size': 1121 labels = [_("Load size from column:"), _("Save size to column:")] 1122 elif self.attributeType == 'width': 1123 labels = [_("Load width from column:"), _("Save width to column:")] 1124 1125 if self.version7 and self.attributeType == 'color': 1126 self.useColumn = wx.CheckBox( 1127 parent, id=wx.ID_ANY, 1128 label=_("Use color column instead of color table:")) 1129 self.useColumn.Bind(wx.EVT_CHECKBOX, self.OnCheckColumn) 1130 1131 fromColumnLabel = StaticText(parent, id=wx.ID_ANY, 1132 label=labels[0]) 1133 toColumnLabel = StaticText(parent, id=wx.ID_ANY, 1134 label=labels[1]) 1135 1136 self.rgb_range_label = StaticText(parent, id=wx.ID_ANY) 1137 self.layerSelect = LayerSelect(parent) 1138 self.sourceColumn = ColumnSelect(parent) 1139 self.fromColumn = ColumnSelect(parent) 1140 self.toColumn = ColumnSelect(parent) 1141 self.addColumn = Button(parent, id=wx.ID_ANY, 1142 label=_('Add column')) 1143 self.addColumn.SetToolTip( 1144 _("Add GRASSRGB column to current attribute table.")) 1145 1146 # layout 1147 inputSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL) 1148 vSizer = wx.GridBagSizer(hgap=5, vgap=5) 1149 row = 0 1150 vSizer.Add(cb_vl_label, pos=(row, 0), 1151 flag=wx.ALIGN_CENTER_VERTICAL) 1152 vSizer.Add(self.layerSelect, pos=(row, 1), 1153 flag=wx.ALIGN_CENTER_VERTICAL) 1154 row += 1 1155 vSizer.Add(cb_vc_label, pos=(row, 0), 1156 flag=wx.ALIGN_CENTER_VERTICAL) 1157 vSizer.Add(self.sourceColumn, pos=(row, 1), 1158 flag=wx.ALIGN_CENTER_VERTICAL) 1159 vSizer.Add(self.rgb_range_label, pos=(row, 2), 1160 flag=wx.ALIGN_CENTER_VERTICAL) 1161 row += 1 1162 if self.version7 and self.attributeType == 'color': 1163 vSizer.Add(self.useColumn, pos=(row, 0), span=(1, 2), 1164 flag=wx.ALIGN_CENTER_VERTICAL) 1165 row += 1 1166 1167 vSizer.Add(fromColumnLabel, pos=(row, 0), 1168 flag=wx.ALIGN_CENTER_VERTICAL) 1169 vSizer.Add(self.fromColumn, pos=(row, 1), 1170 flag=wx.ALIGN_CENTER_VERTICAL) 1171 row += 1 1172 vSizer.Add(toColumnLabel, pos=(row, 0), 1173 flag=wx.ALIGN_CENTER_VERTICAL) 1174 vSizer.Add(self.toColumn, pos=(row, 1), 1175 flag=wx.ALIGN_CENTER_VERTICAL) 1176 vSizer.Add(self.addColumn, pos=(row, 2), 1177 flag=wx.ALIGN_CENTER_VERTICAL) 1178 inputSizer.Add( 1179 vSizer, 1180 flag=wx.ALL | wx.EXPAND, 1181 border=5) 1182 self.colorColumnSizer = vSizer 1183 return inputSizer 1184 1185 def _doLayout(self): 1186 """Do main layout""" 1187 scrollPanel = scrolled.ScrolledPanel(parent=self.panel, id=wx.ID_ANY, 1188 style=wx.TAB_TRAVERSAL) 1189 scrollPanel.SetupScrolling() 1190 sizer = wx.BoxSizer(wx.VERTICAL) 1191 # 1192 # map selection 1193 # 1194 mapSelection = self._createMapSelection(parent=scrollPanel) 1195 sizer.Add(mapSelection, proportion=0, 1196 flag=wx.ALL | wx.EXPAND, border=5) 1197 # 1198 # manage extern tables 1199 # 1200 if self.version7 and self.attributeType == 'color': 1201 self.cp = wx.CollapsiblePane( 1202 scrollPanel, 1203 label=_("Import or export color table"), 1204 id=wx.ID_ANY, 1205 style=wx.CP_DEFAULT_STYLE | wx.CP_NO_TLW_RESIZE) 1206 self.Bind( 1207 wx.EVT_COLLAPSIBLEPANE_CHANGED, 1208 self.OnPaneChanged, 1209 self.cp) 1210 1211 self._createFileSelection(parent=self.cp.GetPane()) 1212 sizer.Add(self.cp, proportion=0, 1213 flag=wx.ALL | wx.EXPAND, border=5) 1214 # 1215 # set vector attributes 1216 # 1217 vectorAttrb = self._createVectorAttrb(parent=scrollPanel) 1218 sizer.Add(vectorAttrb, proportion=0, 1219 flag=wx.ALL | wx.EXPAND, border=5) 1220 # 1221 # body & preview 1222 # 1223 bodySizer = self._createBody(parent=scrollPanel) 1224 sizer.Add(bodySizer, proportion=1, 1225 flag=wx.ALL | wx.EXPAND, border=5) 1226 1227 scrollPanel.SetSizer(sizer) 1228 scrollPanel.Fit() 1229 1230 # 1231 # buttons 1232 # 1233 btnSizer = self._createButtons(self.panel) 1234 1235 mainsizer = wx.BoxSizer(wx.VERTICAL) 1236 mainsizer.Add( 1237 scrollPanel, 1238 proportion=1, 1239 flag=wx.EXPAND | wx.ALL, 1240 border=5) 1241 mainsizer.Add(wx.StaticLine(parent=self.panel, id=wx.ID_ANY, 1242 style=wx.LI_HORIZONTAL), proportion=0, 1243 flag=wx.EXPAND | wx.ALL, border=5) 1244 mainsizer.Add(btnSizer, proportion=0, 1245 flag=wx.ALL | wx.EXPAND, border=5) 1246 1247 self.panel.SetSizer(mainsizer) 1248 mainsizer.Layout() 1249 mainsizer.Fit(self.panel) 1250 self.Layout() 1251 1252 def OnPaneChanged(self, event=None): 1253 # redo the layout 1254 self.panel.Layout() 1255 # and also change the labels 1256 if self.cp.IsExpanded(): 1257 self.cp.SetLabel('') 1258 else: 1259 self.cp.SetLabel(_("Import or export color table")) 1260 1261 def CheckMapset(self): 1262 """Check if current vector is in current mapset""" 1263 if grass.find_file(name=self.inmap, element='vector')[ 1264 'mapset'] == grass.gisenv()['MAPSET']: 1265 return True 1266 else: 1267 return False 1268 1269 def NoConnection(self, vectorName): 1270 dlg = wx.MessageDialog( 1271 parent=self, 1272 message=_( 1273 "Database connection for vector map <%s> " 1274 "is not defined in DB file. Do you want to create and " 1275 "connect new attribute table?") % 1276 vectorName, 1277 caption=_("No database connection defined"), 1278 style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION | wx.CENTRE) 1279 if dlg.ShowModal() == wx.ID_YES: 1280 dlg.Destroy() 1281 GUI(parent=self).ParseCommand(['v.db.addtable', 'map=' + self.inmap], 1282 completed=(self.CreateAttrTable, self.inmap, '')) 1283 else: 1284 dlg.Destroy() 1285 1286 def OnCheckColumn(self, event): 1287 """Use color column instead of color table""" 1288 if self.useColumn.GetValue(): 1289 self.properties['loadColumn'] = self.fromColumn.GetValue() 1290 self.properties['storeColumn'] = self.toColumn.GetValue() 1291 self.fromColumn.Enable(True) 1292 self.toColumn.Enable(True) 1293 self.colorTable = False 1294 1295 if self.properties['loadColumn']: 1296 self.LoadTable() 1297 else: 1298 self.rulesPanel.Clear() 1299 else: 1300 self.properties['loadColumn'] = '' 1301 self.properties['storeColumn'] = '' 1302 self.fromColumn.Enable(False) 1303 self.toColumn.Enable(False) 1304 self.colorTable = True 1305 self.LoadTable() 1306 1307 def EnableVectorAttributes(self, enable): 1308 """Enable/disable part of dialog connected with db""" 1309 for child in self.colorColumnSizer.GetChildren(): 1310 child.GetWindow().Enable(enable) 1311 1312 def DisableClearAll(self): 1313 """Enable, disable the whole dialog""" 1314 self.rulesPanel.Clear() 1315 self.EnableVectorAttributes(False) 1316 self.btnPreview.Enable(False) 1317 self.btnOK.Enable(False) 1318 self.btnApply.Enable(False) 1319 self.preview.EraseMap() 1320 1321 def OnSelectionInput(self, event): 1322 """Vector map selected""" 1323 if event: 1324 if self.inmap: 1325 # switch to another map -> delete temporary column 1326 self.DeleteTemporaryColumn() 1327 self.inmap = event.GetString() 1328 1329 if self.version7 and self.attributeType == 'color': 1330 self.loadRules.SetValue('') 1331 self.saveRules.SetValue('') 1332 1333 if self.inmap: 1334 if not grass.find_file(name=self.inmap, element='vector')['file']: 1335 self.inmap = None 1336 1337 self.UpdateDialog() 1338 1339 def UpdateDialog(self): 1340 """Update dialog after map selection""" 1341 1342 if not self.inmap: 1343 self.DisableClearAll() 1344 return 1345 1346 if not self.CheckMapset(): 1347 # v.colors doesn't need the map to be in current mapset 1348 if not (self.version7 and self.attributeType == 'color'): 1349 message = _( 1350 "Selected map <%(map)s> is not in current mapset <%(mapset)s>. " 1351 "Attribute table cannot be edited.") % { 1352 'map': self.inmap, 1353 'mapset': grass.gisenv()['MAPSET']} 1354 wx.CallAfter(GMessage, parent=self, message=message) 1355 self.DisableClearAll() 1356 return 1357 1358 # check for db connection 1359 self.dbInfo = VectorDBInfo(self.inmap) 1360 enable = True 1361 1362 if not len(self.dbInfo.layers): # no connection 1363 if not (self.version7 and self.attributeType == 1364 'color'): # otherwise it doesn't matter 1365 wx.CallAfter(self.NoConnection, self.inmap) 1366 enable = False 1367 for combo in (self.layerSelect, self.sourceColumn, 1368 self.fromColumn, self.toColumn): 1369 combo.SetValue("") 1370 combo.Clear() 1371 for prop in ('sourceColumn', 'loadColumn', 'storeColumn'): 1372 self.properties[prop] = '' 1373 self.EnableVectorAttributes(False) 1374 else: # db connection exist 1375 # initialize layer selection combobox 1376 self.EnableVectorAttributes(True) 1377 self.layerSelect.InsertLayers(self.inmap) 1378 # initialize attribute table for layer=1 1379 self.properties['layer'] = self.layerSelect.GetString(0) 1380 self.layerSelect.SetStringSelection(self.properties['layer']) 1381 layer = int(self.properties['layer']) 1382 self.properties['table'] = self.dbInfo.layers[layer]['table'] 1383 1384 if self.attributeType == 'color': 1385 self.AddTemporaryColumn(type='varchar(11)') 1386 else: 1387 self.AddTemporaryColumn(type='integer') 1388 1389 # initialize column selection comboboxes 1390 1391 self.OnLayerSelection(event=None) 1392 1393 if self.version7 and self.attributeType == 'color': 1394 self.useColumn.SetValue(False) 1395 self.OnCheckColumn(event=None) 1396 self.useColumn.Enable(self.CheckMapset()) 1397 else: 1398 self.LoadTable() 1399 1400 self.btnPreview.Enable(enable) 1401 self.btnOK.Enable(enable) 1402 self.btnApply.Enable(enable) 1403 1404 def AddTemporaryColumn(self, type): 1405 """Add temporary column to not overwrite the original values, 1406 need to be deleted when closing dialog and unloading map 1407 1408 :param type: type of column (e.g. vachar(11))""" 1409 if not self.CheckMapset(): 1410 return 1411 # because more than one dialog with the same map can be opened we must test column name and 1412 # create another one 1413 while self.properties['tmpColumn'] in self.dbInfo.GetTableDesc(self.properties[ 1414 'table']).keys(): 1415 name, idx = self.properties['tmpColumn'].split('_') 1416 idx = int(idx) 1417 idx += 1 1418 self.properties['tmpColumn'] = name + '_' + str(idx) 1419 1420 if self.version7: 1421 modul = 'v.db.addcolumn' 1422 else: 1423 modul = 'v.db.addcol' 1424 ret = RunCommand(modul, 1425 parent=self, 1426 map=self.inmap, 1427 layer=self.properties['layer'], 1428 column='%s %s' % (self.properties['tmpColumn'], type)) 1429 1430 def DeleteTemporaryColumn(self): 1431 """Delete temporary column""" 1432 if not self.CheckMapset(): 1433 return 1434 1435 if self.inmap: 1436 if self.version7: 1437 modul = 'v.db.dropcolumn' 1438 else: 1439 modul = 'v.db.dropcol' 1440 ret = RunCommand(modul, 1441 map=self.inmap, 1442 layer=self.properties['layer'], 1443 column=self.properties['tmpColumn']) 1444 1445 def OnLayerSelection(self, event): 1446 # reset choices in column selection comboboxes if layer changes 1447 vlayer = int(self.layerSelect.GetStringSelection()) 1448 self._columnWidgetEvtHandler(bind=False) 1449 self.sourceColumn.InsertColumns( 1450 vector=self.inmap, layer=vlayer, 1451 type=['integer', 'double precision'], 1452 dbInfo=self.dbInfo, excludeCols=['tmpColumn']) 1453 self.sourceColumn.SetValue('cat') 1454 self.properties['sourceColumn'] = self.sourceColumn.GetValue() 1455 1456 if self.attributeType == 'color': 1457 type = ['character'] 1458 else: 1459 type = ['integer'] 1460 self.fromColumn.InsertColumns( 1461 vector=self.inmap, 1462 layer=vlayer, 1463 type=type, 1464 dbInfo=self.dbInfo, 1465 excludeCols=['tmpColumn']) 1466 self.toColumn.InsertColumns( 1467 vector=self.inmap, 1468 layer=vlayer, 1469 type=type, 1470 dbInfo=self.dbInfo, 1471 excludeCols=['tmpColumn']) 1472 1473 v = self.columnsProp[self.attributeType]['name'] 1474 found = False 1475 if v in self.fromColumn.GetColumns(): 1476 found = True 1477 1478 if found != wx.NOT_FOUND: 1479 self.fromColumn.SetValue(v) 1480 self.toColumn.SetValue(v) 1481 self.properties['loadColumn'] = v 1482 self.properties['storeColumn'] = v 1483 else: 1484 self.properties['loadColumn'] = '' 1485 self.properties['storeColumn'] = '' 1486 1487 self._columnWidgetEvtHandler() 1488 1489 if event: 1490 self.LoadTable() 1491 self.Update() 1492 1493 def OnSourceColumnSelection(self, event): 1494 self.properties['sourceColumn'] = event.GetString() 1495 1496 self.LoadTable() 1497 1498 def OnAddColumn(self, event): 1499 """Add GRASS(RGB,SIZE,WIDTH) column if it doesn't exist""" 1500 if self.columnsProp[self.attributeType][ 1501 'name'] not in self.fromColumn.GetColumns(): 1502 if self.version7: 1503 modul = 'v.db.addcolumn' 1504 else: 1505 modul = 'v.db.addcol' 1506 ret = RunCommand( 1507 modul, map=self.inmap, layer=self.properties['layer'], columns='%s %s' % 1508 (self.columnsProp[ 1509 self.attributeType]['name'], self.columnsProp[ 1510 self.attributeType]['type1'])) 1511 self.toColumn.InsertColumns( 1512 self.inmap, 1513 self.properties['layer'], 1514 type=self.columnsProp[ 1515 self.attributeType]['type2']) 1516 self.toColumn.SetValue( 1517 self.columnsProp[ 1518 self.attributeType]['name']) 1519 self.properties['storeColumn'] = self.toColumn.GetValue() 1520 1521 self.LoadTable() 1522 else: 1523 GMessage(parent=self, 1524 message=_("%s column already exists.") % 1525 self.columnsProp[self.attributeType]['name']) 1526 1527 def CreateAttrTable(self, dcmd, layer, params, propwin): 1528 """Create attribute table""" 1529 if dcmd: 1530 cmd = cmdlist_to_tuple(dcmd) 1531 ret = RunCommand(cmd[0], **cmd[1]) 1532 if ret == 0: 1533 self.OnSelectionInput(None) 1534 return True 1535 1536 for combo in (self.layerSelect, self.sourceColumn, 1537 self.fromColumn, self.toColumn): 1538 combo.SetValue("") 1539 combo.Disable() 1540 return False 1541 1542 def LoadTable(self): 1543 """Load table""" 1544 if self.colorTable: 1545 ColorTable.LoadTable(self, mapType='vector') 1546 else: 1547 self.LoadRulesFromColumn() 1548 1549 def LoadRulesFromColumn(self): 1550 """Load current column (GRASSRGB, size column)""" 1551 1552 self.rulesPanel.Clear() 1553 if not self.properties['sourceColumn']: 1554 self.preview.EraseMap() 1555 return 1556 1557 busy = wx.BusyInfo( 1558 _("Please wait, loading data from attribute table..."), 1559 parent=self) 1560 wx.GetApp().Yield() 1561 1562 columns = self.properties['sourceColumn'] 1563 if self.properties['loadColumn']: 1564 columns += ',' + self.properties['loadColumn'] 1565 1566 sep = ';' 1567 if self.inmap: 1568 outFile = tempfile.NamedTemporaryFile(mode='w+') 1569 ret = RunCommand('v.db.select', 1570 quiet=True, 1571 flags='c', 1572 map=self.inmap, 1573 layer=self.properties['layer'], 1574 columns=columns, 1575 sep=sep, 1576 stdout=outFile) 1577 else: 1578 self.preview.EraseMap() 1579 del busy 1580 return 1581 1582 outFile.seek(0) 1583 i = 0 1584 minim = maxim = 0.0 1585 limit = 1000 1586 1587 colvallist = [] 1588 readvals = False 1589 1590 while True: 1591 # os.linesep doesn't work here (MSYS) 1592 record = outFile.readline().replace('\n', '') 1593 if not record: 1594 break 1595 self.rulesPanel.ruleslines[i] = {} 1596 1597 if not self.properties['loadColumn']: 1598 col1 = record 1599 col2 = None 1600 else: 1601 col1, col2 = record.split(sep) 1602 1603 if float(col1) < minim: 1604 minim = float(col1) 1605 if float(col1) > maxim: 1606 maxim = float(col1) 1607 1608 # color rules list should only have unique values of col1, not all 1609 # records 1610 if col1 not in colvallist: 1611 self.rulesPanel.ruleslines[i]['value'] = col1 1612 self.rulesPanel.ruleslines[i][self.attributeType] = col2 1613 1614 colvallist.append(col1) 1615 i += 1 1616 1617 if i > limit and readvals == False: 1618 dlg = wx.MessageDialog(parent=self, message=_( 1619 "Number of loaded records reached %d, " 1620 "displaying all the records will be time-consuming " 1621 "and may lead to computer freezing, " 1622 "do you still want to continue?") % i, 1623 caption=_("Too many records"), 1624 style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) 1625 if dlg.ShowModal() == wx.ID_YES: 1626 readvals = True 1627 dlg.Destroy() 1628 else: 1629 del busy 1630 dlg.Destroy() 1631 self.updateColumn = False 1632 return 1633 1634 self.rulesPanel.AddRules(i, start=True) 1635 ret = self.rulesPanel.LoadRules() 1636 1637 self.properties['min'], self.properties['max'] = minim, maxim 1638 self.SetRangeLabel() 1639 1640 if ret: 1641 self.OnPreview() 1642 else: 1643 self.rulesPanel.Clear() 1644 1645 del busy 1646 1647 def SetRangeLabel(self): 1648 """Set labels with info about attribute column range""" 1649 1650 if self.properties['sourceColumn']: 1651 ctype = self.dbInfo.GetTableDesc( 1652 self.properties['table'])[ 1653 self.properties['sourceColumn']]['ctype'] 1654 else: 1655 ctype = int 1656 1657 range = '' 1658 if self.properties['min'] or self.properties['max']: 1659 if ctype == float: 1660 range = "%s: %.1f - %.1f)" % (_("range"), 1661 self.properties['min'], 1662 self.properties['max']) 1663 elif ctype == int: 1664 range = "%s: %d - %d)" % (_("range"), 1665 self.properties['min'], 1666 self.properties['max']) 1667 if range: 1668 if self.colorTable: 1669 self.cr_label.SetLabel( 1670 _("Enter vector attribute values or percents %s:") % 1671 range) 1672 else: 1673 self.cr_label.SetLabel( 1674 _("Enter vector attribute values %s:") % 1675 range) 1676 else: 1677 if self.colorTable: 1678 self.cr_label.SetLabel( 1679 _("Enter vector attribute values or percents:")) 1680 else: 1681 self.cr_label.SetLabel(_("Enter vector attribute values:")) 1682 1683 def OnFromColSelection(self, event): 1684 """Selection in combobox (for loading values) changed""" 1685 self.properties['loadColumn'] = event.GetString() 1686 1687 self.LoadTable() 1688 1689 def OnToColSelection(self, event): 1690 """Selection in combobox (for storing values) changed""" 1691 self.properties['storeColumn'] = event.GetString() 1692 1693 def OnPreview(self, event=None, tmp=True): 1694 """Update preview (based on computational region)""" 1695 if self.colorTable: 1696 self.OnTablePreview(tmp) 1697 else: 1698 self.OnColumnPreview() 1699 1700 def OnTablePreview(self, tmp): 1701 """Update preview (based on computational region)""" 1702 if not self.inmap: 1703 self.preview.EraseMap() 1704 return 1705 1706 ltype = 'vector' 1707 cmdlist = ['d.vect', 1708 'map=%s' % self.inmap] 1709 1710 # find existing color table and copy to temp file 1711 try: 1712 name, mapset = self.inmap.split('@') 1713 except ValueError: 1714 name = self.inmap 1715 mapset = grass.find_file(self.inmap, element='cell')['mapset'] 1716 if not mapset: 1717 return 1718 1719 old_colrtable = None 1720 if mapset == grass.gisenv()['MAPSET']: 1721 old_colrtable = grass.find_file( 1722 name='colr', element=os.path.join( 1723 'vector', name))['file'] 1724 else: 1725 old_colrtable = grass.find_file( 1726 name=name, element=os.path.join( 1727 'vcolr2', mapset))['file'] 1728 1729 if old_colrtable: 1730 colrtemp = utils.GetTempfile() 1731 shutil.copyfile(old_colrtable, colrtemp) 1732 1733 ColorTable.DoPreview(self, ltype, cmdlist) 1734 1735 # restore previous color table 1736 if tmp: 1737 if old_colrtable: 1738 shutil.copyfile(colrtemp, old_colrtable) 1739 os.remove(colrtemp) 1740 else: 1741 RunCommand('v.colors', 1742 parent=self, 1743 flags='r', 1744 map=self.inmap) 1745 1746 def OnColumnPreview(self): 1747 """Update preview (based on computational region)""" 1748 if not self.inmap or not self.properties['tmpColumn']: 1749 self.preview.EraseMap() 1750 return 1751 1752 cmdlist = ['d.vect', 1753 'map=%s' % self.inmap, 1754 'type=point,line,boundary,area'] 1755 1756 if self.attributeType == 'color': 1757 cmdlist.append('rgb_column=%s' % self.properties['tmpColumn']) 1758 elif self.attributeType == 'size': 1759 cmdlist.append('size_column=%s' % self.properties['tmpColumn']) 1760 elif self.attributeType == 'width': 1761 cmdlist.append('width_column=%s' % self.properties['tmpColumn']) 1762 1763 ltype = 'vector' 1764 1765 ColorTable.DoPreview(self, ltype, cmdlist) 1766 1767 def OnHelp(self, event): 1768 """Show GRASS manual page""" 1769 cmd = 'v.colors' 1770 ColorTable.RunHelp(self, cmd=cmd) 1771 1772 def UseAttrColumn(self, useAttrColumn): 1773 """Find layers and apply the changes in d.vect command""" 1774 layers = self.layerTree.FindItemByData(key='name', value=self.inmap) 1775 if not layers: 1776 return 1777 for layer in layers: 1778 if self.layerTree.GetLayerInfo(layer, key='type') != 'vector': 1779 continue 1780 cmdlist = self.layerTree.GetLayerInfo( 1781 layer, key='maplayer').GetCmd() 1782 1783 if self.attributeType == 'color': 1784 if useAttrColumn: 1785 cmdlist[1].update( 1786 {'rgb_column': self.properties['storeColumn']}) 1787 else: 1788 cmdlist[1].pop('rgb_column', None) 1789 elif self.attributeType == 'size': 1790 cmdlist[1].update( 1791 {'size_column': self.properties['storeColumn']}) 1792 elif self.attributeType == 'width': 1793 cmdlist[1].update( 1794 {'width_column': self.properties['storeColumn']}) 1795 self.layerTree.SetLayerInfo(layer, key='cmd', value=cmdlist) 1796 1797 def CreateColorTable(self, tmp=False): 1798 """Create color rules (color table or color column)""" 1799 if self.colorTable: 1800 ret = ColorTable.CreateColorTable(self) 1801 else: 1802 if self.updateColumn: 1803 ret = self.UpdateColorColumn(tmp) 1804 else: 1805 ret = True 1806 1807 return ret 1808 1809 def UpdateColorColumn(self, tmp): 1810 """Creates color table 1811 1812 :return: True on success 1813 :return: False on failure 1814 """ 1815 rulestxt = '' 1816 1817 for rule in six.itervalues(self.rulesPanel.ruleslines): 1818 if 'value' not in rule: # skip empty rules 1819 break 1820 1821 if tmp: 1822 rgb_col = self.properties['tmpColumn'] 1823 else: 1824 rgb_col = self.properties['storeColumn'] 1825 if not self.properties['storeColumn']: 1826 GMessage(parent=self.parent, message=_( 1827 "Please select column to save values to.")) 1828 1829 rulestxt += "UPDATE %s SET %s='%s' WHERE %s ;\n" % ( 1830 self.properties['table'], 1831 rgb_col, rule[self.attributeType], 1832 rule['value']) 1833 if not rulestxt: 1834 return False 1835 1836 gtemp = utils.GetTempfile() 1837 output = open(gtemp, "w") 1838 try: 1839 output.write(rulestxt) 1840 finally: 1841 output.close() 1842 1843 RunCommand('db.execute', 1844 parent=self, 1845 input=gtemp) 1846 return True 1847 1848 def OnCancel(self, event): 1849 """Do not apply any changes and close the dialog""" 1850 self.DeleteTemporaryColumn() 1851 self.Map.Clean() 1852 self.Destroy() 1853 1854 def _apply(self, updatePreview=True): 1855 """Apply selected color table 1856 1857 :return: True on success otherwise False 1858 """ 1859 if self.colorTable: 1860 self.UseAttrColumn(False) 1861 else: 1862 if not self.properties['storeColumn']: 1863 GError(_("No color column defined. Operation canceled."), 1864 parent=self) 1865 return 1866 1867 self.UseAttrColumn(True) 1868 1869 return ColorTable._apply(self, updatePreview) 1870 1871 def _columnWidgetEvtHandler(self, bind=True): 1872 """Bind/Unbind Column widgets handlers""" 1873 widgets = [ 1874 { 1875 'widget': self.sourceColumn, 1876 'event': wx.EVT_TEXT, 1877 'handler': self.OnSourceColumnSelection, 1878 }, 1879 { 1880 'widget': self.fromColumn, 1881 'event': wx.EVT_TEXT, 1882 'handler': self.OnFromColSelection, 1883 }, 1884 { 1885 'widget': self.toColumn, 1886 'event': wx.EVT_TEXT, 1887 'handler': self.OnToColSelection, 1888 }, 1889 ] 1890 for widget in widgets: 1891 if bind is True: 1892 getattr(widget['widget'], 'Bind')( 1893 widget['event'], widget['handler'], 1894 ) 1895 else: 1896 getattr(widget['widget'], 'Unbind')(widget['event']) 1897 1898 1899class ThematicVectorTable(VectorColorTable): 1900 1901 def __init__(self, parent, vectorType, **kwargs): 1902 """Dialog for interactively entering color/size rules 1903 for vector maps for thematic mapping in nviz""" 1904 self.vectorType = vectorType 1905 VectorColorTable.__init__(self, parent=parent, **kwargs) 1906 1907 self.SetTitle(_("Thematic mapping for vector map in 3D view")) 1908 1909 def _initLayer(self): 1910 """Set initial layer when opening dialog""" 1911 self.inmap = self.parent.GetLayerData(nvizType='vector', nameOnly=True) 1912 self.selectionInput.SetValue(self.inmap) 1913 self.selectionInput.Disable() 1914 1915 def _apply(self, updatePreview=True): 1916 """Apply selected color table 1917 1918 :return: True on success otherwise False 1919 """ 1920 ret = self.CreateColorTable() 1921 if not ret: 1922 GMessage(parent=self, message=_("No valid color rules given.")) 1923 1924 data = self.parent.GetLayerData(nvizType='vector') 1925 data['vector']['points']['thematic'][ 1926 'layer'] = int(self.properties['layer']) 1927 1928 value = None 1929 if self.properties['storeColumn']: 1930 value = self.properties['storeColumn'] 1931 1932 if not self.colorTable: 1933 if self.attributeType == 'color': 1934 data['vector'][self.vectorType][ 1935 'thematic']['rgbcolumn'] = value 1936 else: 1937 data['vector'][self.vectorType][ 1938 'thematic']['sizecolumn'] = value 1939 else: 1940 if self.attributeType == 'color': 1941 data['vector'][self.vectorType]['thematic']['rgbcolumn'] = None 1942 else: 1943 data['vector'][self.vectorType][ 1944 'thematic']['sizecolumn'] = None 1945 1946 data['vector'][self.vectorType]['thematic']['update'] = None 1947 1948 from nviz.main import haveNviz 1949 if haveNviz: 1950 from nviz.mapwindow import wxUpdateProperties 1951 1952 event = wxUpdateProperties(data=data) 1953 wx.PostEvent(self.parent.mapWindow, event) 1954 1955 self.parent.mapWindow.Refresh(False) 1956 1957 return ret 1958 1959 1960class BufferedWindow(wx.Window): 1961 """A Buffered window class""" 1962 1963 def __init__(self, parent, id, 1964 style=wx.NO_FULL_REPAINT_ON_RESIZE, 1965 Map=None, **kwargs): 1966 1967 wx.Window.__init__(self, parent, id, style=style, **kwargs) 1968 1969 self.parent = parent 1970 self.Map = Map 1971 1972 # re-render the map from GRASS or just redraw image 1973 self.render = True 1974 # indicates whether or not a resize event has taken place 1975 self.resize = False 1976 1977 # 1978 # event bindings 1979 # 1980 self.Bind(wx.EVT_PAINT, self.OnPaint) 1981 self.Bind(wx.EVT_IDLE, self.OnIdle) 1982 self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None) 1983 1984 # 1985 # render output objects 1986 # 1987 # image file to be rendered 1988 self.mapfile = None 1989 # wx.Image object (self.mapfile) 1990 self.img = None 1991 1992 self.pdc = PseudoDC() 1993 # will store an off screen empty bitmap for saving to file 1994 self._Buffer = None 1995 1996 # make sure that extents are updated at init 1997 self.Map.region = self.Map.GetRegion() 1998 self.Map.SetRegion() 1999 self.Map.GetRenderMgr().renderDone.connect(self._updatePreviewFinished) 2000 2001 def Draw(self, pdc, img=None, pdctype='image'): 2002 """Draws preview or clears window""" 2003 pdc.BeginDrawing() 2004 2005 Debug.msg(3, "BufferedWindow.Draw(): pdctype=%s" % (pdctype)) 2006 2007 if pdctype == 'clear': # erase the display 2008 bg = wx.WHITE_BRUSH 2009 pdc.SetBackground(bg) 2010 pdc.Clear() 2011 self.Refresh() 2012 pdc.EndDrawing() 2013 return 2014 2015 if pdctype == 'image' and img: 2016 bg = wx.TRANSPARENT_BRUSH 2017 pdc.SetBackground(bg) 2018 bitmap = BitmapFromImage(img) 2019 w, h = bitmap.GetSize() 2020 pdc.DrawBitmap(bitmap, 0, 0, True) # draw the composite map 2021 2022 pdc.EndDrawing() 2023 self.Refresh() 2024 2025 def OnPaint(self, event): 2026 """Draw pseudo DC to buffer""" 2027 self._Buffer = EmptyBitmap(self.Map.width, self.Map.height) 2028 dc = wx.BufferedPaintDC(self, self._Buffer) 2029 2030 # use PrepareDC to set position correctly 2031 # probably does nothing, removed from wxPython 2.9 2032 # self.PrepareDC(dc) 2033 2034 # we need to clear the dc BEFORE calling PrepareDC 2035 bg = wx.Brush(self.GetBackgroundColour()) 2036 dc.SetBackground(bg) 2037 dc.Clear() 2038 2039 # create a clipping rect from our position and size 2040 # and the Update Region 2041 rgn = self.GetUpdateRegion() 2042 r = rgn.GetBox() 2043 2044 # draw to the dc using the calculated clipping rect 2045 self.pdc.DrawToDCClipped(dc, r) 2046 2047 def OnSize(self, event): 2048 """Init image size to match window size""" 2049 # set size of the input image 2050 self.Map.width, self.Map.height = self.GetClientSize() 2051 2052 # Make new off screen bitmap: this bitmap will always have the 2053 # current drawing in it, so it can be used to save the image to 2054 # a file, or whatever. 2055 self._Buffer = EmptyBitmap(self.Map.width, self.Map.height) 2056 2057 # get the image to be rendered 2058 self.img = self.GetImage() 2059 2060 # update map display 2061 if self.img and self.Map.width + self.Map.height > 0: # scale image during resize 2062 self.img = self.img.Scale(self.Map.width, self.Map.height) 2063 self.render = False 2064 self.UpdatePreview() 2065 2066 # re-render image on idle 2067 self.resize = True 2068 2069 def OnIdle(self, event): 2070 """Only re-render a preview image from GRASS during 2071 idle time instead of multiple times during resizing. 2072 """ 2073 if self.resize: 2074 self.render = True 2075 self.UpdatePreview() 2076 event.Skip() 2077 2078 def GetImage(self): 2079 """Converts files to wx.Image""" 2080 if self.Map.mapfile and os.path.isfile(self.Map.mapfile) and \ 2081 os.path.getsize(self.Map.mapfile): 2082 img = wx.Image(self.Map.mapfile, wx.BITMAP_TYPE_ANY) 2083 else: 2084 img = None 2085 2086 return img 2087 2088 def UpdatePreview(self, img=None): 2089 """Update canvas if window changes geometry""" 2090 Debug.msg( 2091 2, "BufferedWindow.UpdatePreview(%s): render=%s" % 2092 (img, self.render)) 2093 2094 if not self.render: 2095 return 2096 2097 # extent is taken from current map display 2098 try: 2099 self.Map.region = copy.deepcopy( 2100 self.parent.parent.GetLayerTree().GetMap().GetCurrentRegion()) 2101 except AttributeError: 2102 self.Map.region = self.Map.GetRegion() 2103 # render new map images 2104 self.mapfile = self.Map.Render(force=self.render) 2105 2106 def _updatePreviewFinished(self): 2107 if not self.render: 2108 return 2109 2110 self.img = self.GetImage() 2111 self.resize = False 2112 2113 if not self.img: 2114 return 2115 2116 # paint images to PseudoDC 2117 self.pdc.Clear() 2118 self.pdc.RemoveAll() 2119 # draw map image background 2120 self.Draw(self.pdc, self.img, pdctype='image') 2121 2122 self.resize = False 2123 2124 def EraseMap(self): 2125 """Erase preview""" 2126 self.Draw(self.pdc, pdctype='clear') 2127