1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909 |
- """
- @package module.colorrules
- @brief Dialog for interactive management of raster/vector color tables
- and color rules.
- Classes:
- - colorrules::RulesPanel
- - colorrules::ColorTable
- - colorrules::RasterColorTable
- - colorrules::VectorColorTable
- - colorrules::ThematicVectorTable
- - colorrules::BufferedWindow
- (C) 2008, 2010-2012 by the GRASS Development Team
- This program is free software under the GNU General Public License
- (>=v2). Read the file COPYING that comes with GRASS for details.
- @author Michael Barton (Arizona State University)
- @author Martin Landa <landa.martin gmail.com> (various updates, pre-defined color table)
- @author Anna Kratochvilova <kratochanna gmail.com> (split to base and derived classes)
- """
- import os
- import shutil
- import copy
- import tempfile
- import wx
- import wx.lib.colourselect as csel
- import wx.lib.scrolledpanel as scrolled
- import wx.lib.filebrowsebutton as filebrowse
- import grass.script as grass
- from grass.script.task import cmdlist_to_tuple
- from core import globalvar
- from core import utils
- from core.utils import _
- from core.gcmd import GMessage, RunCommand, GError
- from gui_core.gselect import Select, LayerSelect, ColumnSelect, VectorDBInfo
- from core.render import Map
- from gui_core.forms import GUI
- from core.debug import Debug as Debug
- from core.settings import UserSettings
- from gui_core.widgets import ColorTablesComboBox
- class RulesPanel:
- def __init__(self, parent, mapType, attributeType, properties, panelWidth = 180):
- """Create rules panel
-
- :param mapType: raster/vector
- :param attributeType: color/size for choosing widget type
- :param properties: properties of classes derived from ColorTable
- :param panelWidth: width of scroll panel"""
-
- self.ruleslines = {}
- self.mapType = mapType
- self.attributeType = attributeType
- self.properties = properties
- self.parent = parent
- self.panelWidth = panelWidth
-
- self.mainSizer = wx.FlexGridSizer(cols = 3, vgap = 6, hgap = 4)
- # put small border at the top of panel
- for i in range(3):
- self.mainSizer.Add(item = wx.Size(3, 3))
-
- self.mainPanel = scrolled.ScrolledPanel(parent, id = wx.ID_ANY,
- size = (self.panelWidth, 300),
- style = wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER)
-
- # (un)check all
- self.checkAll = wx.CheckBox(parent, id = wx.ID_ANY, label = _("Check all"))
- self.checkAll.SetValue(True)
- # clear button
- self.clearAll = wx.Button(parent, id = wx.ID_ANY, label = _("Clear all"))
- # determines how many rules should be added
- self.numRules = wx.SpinCtrl(parent, id = wx.ID_ANY,
- min = 1, max = 1e6, initial = 1)
- # add rules
- self.btnAdd = wx.Button(parent, id = wx.ID_ADD)
-
- self.btnAdd.Bind(wx.EVT_BUTTON, self.OnAddRules)
- self.checkAll.Bind(wx.EVT_CHECKBOX, self.OnCheckAll)
- self.clearAll.Bind(wx.EVT_BUTTON, self.OnClearAll)
- self.mainPanel.SetSizer(self.mainSizer)
- self.mainPanel.SetAutoLayout(True)
- self.mainPanel.SetupScrolling()
-
- def Clear(self):
- """Clear and widgets and delete information"""
- self.ruleslines.clear()
- self.mainSizer.Clear(deleteWindows=True)
-
- def OnCheckAll(self, event):
- """(Un)check all rules"""
- check = event.GetInt()
- for child in self.mainPanel.GetChildren():
- if child.GetName() == 'enable':
- child.SetValue(check)
- else:
- child.Enable(check)
-
- def OnClearAll(self, event):
- """Delete all widgets in panel"""
- self.Clear()
-
- def OnAddRules(self, event):
- """Add rules button pressed"""
- nrules = self.numRules.GetValue()
- self.AddRules(nrules)
-
- def AddRules(self, nrules, start = False):
- """Add rules
-
- :param start: set widgets (not append)
- """
-
- snum = len(self.ruleslines.keys())
- if start:
- snum = 0
- for num in range(snum, snum + nrules):
- # enable
- enable = wx.CheckBox(parent = self.mainPanel, id = num)
- enable.SetValue(True)
- enable.SetName('enable')
- enable.Bind(wx.EVT_CHECKBOX, self.OnRuleEnable)
- # value
- txt_ctrl = wx.TextCtrl(parent = self.mainPanel, id = 1000 + num,
- size = (80, -1),
- style = wx.TE_NOHIDESEL)
- if self.mapType == 'vector':
- txt_ctrl.SetToolTipString(_("Enter vector attribute values"))
- txt_ctrl.Bind(wx.EVT_TEXT, self.OnRuleValue)
- txt_ctrl.SetName('source')
- if self.attributeType == 'color':
- # color
- columnCtrl = csel.ColourSelect(self.mainPanel, id = 2000 + num,
- size = globalvar.DIALOG_COLOR_SIZE)
- columnCtrl.Bind(csel.EVT_COLOURSELECT, self.OnRuleColor)
- columnCtrl.SetName('target')
- if not start:
- self.ruleslines[enable.GetId()] = { 'value' : '',
- 'color': "0:0:0" }
- else:
- # size or width
- init = 2
- if self.attributeType == 'size':
- init = 100
- columnCtrl = wx.SpinCtrl(self.mainPanel, id = 2000 + num,
- size = (50, -1), min = 1, max = 1e4,
- initial = init)
- columnCtrl.Bind(wx.EVT_SPINCTRL, self.OnRuleSize)
- columnCtrl.Bind(wx.EVT_TEXT, self.OnRuleSize)
- columnCtrl.SetName('target')
- if not start:
- self.ruleslines[enable.GetId()] = { 'value' : '',
- self.attributeType: init }
-
- self.mainSizer.Add(item = enable, proportion = 0,
- flag = wx.ALIGN_CENTER_VERTICAL)
- self.mainSizer.Add(item = txt_ctrl, proportion = 0,
- flag = wx.ALIGN_CENTER | wx.RIGHT, border = 5)
- self.mainSizer.Add(item = columnCtrl, proportion = 0,
- flag = wx.ALIGN_CENTER | wx.RIGHT, border = 10)
-
- self.mainPanel.Layout()
- self.mainPanel.SetupScrolling(scroll_x = False)
-
- def OnRuleEnable(self, event):
- """Rule enabled/disabled"""
- id = event.GetId()
-
- if event.IsChecked():
- self.mainPanel.FindWindowById(id + 1000).Enable()
- self.mainPanel.FindWindowById(id + 2000).Enable()
- if self.mapType == 'vector' and not self.parent.GetParent().colorTable:
- vals = []
- vals.append(self.mainPanel.FindWindowById(id + 1000).GetValue())
- try:
- vals.append(self.mainPanel.FindWindowById(id + 1 + 1000).GetValue())
- except AttributeError:
- vals.append(None)
- value = self.SQLConvert(vals)
- else:
- value = self.mainPanel.FindWindowById(id + 1000).GetValue()
- color = self.mainPanel.FindWindowById(id + 2000).GetValue()
-
- if self.attributeType == 'color':
- # color
- color_str = str(color[0]) + ':' \
- + str(color[1]) + ':' \
- + str(color[2])
- self.ruleslines[id] = {'value' : value,
- 'color' : color_str }
-
- else:
- # size or width
- self.ruleslines[id] = {'value' : value,
- self.attributeType : float(color) }
-
- else:
- self.mainPanel.FindWindowById(id + 1000).Disable()
- self.mainPanel.FindWindowById(id + 2000).Disable()
- del self.ruleslines[id]
-
- def OnRuleColor(self, event):
- """Rule color changed"""
- num = event.GetId()
-
- rgba_color = event.GetValue()
-
- rgb_string = str(rgba_color[0]) + ':' \
- + str(rgba_color[1]) + ':' \
- + str(rgba_color[2])
-
- self.ruleslines[num-2000]['color'] = rgb_string
-
- def OnRuleSize(self, event):
- """Rule size changed"""
- num = event.GetId()
- size = event.GetInt()
-
- self.ruleslines[num - 2000][self.attributeType] = size
-
- def OnRuleValue(self, event):
- """Rule value changed"""
- num = event.GetId()
- val = event.GetString().strip()
-
- if val == '':
- return
- try:
- table = self.parent.colorTable
- except AttributeError:
- # due to panel/scrollpanel in vector dialog
- if isinstance(self.parent.GetParent(), RasterColorTable):
- table = self.parent.GetParent().colorTable
- else:
- table = self.parent.GetParent().GetParent().colorTable
- if table:
- self.SetRasterRule(num, val)
- else:
- self.SetVectorRule(num, val)
- def SetRasterRule(self, num, val):
- """Set raster rule"""
- self.ruleslines[num - 1000]['value'] = val
- def SetVectorRule(self, num, val):
- """Set vector rule"""
- vals = []
- vals.append(val)
- try:
- vals.append(self.mainPanel.FindWindowById(num + 1).GetValue())
- except AttributeError:
- vals.append(None)
- self.ruleslines[num - 1000]['value'] = self.SQLConvert(vals)
-
- def Enable(self, enable = True):
- """Enable/Disable all widgets"""
- for child in self.mainPanel.GetChildren():
- child.Enable(enable)
- sql = True
- self.LoadRulesline(sql)# todo
- self.btnAdd.Enable(enable)
- self.numRules.Enable(enable)
- self.checkAll.Enable(enable)
- self.clearAll.Enable(enable)
-
-
- def LoadRules(self):
- message = ""
- for item in range(len(self.ruleslines)):
- try:
- self.mainPanel.FindWindowById(item + 1000).SetValue(self.ruleslines[item]['value'])
- r, g, b = (0, 0, 0) # default
- if not self.ruleslines[item][self.attributeType]:
- if self.attributeType == 'color':
- self.ruleslines[item][self.attributeType] = '%d:%d:%d' % (r, g, b)
- elif self.attributeType == 'size':
- self.ruleslines[item][self.attributeType] = 100
- elif self.attributeType == 'width':
- self.ruleslines[item][self.attributeType] = 2
-
- if self.attributeType == 'color':
- try:
- r, g, b = map(int, self.ruleslines[item][self.attributeType].split(':'))
- except ValueError as e:
- message = _("Bad color format. Use color format '0:0:0'")
- self.mainPanel.FindWindowById(item + 2000).SetValue((r, g, b))
- else:
- value = float(self.ruleslines[item][self.attributeType])
- self.mainPanel.FindWindowById(item + 2000).SetValue(value)
- except:
- continue
-
- if message:
- GMessage(parent = self.parent, message = message)
- return False
-
- return True
-
- def SQLConvert(self, vals):
- """Prepare value for SQL query"""
- if vals[0].isdigit():
- sqlrule = '%s=%s' % (self.properties['sourceColumn'], vals[0])
- if vals[1]:
- sqlrule += ' AND %s<%s' % (self.properties['sourceColumn'], vals[1])
- else:
- sqlrule = '%s=%s' % (self.properties['sourceColumn'], vals[0])
-
- return sqlrule
- class ColorTable(wx.Frame):
- def __init__(self, parent, title, layerTree = None, id = wx.ID_ANY,
- style = wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER,
- **kwargs):
- """Dialog for interactively entering rules for map management
- commands
- :param raster: True to raster otherwise vector
- :param nviz: True if ColorTable is called from nviz thematic mapping
- """
- self.parent = parent # GMFrame ?
- self.layerTree = layerTree # LayerTree or None
-
- wx.Frame.__init__(self, parent, id, title, style = style, **kwargs)
-
- self.SetIcon(wx.Icon(os.path.join(globalvar.ICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
-
- self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
-
- # instance of render.Map to be associated with display
- self.Map = Map()
-
- # input map to change
- self.inmap = ''
-
- # reference to layer with preview
- self.layer = None
-
- # layout
- self._doLayout()
-
- # bindings
- self.Bind(wx.EVT_BUTTON, self.OnHelp, self.btnHelp)
- self.selectionInput.Bind(wx.EVT_TEXT, self.OnSelectionInput)
- self.Bind(wx.EVT_BUTTON, self.OnCancel, self.btnCancel)
- self.Bind(wx.EVT_BUTTON, self.OnApply, self.btnApply)
- self.Bind(wx.EVT_BUTTON, self.OnOK, self.btnOK)
- self.Bind(wx.EVT_BUTTON, self.OnLoadDefaultTable, self.btnDefault)
-
- self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
-
- self.Bind(wx.EVT_BUTTON, self.OnPreview, self.btnPreview)
-
- def _initLayer(self):
- """Set initial layer when opening dialog"""
- # set map layer from layer tree, first selected,
- # if not the right type, than select another
- try:
- sel = self.layerTree.layer_selected
- if sel and self.layerTree.GetLayerInfo(sel, key = 'type') == self.mapType:
- layer = sel
- else:
- layer = self.layerTree.FindItemByData(key = 'type', value = self.mapType)
- except:
- layer = None
- if layer:
- mapLayer = self.layerTree.GetLayerInfo(layer, key = 'maplayer')
- name = mapLayer.GetName()
- type = mapLayer.GetType()
- self.selectionInput.SetValue(name)
- self.inmap = name
-
- def _createMapSelection(self, parent):
- """Create map selection part of dialog"""
- # top controls
- if self.mapType == 'raster':
- maplabel = _('Select raster map:')
- else:
- maplabel = _('Select vector map:')
- inputBox = wx.StaticBox(parent, id = wx.ID_ANY,
- label = " %s " % maplabel)
- inputSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL)
- self.selectionInput = Select(parent = parent, id = wx.ID_ANY,
- size = globalvar.DIALOG_GSELECT_SIZE,
- type = self.mapType)
- # layout
- inputSizer.Add(item = self.selectionInput,
- flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border = 5)
-
- return inputSizer
-
- def _createFileSelection(self, parent):
- """Create file (open/save rules) selection part of dialog"""
- inputBox = wx.StaticBox(parent, id = wx.ID_ANY,
- label = " %s " % _("Import or export color table:"))
- inputSizer = wx.StaticBoxSizer(inputBox, wx.HORIZONTAL)
-
- self.loadRules = filebrowse.FileBrowseButton(parent = parent, id = wx.ID_ANY, fileMask = '*',
- labelText = '',
- dialogTitle = _('Choose file to load color table'),
- buttonText = _('Load'),
- toolTip = _("Type filename or click to choose "
- "file and load color table"),
- startDirectory = os.getcwd(), fileMode = wx.FD_OPEN,
- changeCallback = self.OnLoadRulesFile)
- self.saveRules = filebrowse.FileBrowseButton(parent = parent, id = wx.ID_ANY, fileMask = '*',
- labelText = '',
- dialogTitle = _('Choose file to save color table'),
- toolTip = _("Type filename or click to choose "
- "file and save color table"),
- buttonText = _('Save'),
- startDirectory = os.getcwd(), fileMode = wx.FD_SAVE,
- changeCallback = self.OnSaveRulesFile)
- colorTable = ColorTablesComboBox(parent = parent, size=globalvar.DIALOG_COMBOBOX_SIZE,
- choices = utils.GetColorTables(), name="colorTableChoice")
- self.btnSet = wx.Button(parent = parent, id = wx.ID_ANY, label = _("&Set"), name = 'btnSet')
- self.btnSet.Bind(wx.EVT_BUTTON, self.OnSetTable)
- self.btnSet.Enable(False)
-
- # layout
- gridSizer = wx.GridBagSizer(hgap = 2, vgap = 2)
-
- gridSizer.Add(item = wx.StaticText(parent, label = _("Load color table:")),
- pos = (0, 0), flag = wx.ALIGN_CENTER_VERTICAL)
- gridSizer.Add(item = colorTable, pos = (0, 1))
- gridSizer.Add(item = self.btnSet, pos = (0, 2), flag = wx.ALIGN_RIGHT)
- gridSizer.Add(item = wx.StaticText(parent, label = _('Load color table from file:')),
- pos = (1, 0), flag = wx.ALIGN_CENTER_VERTICAL)
- gridSizer.Add(item = self.loadRules, pos = (1, 1), span = (1, 2), flag = wx.EXPAND)
- gridSizer.Add(item = wx.StaticText(parent, label = _('Save color table to file:')),
- pos = (2, 0), flag = wx.ALIGN_CENTER_VERTICAL)
- gridSizer.Add(item = self.saveRules, pos = (2, 1), span = (1, 2), flag = wx.EXPAND)
-
- gridSizer.AddGrowableCol(1)
- inputSizer.Add(gridSizer, proportion = 1, flag = wx.EXPAND | wx.ALL,
- border = 5)
-
- if self.mapType == 'vector':
- # parent is collapsible pane
- parent.SetSizer(inputSizer)
-
- return inputSizer
-
- def _createPreview(self, parent):
- """Create preview"""
- # initialize preview display
- self.InitDisplay()
- self.preview = BufferedWindow(parent, id = wx.ID_ANY, size = (400, 300),
- Map = self.Map)
- self.preview.EraseMap()
-
- def _createButtons(self, parent):
- """Create buttons for leaving dialog"""
- self.btnHelp = wx.Button(parent, id = wx.ID_HELP)
- self.btnCancel = wx.Button(parent, id = wx.ID_CANCEL)
- self.btnApply = wx.Button(parent, id = wx.ID_APPLY)
- self.btnOK = wx.Button(parent, id = wx.ID_OK)
- self.btnDefault = wx.Button(parent, id = wx.ID_ANY,
- label = _("Reload default table"))
-
- self.btnOK.SetDefault()
- self.btnOK.Enable(False)
- self.btnApply.Enable(False)
- self.btnDefault.Enable(False)
-
- # layout
- btnSizer = wx.BoxSizer(wx.HORIZONTAL)
- btnSizer.Add(wx.Size(-1, -1), proportion = 1)
- btnSizer.Add(self.btnDefault,
- flag = wx.LEFT | wx.RIGHT, border = 5)
- btnSizer.Add(self.btnHelp,
- flag = wx.LEFT | wx.RIGHT, border = 5)
- btnSizer.Add(self.btnCancel,
- flag = wx.LEFT | wx.RIGHT, border = 5)
- btnSizer.Add(self.btnApply,
- flag = wx.LEFT | wx.RIGHT, border = 5)
- btnSizer.Add(self.btnOK,
- flag = wx.LEFT | wx.RIGHT, border = 5)
-
- return btnSizer
-
- def _createBody(self, parent):
- """Create dialog body consisting of rules and preview"""
- bodySizer = wx.GridBagSizer(hgap = 5, vgap = 5)
- row = 0
- # label with range
- self.cr_label = wx.StaticText(parent, id = wx.ID_ANY)
- bodySizer.Add(item = self.cr_label, pos = (row, 0), span = (1, 3),
- flag = wx.ALL, border = 5)
- row += 1
- # color table
- self.rulesPanel = RulesPanel(parent = parent, mapType = self.mapType,
- attributeType = self.attributeType, properties = self.properties)
-
- bodySizer.Add(item = self.rulesPanel.mainPanel, pos = (row, 0),
- span = (1, 2), flag = wx.EXPAND)
- # add two rules as default
- self.rulesPanel.AddRules(2)
-
- # preview window
- self._createPreview(parent = parent)
- bodySizer.Add(item = self.preview, pos = (row, 2),
- flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER)
-
- row += 1
- # add ckeck all and clear all
- bodySizer.Add(item = self.rulesPanel.checkAll, flag = wx.ALIGN_CENTER_VERTICAL,
- pos = (row, 0))
- bodySizer.Add(item = self.rulesPanel.clearAll, pos = (row, 1))
-
- # preview button
- self.btnPreview = wx.Button(parent, id = wx.ID_ANY,
- label = _("Preview"))
- bodySizer.Add(item = self.btnPreview, pos = (row, 2),
- flag = wx.ALIGN_RIGHT)
- self.btnPreview.Enable(False)
- self.btnPreview.SetToolTipString(_("Show preview of map "
- "(current Map Display extent is used)."))
-
- row +=1
- # add rules button and spin to sizer
- bodySizer.Add(item = self.rulesPanel.numRules, pos = (row, 0),
- flag = wx.ALIGN_CENTER_VERTICAL)
- bodySizer.Add(item = self.rulesPanel.btnAdd, pos = (row, 1))
-
- bodySizer.AddGrowableRow(1)
- bodySizer.AddGrowableCol(2)
- return bodySizer
-
- def InitDisplay(self):
- """Initialize preview display, set dimensions and region
- """
- self.width = self.Map.width = 400
- self.height = self.Map.height = 300
- self.Map.geom = self.width, self.height
- def OnCloseWindow(self, event):
- """Window closed
- """
- self.OnCancel(event)
-
- def OnApply(self, event):
- """Apply selected color table
-
- :return: True on success otherwise False
- """
- ret = self.CreateColorTable()
- if not ret:
- GMessage(parent = self, message = _("No valid color rules given."))
- else:
- # re-render preview and current map window
- self.OnPreview(None)
- display = self.layerTree.GetMapDisplay()
- if display and display.IsAutoRendered():
- display.GetWindow().UpdateMap(render = True)
-
- return ret
- def OnOK(self, event):
- """Apply selected color table and close the dialog"""
- if self.OnApply(event):
- self.OnCancel(event)
-
- def OnCancel(self, event):
- """Do not apply any changes, remove associated
- rendered images and close the dialog"""
- self.Map.Clean()
- self.Destroy()
-
- def OnSetTable(self, event):
- """Load pre-defined color table"""
- ct = self.FindWindowByName("colorTableChoice").GetValue()
- # save original color table
- ctOriginal = RunCommand('r.colors.out', read = True, map = self.inmap, rules = '-')
- # set new color table
- ret, err = RunCommand('r.colors', map = self.inmap, color = ct, getErrorMsg = True)
- if ret != 0:
- GError(err, parent = self)
- return
- ctNew = RunCommand('r.colors.out', read = True, map = self.inmap, rules = '-')
- # restore original table
- RunCommand('r.colors', map = self.inmap, rules = '-', stdin = ctOriginal)
- # load color table
- self.rulesPanel.Clear()
- self.ReadColorTable(ctable = ctNew)
- def OnSaveRulesFile(self, event):
- """Save color table to file"""
- path = event.GetString()
- if not os.path.exists(path):
- return
-
- rulestxt = ''
- for rule in self.rulesPanel.ruleslines.itervalues():
- if 'value' not in rule:
- continue
- rulestxt += rule['value'] + ' ' + rule['color'] + '\n'
- if not rulestxt:
- GMessage(message = _("Nothing to save."),
- parent = self)
- return
-
- fd = open(path, 'w')
- fd.write(rulestxt)
- fd.close()
-
- def OnLoadRulesFile(self, event):
- """Load color table from file"""
- path = event.GetString()
- if not os.path.exists(path):
- return
-
- self.rulesPanel.Clear()
-
- fd = open(path, 'r')
- self.ReadColorTable(ctable = fd.read())
- fd.close()
-
- def ReadColorTable(self, ctable):
- """Read color table
-
- :param table: color table in format coming from r.colors.out"""
-
- rulesNumber = len(ctable.splitlines())
- self.rulesPanel.AddRules(rulesNumber)
-
- minim = maxim = count = 0
- for line in ctable.splitlines():
- try:
- value, color = map(lambda x: x.strip(), line.split(' '))
- except ValueError:
- GMessage(parent = self, message = _("Invalid color table format"))
- self.rulesPanel.Clear()
- return
-
- self.rulesPanel.ruleslines[count]['value'] = value
- self.rulesPanel.ruleslines[count]['color'] = color
- self.rulesPanel.mainPanel.FindWindowById(count + 1000).SetValue(value)
- rgb = list()
- for c in color.split(':'):
- rgb.append(int(c))
- self.rulesPanel.mainPanel.FindWindowById(count + 2000).SetColour(rgb)
- # range
- try:
- if float(value) < minim:
- minim = float(value)
- if float(value) > maxim:
- maxim = float(value)
- except ValueError: # nv, default
- pass
- count += 1
-
- if self.mapType == 'vector':
- # raster min, max is known from r.info
- self.properties['min'], self.properties['max'] = minim, maxim
- self.SetRangeLabel()
-
- self.OnPreview(tmp = True)
-
- def OnLoadDefaultTable(self, event):
- """Load internal color table"""
- self.LoadTable()
-
- def LoadTable(self, mapType = 'raster'):
- """Load current color table (using `r(v).colors.out`)
-
- :param mapType: map type (raster or vector)"""
- self.rulesPanel.Clear()
-
- if mapType == 'raster':
- cmd = ['r.colors.out',
- 'read=True',
- 'map=%s' % self.inmap,
- 'rules=-']
- else:
- cmd = ['v.colors.out',
- 'read=True',
- 'map=%s' % self.inmap,
- 'rules=-']
-
- if self.properties['sourceColumn'] and self.properties['sourceColumn'] != 'cat':
- cmd.append('column=%s' % self.properties['sourceColumn'])
-
- cmd = cmdlist_to_tuple(cmd)
-
- if self.inmap:
- ctable = RunCommand(cmd[0], **cmd[1])
- else:
- self.OnPreview()
- return
-
- self.ReadColorTable(ctable = ctable)
-
- def CreateColorTable(self, tmp = False):
- """Creates color table
- :return: True on success
- :return: False on failure
- """
- rulestxt = ''
-
- for rule in self.rulesPanel.ruleslines.itervalues():
- if 'value' not in rule: # skip empty rules
- continue
-
- if rule['value'] not in ('nv', 'default') and \
- rule['value'][-1] != '%' and \
- not self._IsNumber(rule['value']):
- GError(_("Invalid rule value '%s'. Unable to apply color table.") % rule['value'],
- parent = self)
- return False
-
- rulestxt += rule['value'] + ' ' + rule['color'] + '\n'
-
- if not rulestxt:
- return False
-
- gtemp = utils.GetTempfile()
- output = open(gtemp, "w")
- try:
- output.write(rulestxt)
- finally:
- output.close()
-
- cmd = ['%s.colors' % self.mapType[0], #r.colors/v.colors
- 'map=%s' % self.inmap,
- 'rules=%s' % gtemp]
- if self.mapType == 'vector' and self.properties['sourceColumn'] \
- and self.properties['sourceColumn'] != 'cat':
- cmd.append('column=%s' % self.properties['sourceColumn'])
- cmd = cmdlist_to_tuple(cmd)
- ret = RunCommand(cmd[0], **cmd[1])
- if ret != 0:
- return False
-
- return True
-
- def DoPreview(self, ltype, cmdlist):
- """Update preview (based on computational region)"""
-
- if not self.layer:
- self.layer = self.Map.AddLayer(ltype = ltype, name = 'preview', command = cmdlist,
- active = True, hidden = False, opacity = 1.0,
- render = False)
- else:
- self.layer.SetCmd(cmdlist)
-
- # apply new color table and display preview
- self.CreateColorTable(tmp = True)
- self.preview.UpdatePreview()
-
- def RunHelp(self, cmd):
- """Show GRASS manual page"""
- RunCommand('g.manual',
- quiet = True,
- parent = self,
- entry = cmd)
- def SetMap(self, name):
- """Set map name and update dialog"""
- self.selectionInput.SetValue(name)
-
- def _IsNumber(self, s):
- """Check if 's' is a number"""
- try:
- float(s)
- return True
- except ValueError:
- return False
-
- class RasterColorTable(ColorTable):
- def __init__(self, parent, **kwargs):
- """Dialog for interactively entering color rules for raster maps"""
- self.mapType = 'raster'
- self.attributeType = 'color'
- self.colorTable = True
- # raster properties
- self.properties = {
- # min cat in raster map
- 'min' : None,
- # max cat in raster map
- 'max' : None,
- }
-
- ColorTable.__init__(self, parent,
- title = _('Create new color table for raster map'), **kwargs)
-
- self._initLayer()
-
- # self.SetMinSize(self.GetSize())
- self.SetMinSize((650, 700))
-
- def _doLayout(self):
- """Do main layout"""
- sizer = wx.BoxSizer(wx.VERTICAL)
- #
- # map selection
- #
- mapSelection = self._createMapSelection(parent = self.panel)
- sizer.Add(item = mapSelection, proportion = 0,
- flag = wx.ALL | wx.EXPAND, border = 5)
- #
- # manage extern tables
- #
- fileSelection = self._createFileSelection(parent = self.panel)
- sizer.Add(item = fileSelection, proportion = 0,
- flag = wx.LEFT | wx.RIGHT | wx.EXPAND, border = 5)
- #
- # body & preview
- #
- bodySizer = self._createBody(parent = self.panel)
- sizer.Add(item = bodySizer, proportion = 1,
- flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 5)
- #
- # buttons
- #
- btnSizer = self._createButtons(parent = self.panel)
- sizer.Add(item = wx.StaticLine(parent = self.panel, id = wx.ID_ANY,
- style = wx.LI_HORIZONTAL), proportion = 0,
- flag = wx.EXPAND | wx.ALL, border = 5)
-
- sizer.Add(item = btnSizer, proportion = 0,
- flag = wx.ALL | wx.ALIGN_RIGHT, border = 5)
-
- self.panel.SetSizer(sizer)
- sizer.Layout()
- sizer.Fit(self.panel)
- self.Layout()
-
- def OnSelectionInput(self, event):
- """Raster map selected"""
- if event:
- self.inmap = event.GetString()
-
- self.loadRules.SetValue('')
- self.saveRules.SetValue('')
-
- if self.inmap:
- if not grass.find_file(name = self.inmap, element = 'cell')['file']:
- self.inmap = None
-
- if not self.inmap:
- for btn in (self.btnPreview, self.btnOK,
- self.btnApply, self.btnDefault, self.btnSet):
- btn.Enable(False)
- self.LoadTable()
- return
-
- info = grass.raster_info(map = self.inmap)
-
- if info:
- self.properties['min'] = info['min']
- self.properties['max'] = info['max']
- self.LoadTable()
- else:
- self.inmap = ''
- self.properties['min'] = self.properties['max'] = None
- for btn in (self.btnPreview, self.btnOK,
- self.btnApply, self.btnDefault, self.btnSet):
- btn.Enable(False)
- self.preview.EraseMap()
- self.cr_label.SetLabel(_('Enter raster category values or percents'))
- return
-
- if info['datatype'] == 'CELL':
- mapRange = _('range')
- else:
- mapRange = _('fp range')
- self.cr_label.SetLabel(_('Enter raster category values or percents (%(range)s = %(min)d-%(max)d)') %
- { 'range' : mapRange,
- 'min' : self.properties['min'],
- 'max' : self.properties['max'] })
-
- for btn in (self.btnPreview, self.btnOK,
- self.btnApply, self.btnDefault, self.btnSet):
- btn.Enable()
-
- def OnPreview(self, tmp = True):
- """Update preview (based on computational region)"""
- if not self.inmap:
- self.preview.EraseMap()
- return
-
- cmdlist = ['d.rast',
- 'map=%s' % self.inmap]
- ltype = 'raster'
-
- # find existing color table and copy to temp file
- try:
- name, mapset = self.inmap.split('@')
- except ValueError:
- name = self.inmap
- mapset = grass.find_file(self.inmap, element = 'cell')['mapset']
- if not mapset:
- return
- old_colrtable = None
- if mapset == grass.gisenv()['MAPSET']:
- old_colrtable = grass.find_file(name = name, element = 'colr')['file']
- else:
- old_colrtable = grass.find_file(name = name, element = 'colr2/' + mapset)['file']
-
- if old_colrtable:
- colrtemp = utils.GetTempfile()
- shutil.copyfile(old_colrtable, colrtemp)
-
- ColorTable.DoPreview(self, ltype, cmdlist)
-
- # restore previous color table
- if tmp:
- if old_colrtable:
- shutil.copyfile(colrtemp, old_colrtable)
- os.remove(colrtemp)
- else:
- RunCommand('r.colors',
- parent = self,
- flags = 'r',
- map = self.inmap)
-
- def OnHelp(self, event):
- """Show GRASS manual page"""
- cmd = 'r.colors'
- ColorTable.RunHelp(self, cmd = cmd)
-
- class VectorColorTable(ColorTable):
- def __init__(self, parent, attributeType, **kwargs):
- """Dialog for interactively entering color rules for vector maps"""
- # dialog attributes
- self.mapType = 'vector'
- self.attributeType = attributeType # color, size, width
- # in version 7 v.colors used, otherwise color column only
- self.version7 = int(grass.version()['version'].split('.')[0]) >= 7
- self.colorTable = False
- self.updateColumn = True
- # vector properties
- self.properties = {
- # vector layer for attribute table to use for setting color
- 'layer' : 1,
- # vector attribute table used for setting color
- 'table' : '',
- # vector attribute column for assigning colors
- 'sourceColumn' : '',
- # vector attribute column to use for loading colors
- 'loadColumn' : '',
- # vector attribute column to use for storing colors
- 'storeColumn' : '',
- # vector attribute column for temporary storing colors
- 'tmpColumn' : 'tmp_0',
- # min value of attribute column/vector color table
- 'min': None,
- # max value of attribute column/vector color table
- 'max': None
- }
- self.columnsProp = {'color': {'name': 'GRASSRGB', 'type1': 'varchar(11)', 'type2': ['character']},
- 'size' : {'name': 'GRASSSIZE', 'type1': 'integer', 'type2': ['integer']},
- 'width': {'name': 'GRASSWIDTH', 'type1': 'integer', 'type2': ['integer']}}
- ColorTable.__init__(self, parent = parent,
- title = _('Create new color rules for vector map'), **kwargs)
-
- # additional bindings for vector color management
- self.Bind(wx.EVT_COMBOBOX, self.OnLayerSelection, self.layerSelect)
- self.sourceColumn.Bind(wx.EVT_TEXT, self.OnSourceColumnSelection)
- self.fromColumn.Bind(wx.EVT_TEXT, self.OnFromColSelection)
- self.toColumn.Bind(wx.EVT_TEXT, self.OnToColSelection)
- self.Bind(wx.EVT_BUTTON, self.OnAddColumn, self.addColumn)
-
- self._initLayer()
- if self.colorTable:
- self.cr_label.SetLabel(_("Enter vector attribute values or percents:"))
- else:
- self.cr_label.SetLabel(_("Enter vector attribute values:"))
-
- #self.SetMinSize(self.GetSize())
- self.SetMinSize((650, 700))
-
- self.CentreOnScreen()
- self.Show()
-
- def _createVectorAttrb(self, parent):
- """Create part of dialog with layer/column selection"""
- inputBox = wx.StaticBox(parent = parent, id = wx.ID_ANY,
- label = " %s " % _("Select vector columns"))
- cb_vl_label = wx.StaticText(parent, id = wx.ID_ANY,
- label = _('Layer:'))
- cb_vc_label = wx.StaticText(parent, id = wx.ID_ANY,
- label = _('Attribute column:'))
-
- if self.attributeType == 'color':
- labels = [_("Load color from column:"), _("Save color to column:")]
- elif self.attributeType == 'size':
- labels = [_("Load size from column:"), _("Save size to column:")]
- elif self.attributeType == 'width':
- labels = [_("Load width from column:"), _("Save width to column:")]
-
- if self.version7 and self.attributeType == 'color':
- self.useColumn = wx.CheckBox(parent, id = wx.ID_ANY,
- label = _("Use color column instead of color table:"))
- self.useColumn.Bind(wx.EVT_CHECKBOX, self.OnCheckColumn)
-
- fromColumnLabel = wx.StaticText(parent, id = wx.ID_ANY,
- label = labels[0])
- toColumnLabel = wx.StaticText(parent, id = wx.ID_ANY,
- label = labels[1])
-
- self.rgb_range_label = wx.StaticText(parent, id = wx.ID_ANY)
- self.layerSelect = LayerSelect(parent)
- self.sourceColumn = ColumnSelect(parent)
- self.fromColumn = ColumnSelect(parent)
- self.toColumn = ColumnSelect(parent)
- self.addColumn = wx.Button(parent, id = wx.ID_ANY,
- label = _('Add column'))
- self.addColumn.SetToolTipString(_("Add GRASSRGB column to current attribute table."))
-
- # layout
- inputSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL)
- vSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
- row = 0
- vSizer.Add(cb_vl_label, pos = (row, 0),
- flag = wx.ALIGN_CENTER_VERTICAL)
- vSizer.Add(self.layerSelect, pos = (row, 1),
- flag = wx.ALIGN_CENTER_VERTICAL)
- row += 1
- vSizer.Add(cb_vc_label, pos = (row, 0),
- flag = wx.ALIGN_CENTER_VERTICAL)
- vSizer.Add(self.sourceColumn, pos = (row, 1),
- flag = wx.ALIGN_CENTER_VERTICAL)
- vSizer.Add(self.rgb_range_label, pos = (row, 2),
- flag = wx.ALIGN_CENTER_VERTICAL)
- row += 1
- if self.version7 and self.attributeType == 'color':
- vSizer.Add(self.useColumn, pos = (row, 0), span = (1, 2),
- flag = wx.ALIGN_CENTER_VERTICAL)
- row += 1
-
- vSizer.Add(fromColumnLabel, pos = (row, 0),
- flag = wx.ALIGN_CENTER_VERTICAL)
- vSizer.Add(self.fromColumn, pos = (row, 1),
- flag = wx.ALIGN_CENTER_VERTICAL)
- row += 1
- vSizer.Add(toColumnLabel, pos = (row, 0),
- flag = wx.ALIGN_CENTER_VERTICAL)
- vSizer.Add(self.toColumn, pos = (row, 1),
- flag = wx.ALIGN_CENTER_VERTICAL)
- vSizer.Add(self.addColumn, pos = (row, 2),
- flag = wx.ALIGN_CENTER_VERTICAL)
- inputSizer.Add(item = vSizer,
- flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border = 5)
- self.colorColumnSizer = vSizer
- return inputSizer
-
- def _doLayout(self):
- """Do main layout"""
- scrollPanel = scrolled.ScrolledPanel(parent = self.panel, id = wx.ID_ANY,
- style = wx.TAB_TRAVERSAL)
- scrollPanel.SetupScrolling()
- sizer = wx.BoxSizer(wx.VERTICAL)
- #
- # map selection
- #
- mapSelection = self._createMapSelection(parent = scrollPanel)
- sizer.Add(item = mapSelection, proportion = 0,
- flag = wx.ALL | wx.EXPAND, border = 5)
- #
- # manage extern tables
- #
- if self.version7 and self.attributeType == 'color':
- self.cp = wx.CollapsiblePane(scrollPanel, label = _("Import or export color table"),
- winid = wx.ID_ANY,
- style = wx.CP_DEFAULT_STYLE|wx.CP_NO_TLW_RESIZE)
- self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnPaneChanged, self.cp)
-
- self._createFileSelection(parent = self.cp.GetPane())
- sizer.Add(item = self.cp, proportion = 0,
- flag = wx.ALL | wx.EXPAND, border = 5)
- #
- # set vector attributes
- #
- vectorAttrb = self._createVectorAttrb(parent = scrollPanel)
- sizer.Add(item = vectorAttrb, proportion = 0,
- flag = wx.ALL | wx.EXPAND, border = 5)
- #
- # body & preview
- #
- bodySizer = self._createBody(parent = scrollPanel)
- sizer.Add(item = bodySizer, proportion = 1,
- flag = wx.ALL | wx.EXPAND, border = 5)
-
- scrollPanel.SetSizer(sizer)
- scrollPanel.Fit()
-
- #
- # buttons
- #
- btnSizer = self._createButtons(self.panel)
-
- mainsizer = wx.BoxSizer(wx.VERTICAL)
- mainsizer.Add(scrollPanel, proportion = 1, flag = wx.EXPAND | wx.ALL, border = 5)
- mainsizer.Add(item = wx.StaticLine(parent = self.panel, id = wx.ID_ANY,
- style = wx.LI_HORIZONTAL), proportion = 0,
- flag = wx.EXPAND | wx.ALL, border = 5)
- mainsizer.Add(item = btnSizer, proportion = 0,
- flag = wx.ALL | wx.ALIGN_RIGHT | wx.EXPAND, border = 5)
-
- self.panel.SetSizer(mainsizer)
- mainsizer.Layout()
- mainsizer.Fit(self.panel)
- self.Layout()
-
- def OnPaneChanged(self, event = None):
- # redo the layout
- self.Layout()
- # and also change the labels
- if self.cp.IsExpanded():
- self.cp.SetLabel('')
- else:
- self.cp.SetLabel(_("Import or export color table"))
-
- def CheckMapset(self):
- """Check if current vector is in current mapset"""
- if grass.find_file(name = self.inmap,
- element = 'vector')['mapset'] == grass.gisenv()['MAPSET']:
- return True
- else:
- return False
-
- def NoConnection(self, vectorName):
- dlg = wx.MessageDialog(parent = self,
- message = _("Database connection for vector map <%s> "
- "is not defined in DB file. Do you want to create and "
- "connect new attribute table?") % vectorName,
- caption = _("No database connection defined"),
- style = wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION | wx.CENTRE)
- if dlg.ShowModal() == wx.ID_YES:
- dlg.Destroy()
- GUI(parent = self).ParseCommand(['v.db.addtable', 'map=' + self.inmap],
- completed = (self.CreateAttrTable, self.inmap, ''))
- else:
- dlg.Destroy()
-
- def OnCheckColumn(self, event):
- """Use color column instead of color table"""
- if self.useColumn.GetValue():
- self.properties['loadColumn'] = self.fromColumn.GetValue()
- self.properties['storeColumn'] = self.toColumn.GetValue()
- self.fromColumn.Enable(True)
- self.toColumn.Enable(True)
- self.colorTable = False
-
- if self.properties['loadColumn']:
- self.LoadTable()
- else:
- self.rulesPanel.Clear()
- else:
- self.properties['loadColumn'] = ''
- self.properties['storeColumn'] = ''
- self.fromColumn.Enable(False)
- self.toColumn.Enable(False)
- self.colorTable = True
- self.LoadTable()
-
- def EnableVectorAttributes(self, enable):
- """Enable/disable part of dialog connected with db"""
- for child in self.colorColumnSizer.GetChildren():
- child.GetWindow().Enable(enable)
-
- def DisableClearAll(self):
- """Enable, disable the whole dialog"""
- self.rulesPanel.Clear()
- self.EnableVectorAttributes(False)
- self.btnPreview.Enable(False)
- self.btnOK.Enable(False)
- self.btnApply.Enable(False)
- self.preview.EraseMap()
-
- def OnSelectionInput(self, event):
- """Vector map selected"""
- if event:
- if self.inmap:
- # switch to another map -> delete temporary column
- self.DeleteTemporaryColumn()
- self.inmap = event.GetString()
-
- if self.version7 and self.attributeType == 'color':
- self.loadRules.SetValue('')
- self.saveRules.SetValue('')
-
- if self.inmap:
- if not grass.find_file(name = self.inmap, element = 'vector')['file']:
- self.inmap = None
-
- self.UpdateDialog()
-
- def UpdateDialog(self):
- """Update dialog after map selection"""
-
- if not self.inmap:
- self.DisableClearAll()
- return
-
- if not self.CheckMapset():
- # v.colors doesn't need the map to be in current mapset
- if not (self.version7 and self.attributeType == 'color'):
- message = _("Selected map <%(map)s> is not in current mapset <%(mapset)s>. "
- "Attribute table cannot be edited.") % \
- { 'map' : self.inmap,
- 'mapset' : grass.gisenv()['MAPSET'] }
- wx.CallAfter(GMessage, parent = self, message = message)
- self.DisableClearAll()
- return
-
- # check for db connection
- self.dbInfo = VectorDBInfo(self.inmap)
- enable = True
- if not len(self.dbInfo.layers): # no connection
- if not (self.version7 and self.attributeType == 'color'): # otherwise it doesn't matter
- wx.CallAfter(self.NoConnection, self.inmap)
- enable = False
- for combo in (self.layerSelect, self.sourceColumn, self.fromColumn, self.toColumn):
- combo.SetValue("")
- combo.Clear()
- for prop in ('sourceColumn', 'loadColumn', 'storeColumn'):
- self.properties[prop] = ''
- self.EnableVectorAttributes(False)
- else: # db connection exist
- # initialize layer selection combobox
- self.EnableVectorAttributes(True)
- self.layerSelect.InsertLayers(self.inmap)
- # initialize attribute table for layer=1
- self.properties['layer'] = self.layerSelect.GetString(0)
- self.layerSelect.SetStringSelection(self.properties['layer'])
- layer = int(self.properties['layer'])
- self.properties['table'] = self.dbInfo.layers[layer]['table']
-
- if self.attributeType == 'color':
- self.AddTemporaryColumn(type = 'varchar(11)')
- else:
- self.AddTemporaryColumn(type = 'integer')
-
- # initialize column selection comboboxes
-
- self.OnLayerSelection(event = None)
- if self.version7 and self.attributeType == 'color':
- self.useColumn.SetValue(False)
- self.OnCheckColumn(event = None)
- self.useColumn.Enable(self.CheckMapset())
-
- self.LoadTable()
-
- self.btnPreview.Enable(enable)
- self.btnOK.Enable(enable)
- self.btnApply.Enable(enable)
-
- def AddTemporaryColumn(self, type):
- """Add temporary column to not overwrite the original values,
- need to be deleted when closing dialog and unloading map
-
- :param type: type of column (e.g. vachar(11))"""
- if not self.CheckMapset():
- return
- # because more than one dialog with the same map can be opened we must test column name and
- # create another one
- while self.properties['tmpColumn'] in self.dbInfo.GetTableDesc(self.properties['table']).keys():
- name, idx = self.properties['tmpColumn'].split('_')
- idx = int(idx)
- idx += 1
- self.properties['tmpColumn'] = name + '_' + str(idx)
-
- if self.version7:
- modul = 'v.db.addcolumn'
- else:
- modul = 'v.db.addcol'
- ret = RunCommand(modul,
- parent = self,
- map = self.inmap,
- layer = self.properties['layer'],
- column = '%s %s' % (self.properties['tmpColumn'], type))
-
- def DeleteTemporaryColumn(self):
- """Delete temporary column"""
- if not self.CheckMapset():
- return
-
- if self.inmap:
- if self.version7:
- modul = 'v.db.dropcolumn'
- else:
- modul = 'v.db.dropcol'
- ret = RunCommand(modul,
- map = self.inmap,
- layer = self.properties['layer'],
- column = self.properties['tmpColumn'])
-
- def OnLayerSelection(self, event):
- # reset choices in column selection comboboxes if layer changes
- vlayer = int(self.layerSelect.GetStringSelection())
- self.sourceColumn.InsertColumns(vector = self.inmap, layer = vlayer,
- type = ['integer', 'double precision'], dbInfo = self.dbInfo,
- excludeCols = ['tmpColumn'])
- self.sourceColumn.SetValue('cat')
- self.properties['sourceColumn'] = self.sourceColumn.GetValue()
-
- if self.attributeType == 'color':
- type = ['character']
- else:
- type = ['integer']
- self.fromColumn.InsertColumns(vector = self.inmap, layer = vlayer, type = type,
- dbInfo = self.dbInfo, excludeCols = ['tmpColumn'])
- self.toColumn.InsertColumns(vector = self.inmap, layer = vlayer, type = type,
- dbInfo = self.dbInfo, excludeCols = ['tmpColumn'])
-
- v = self.columnsProp[self.attributeType]['name']
- found = False
- if v in self.fromColumn.GetColumns():
- found = True
- if found != wx.NOT_FOUND:
- self.fromColumn.SetValue(v)
- self.toColumn.SetValue(v)
- self.properties['loadColumn'] = v
- self.properties['storeColumn'] = v
- else:
- self.properties['loadColumn'] = ''
- self.properties['storeColumn'] = ''
-
- if event:
- self.LoadTable()
- self.Update()
-
- def OnSourceColumnSelection(self, event):
- self.properties['sourceColumn'] = event.GetString()
-
- self.LoadTable()
-
- def OnAddColumn(self, event):
- """Add GRASS(RGB,SIZE,WIDTH) column if it doesn't exist"""
- if self.columnsProp[self.attributeType]['name'] not in self.fromColumn.GetColumns():
- if self.version7:
- modul = 'v.db.addcolumn'
- else:
- modul = 'v.db.addcol'
- ret = RunCommand(modul,
- map = self.inmap,
- layer = self.properties['layer'],
- columns = '%s %s' % (self.columnsProp[self.attributeType]['name'],
- self.columnsProp[self.attributeType]['type1']))
- self.toColumn.InsertColumns(self.inmap, self.properties['layer'],
- type = self.columnsProp[self.attributeType]['type2'])
- self.toColumn.SetValue(self.columnsProp[self.attributeType]['name'])
- self.properties['storeColumn'] = self.toColumn.GetValue()
-
- self.LoadTable()
- else:
- GMessage(parent = self,
- message = _("%s column already exists.") % \
- self.columnsProp[self.attributeType]['name'])
-
- def CreateAttrTable(self, dcmd, layer, params, propwin):
- """Create attribute table"""
- if dcmd:
- cmd = cmdlist_to_tuple(dcmd)
- ret = RunCommand(cmd[0], **cmd[1])
- if ret == 0:
- self.OnSelectionInput(None)
- return True
-
- for combo in (self.layerSelect, self.sourceColumn, self.fromColumn, self.toColumn):
- combo.SetValue("")
- combo.Disable()
- return False
-
- def LoadTable(self):
- """Load table"""
- if self.colorTable:
- ColorTable.LoadTable(self, mapType = 'vector')
- else:
- self.LoadRulesFromColumn()
-
- def LoadRulesFromColumn(self):
- """Load current column (GRASSRGB, size column)"""
-
- self.rulesPanel.Clear()
- if not self.properties['sourceColumn']:
- self.preview.EraseMap()
- return
-
- busy = wx.BusyInfo(message = _("Please wait, loading data from attribute table..."),
- parent = self)
- wx.Yield()
-
- columns = self.properties['sourceColumn']
- if self.properties['loadColumn']:
- columns += ',' + self.properties['loadColumn']
-
- sep = ';'
- if self.inmap:
- outFile = tempfile.NamedTemporaryFile(mode = 'w+b')
- ret = RunCommand('v.db.select',
- quiet = True,
- flags = 'c',
- map = self.inmap,
- layer = self.properties['layer'],
- columns = columns,
- sep = sep,
- stdout = outFile)
- else:
- self.preview.EraseMap()
- busy.Destroy()
- return
-
- outFile.seek(0)
- i = 0
- minim = maxim = 0.0
- limit = 1000
-
- colvallist = []
- readvals = False
-
- while True:
- # os.linesep doesn't work here (MSYS)
- record = outFile.readline().replace('\n', '')
- if not record:
- break
- self.rulesPanel.ruleslines[i] = {}
-
- if not self.properties['loadColumn']:
- col1 = record
- col2 = None
- else:
- col1, col2 = record.split(sep)
-
- if float(col1) < minim:
- minim = float(col1)
- if float(col1) > maxim:
- maxim = float(col1)
-
- # color rules list should only have unique values of col1, not all records
- if col1 not in colvallist:
- self.rulesPanel.ruleslines[i]['value'] = col1
- self.rulesPanel.ruleslines[i][self.attributeType] = col2
- colvallist.append(col1)
- i += 1
-
- if i > limit and readvals == False:
- dlg = wx.MessageDialog (parent = self, message = _(
- "Number of loaded records reached %d, "
- "displaying all the records will be time-consuming "
- "and may lead to computer freezing, "
- "do you still want to continue?") % i,
- caption = _("Too many records"),
- style = wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
- if dlg.ShowModal() == wx.ID_YES:
- readvals = True
- dlg.Destroy()
- else:
- busy.Destroy()
- dlg.Destroy()
- self.updateColumn = False
- return
-
- self.rulesPanel.AddRules(i, start = True)
- ret = self.rulesPanel.LoadRules()
-
- self.properties['min'], self.properties['max'] = minim, maxim
- self.SetRangeLabel()
-
- if ret:
- self.OnPreview()
- else:
- self.rulesPanel.Clear()
-
- busy.Destroy()
-
- def SetRangeLabel(self):
- """Set labels with info about attribute column range"""
-
- if self.properties['sourceColumn']:
- ctype = self.dbInfo.GetTableDesc(self.properties['table'])[self.properties['sourceColumn']]['ctype']
- else:
- ctype = int
-
- range = ''
- if self.properties['min'] or self.properties['max']:
- if ctype == float:
- range = "%s: %.1f - %.1f)" % (_("range"),
- self.properties['min'], self.properties['max'])
- elif ctype == int:
- range = "%s: %d - %d)" % (_("range"),
- self.properties['min'], self.properties['max'])
- if range:
- if self.colorTable:
- self.cr_label.SetLabel(_("Enter vector attribute values or percents %s:") % range)
- else:
- self.cr_label.SetLabel(_("Enter vector attribute values %s:") % range)
- else:
- if self.colorTable:
- self.cr_label.SetLabel(_("Enter vector attribute values or percents:"))
- else:
- self.cr_label.SetLabel(_("Enter vector attribute values:"))
-
- def OnFromColSelection(self, event):
- """Selection in combobox (for loading values) changed"""
- self.properties['loadColumn'] = event.GetString()
-
- self.LoadTable()
-
- def OnToColSelection(self, event):
- """Selection in combobox (for storing values) changed"""
- self.properties['storeColumn'] = event.GetString()
-
- def OnPreview(self, event = None, tmp = True):
- """Update preview (based on computational region)"""
- if self.colorTable:
- self.OnTablePreview(tmp)
- else:
- self.OnColumnPreview()
-
- def OnTablePreview(self, tmp):
- """Update preview (based on computational region)"""
- if not self.inmap:
- self.preview.EraseMap()
- return
-
- ltype = 'vector'
- cmdlist = ['d.vect',
- 'map=%s' % self.inmap]
-
- # find existing color table and copy to temp file
- try:
- name, mapset = self.inmap.split('@')
- except ValueError:
- name = self.inmap
- mapset = grass.find_file(self.inmap, element = 'cell')['mapset']
- if not mapset:
- return
-
- old_colrtable = None
- if mapset == grass.gisenv()['MAPSET']:
- old_colrtable = grass.find_file(name = 'colr', element = os.path.join('vector', name))['file']
- else:
- old_colrtable = grass.find_file(name = name, element = os.path.join('vcolr2', mapset))['file']
-
- if old_colrtable:
- colrtemp = utils.GetTempfile()
- shutil.copyfile(old_colrtable, colrtemp)
-
- ColorTable.DoPreview(self, ltype, cmdlist)
-
- # restore previous color table
- if tmp:
- if old_colrtable:
- shutil.copyfile(colrtemp, old_colrtable)
- os.remove(colrtemp)
- else:
- RunCommand('v.colors',
- parent = self,
- flags = 'r',
- map = self.inmap)
- def OnColumnPreview(self):
- """Update preview (based on computational region)"""
- if not self.inmap or not self.properties['tmpColumn']:
- self.preview.EraseMap()
- return
-
- cmdlist = ['d.vect',
- 'map=%s' % self.inmap,
- 'type=point,line,boundary,area']
-
- if self.attributeType == 'color':
- cmdlist.append('rgb_column=%s' % self.properties['tmpColumn'])
- elif self.attributeType == 'size':
- cmdlist.append('size_column=%s' % self.properties['tmpColumn'])
- elif self.attributeType == 'width':
- cmdlist.append('width_column=%s' % self.properties['tmpColumn'])
-
- ltype = 'vector'
-
- ColorTable.DoPreview(self, ltype, cmdlist)
-
- def OnHelp(self, event):
- """Show GRASS manual page"""
- cmd = 'v.colors'
- ColorTable.RunHelp(self, cmd = cmd)
-
- def UseAttrColumn(self, useAttrColumn):
- """Find layers and apply the changes in d.vect command"""
- layers = self.layerTree.FindItemByData(key = 'name', value = self.inmap)
- if not layers:
- return
- for layer in layers:
- if self.layerTree.GetLayerInfo(layer, key = 'type') != 'vector':
- continue
- cmdlist = self.layerTree.GetLayerInfo(layer, key = 'maplayer').GetCmd()
-
- if self.attributeType == 'color':
- if useAttrColumn:
- cmdlist[1].update({'rgb_column': self.properties['storeColumn']})
- else:
- cmdlist[1].pop('rgb_column', None)
- elif self.attributeType == 'size':
- cmdlist[1].update({'size_column': self.properties['storeColumn']})
- elif self.attributeType == 'width':
- cmdlist[1].update({'width_column' :self.properties['storeColumn']})
- self.layerTree.SetLayerInfo(layer, key = 'cmd', value = cmdlist)
-
- def CreateColorTable(self, tmp = False):
- """Create color rules (color table or color column)"""
- if self.colorTable:
- ret = ColorTable.CreateColorTable(self)
- else:
- if self.updateColumn:
- ret = self.UpdateColorColumn(tmp)
- else:
- ret = True
-
- return ret
-
- def UpdateColorColumn(self, tmp):
- """Creates color table
- :return: True on success
- :return: False on failure
- """
- rulestxt = ''
-
- for rule in self.rulesPanel.ruleslines.itervalues():
- if 'value' not in rule: # skip empty rules
- break
-
- if tmp:
- rgb_col = self.properties['tmpColumn']
- else:
- rgb_col = self.properties['storeColumn']
- if not self.properties['storeColumn']:
- GMessage(parent = self.parent,
- message = _("Please select column to save values to."))
-
- rulestxt += "UPDATE %s SET %s='%s' WHERE %s ;\n" % (self.properties['table'],
- rgb_col,
- rule[self.attributeType],
- rule['value'])
- if not rulestxt:
- return False
-
- gtemp = utils.GetTempfile()
- output = open(gtemp, "w")
- try:
- output.write(rulestxt)
- finally:
- output.close()
-
- RunCommand('db.execute',
- parent = self,
- input = gtemp)
- return True
-
- def OnCancel(self, event):
- """Do not apply any changes and close the dialog"""
- self.DeleteTemporaryColumn()
- self.Map.Clean()
- self.Destroy()
- def OnApply(self, event):
- """Apply selected color table
-
- :return: True on success otherwise False
- """
- if self.colorTable:
- self.UseAttrColumn(False)
- else:
- if not self.properties['storeColumn']:
- GError(_("No color column defined. Operation canceled."),
- parent = self)
- return
-
- self.UseAttrColumn(True)
-
- return ColorTable.OnApply(self, event)
-
- class ThematicVectorTable(VectorColorTable):
- def __init__(self, parent, vectorType, **kwargs):
- """Dialog for interactively entering color/size rules
- for vector maps for thematic mapping in nviz"""
- self.vectorType = vectorType
- VectorColorTable.__init__(self, parent = parent, **kwargs)
-
- self.SetTitle(_("Thematic mapping for vector map in 3D view"))
-
- def _initLayer(self):
- """Set initial layer when opening dialog"""
- self.inmap = self.parent.GetLayerData(nvizType = 'vector', nameOnly = True)
- self.selectionInput.SetValue(self.inmap)
- self.selectionInput.Disable()
-
- def OnApply(self, event):
- """Apply selected color table
- :return: True on success otherwise False
- """
- ret = self.CreateColorTable()
- if not ret:
- GMessage(parent = self, message = _("No valid color rules given."))
-
- data = self.parent.GetLayerData(nvizType = 'vector')
- data['vector']['points']['thematic']['layer'] = int(self.properties['layer'])
-
- value = None
- if self.properties['storeColumn']:
- value = self.properties['storeColumn']
-
- if not self.colorTable:
- if self.attributeType == 'color':
- data['vector'][self.vectorType]['thematic']['rgbcolumn'] = value
- else:
- data['vector'][self.vectorType]['thematic']['sizecolumn'] = value
- else:
- if self.attributeType == 'color':
- data['vector'][self.vectorType]['thematic']['rgbcolumn'] = None
- else:
- data['vector'][self.vectorType]['thematic']['sizecolumn'] = None
-
- data['vector'][self.vectorType]['thematic']['update'] = None
-
- from nviz.main import haveNviz
- if haveNviz:
- from nviz.mapwindow import wxUpdateProperties
-
- event = wxUpdateProperties(data = data)
- wx.PostEvent(self.parent.mapWindow, event)
-
- self.parent.mapWindow.Refresh(False)
-
- return ret
-
- class BufferedWindow(wx.Window):
- """A Buffered window class"""
- def __init__(self, parent, id,
- style = wx.NO_FULL_REPAINT_ON_RESIZE,
- Map = None, **kwargs):
-
- wx.Window.__init__(self, parent, id, style = style, **kwargs)
- self.parent = parent
- self.Map = Map
-
- # re-render the map from GRASS or just redraw image
- self.render = True
- # indicates whether or not a resize event has taken place
- self.resize = False
- #
- # event bindings
- #
- self.Bind(wx.EVT_PAINT, self.OnPaint)
- self.Bind(wx.EVT_IDLE, self.OnIdle)
- self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None)
- #
- # render output objects
- #
- # image file to be rendered
- self.mapfile = None
- # wx.Image object (self.mapfile)
- self.img = None
- self.pdc = wx.PseudoDC()
- # will store an off screen empty bitmap for saving to file
- self._Buffer = None
- # make sure that extents are updated at init
- self.Map.region = self.Map.GetRegion()
- self.Map.SetRegion()
- def Draw(self, pdc, img = None, pdctype = 'image'):
- """Draws preview or clears window"""
- pdc.BeginDrawing()
- Debug.msg (3, "BufferedWindow.Draw(): pdctype=%s" % (pdctype))
- if pdctype == 'clear': # erase the display
- bg = wx.WHITE_BRUSH
- pdc.SetBackground(bg)
- pdc.Clear()
- self.Refresh()
- pdc.EndDrawing()
- return
- if pdctype == 'image' and img:
- bg = wx.TRANSPARENT_BRUSH
- pdc.SetBackground(bg)
- bitmap = wx.BitmapFromImage(img)
- w, h = bitmap.GetSize()
- pdc.DrawBitmap(bitmap, 0, 0, True) # draw the composite map
-
- pdc.EndDrawing()
- self.Refresh()
- def OnPaint(self, event):
- """Draw pseudo DC to buffer"""
- self._Buffer = wx.EmptyBitmap(self.Map.width, self.Map.height)
- dc = wx.BufferedPaintDC(self, self._Buffer)
-
- # use PrepareDC to set position correctly
- # probably does nothing, removed from wxPython 2.9
- # self.PrepareDC(dc)
-
- # we need to clear the dc BEFORE calling PrepareDC
- bg = wx.Brush(self.GetBackgroundColour())
- dc.SetBackground(bg)
- dc.Clear()
-
- # create a clipping rect from our position and size
- # and the Update Region
- rgn = self.GetUpdateRegion()
- r = rgn.GetBox()
-
- # draw to the dc using the calculated clipping rect
- self.pdc.DrawToDCClipped(dc, r)
-
- def OnSize(self, event):
- """Init image size to match window size"""
- # set size of the input image
- self.Map.width, self.Map.height = self.GetClientSize()
- # Make new off screen bitmap: this bitmap will always have the
- # current drawing in it, so it can be used to save the image to
- # a file, or whatever.
- self._Buffer = wx.EmptyBitmap(self.Map.width, self.Map.height)
- # get the image to be rendered
- self.img = self.GetImage()
- # update map display
- if self.img and self.Map.width + self.Map.height > 0: # scale image during resize
- self.img = self.img.Scale(self.Map.width, self.Map.height)
- self.render = False
- self.UpdatePreview()
- # re-render image on idle
- self.resize = True
- def OnIdle(self, event):
- """Only re-render a preview image from GRASS during
- idle time instead of multiple times during resizing.
- """
- if self.resize:
- self.render = True
- self.UpdatePreview()
- event.Skip()
- def GetImage(self):
- """Converts files to wx.Image"""
- if self.Map.mapfile and os.path.isfile(self.Map.mapfile) and \
- os.path.getsize(self.Map.mapfile):
- img = wx.Image(self.Map.mapfile, wx.BITMAP_TYPE_ANY)
- else:
- img = None
-
- return img
-
- def UpdatePreview(self, img = None):
- """Update canvas if window changes geometry"""
- Debug.msg (2, "BufferedWindow.UpdatePreview(%s): render=%s" % (img, self.render))
- oldfont = ""
- oldencoding = ""
-
- if self.render:
- # extent is taken from current map display
- try:
- self.Map.region = copy.deepcopy(self.parent.parent.GetLayerTree().GetMap().GetCurrentRegion())
- except AttributeError:
- self.Map.region = self.Map.GetRegion()
- # render new map images
- self.mapfile = self.Map.Render(force = self.render)
- self.img = self.GetImage()
- self.resize = False
-
- if not self.img:
- return
-
- # paint images to PseudoDC
- self.pdc.Clear()
- self.pdc.RemoveAll()
- # draw map image background
- self.Draw(self.pdc, self.img, pdctype = 'image')
-
- self.resize = False
-
- def EraseMap(self):
- """Erase preview"""
- self.Draw(self.pdc, pdctype = 'clear')
-
|