123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094 |
- """
- @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-2015 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
- from gui_core.wrap import SpinCtrl, PseudoDC
- 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(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 = 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(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 = 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(enable, proportion=0,
- flag=wx.ALIGN_CENTER_VERTICAL)
- self.mainSizer.Add(txt_ctrl, proportion=0,
- flag=wx.ALIGN_CENTER | wx.RIGHT, border=5)
- self.mainSizer.Add(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(
- 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(wx.StaticText(parent, label=_("Load color table:")),
- pos=(0, 0), flag=wx.ALIGN_CENTER_VERTICAL)
- gridSizer.Add(colorTable, pos=(0, 1))
- gridSizer.Add(self.btnSet, pos=(0, 2), flag=wx.ALIGN_RIGHT)
- gridSizer.Add(
- wx.StaticText(
- parent, label=_('Load color table from file:')), pos=(
- 1, 0), flag=wx.ALIGN_CENTER_VERTICAL)
- gridSizer.Add(
- self.loadRules, pos=(
- 1, 1), span=(
- 1, 2), flag=wx.EXPAND)
- gridSizer.Add(
- wx.StaticText(
- parent, label=_('Save color table to file:')), pos=(
- 2, 0), flag=wx.ALIGN_CENTER_VERTICAL)
- gridSizer.Add(
- 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(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(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(self.preview, pos=(row, 2),
- flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER)
- row += 1
- # add ckeck all and clear all
- bodySizer.Add(
- self.rulesPanel.checkAll,
- flag=wx.ALIGN_CENTER_VERTICAL,
- pos=(
- row,
- 0))
- bodySizer.Add(self.rulesPanel.clearAll, pos=(row, 1))
- # preview button
- self.btnPreview = wx.Button(parent, id=wx.ID_ANY,
- label=_("Preview"))
- bodySizer.Add(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(self.rulesPanel.numRules, pos=(row, 0),
- flag=wx.ALIGN_CENTER_VERTICAL)
- bodySizer.Add(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 path:
- return
- if os.path.exists(path):
- dlgOw = wx.MessageDialog(
- parent,
- message=_(
- "File <%s> already already exists. "
- "Do you want to overwrite it?") %
- path,
- caption=_("Overwrite?"),
- style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
- if dlgOw.ShowModal() != wx.ID_YES:
- 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.Map.GetRenderMgr().renderDone.connect(self._restoreColorTable)
- # 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(mapSelection, proportion=0,
- flag=wx.ALL | wx.EXPAND, border=5)
- #
- # manage extern tables
- #
- fileSelection = self._createFileSelection(parent=self.panel)
- sizer.Add(fileSelection, proportion=0,
- flag=wx.LEFT | wx.RIGHT | wx.EXPAND, border=5)
- #
- # body & preview
- #
- bodySizer = self._createBody(parent=self.panel)
- sizer.Add(bodySizer, proportion=1,
- flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=5)
- #
- # buttons
- #
- btnSizer = self._createButtons(parent=self.panel)
- sizer.Add(wx.StaticLine(parent=self.panel, id=wx.ID_ANY,
- style=wx.LI_HORIZONTAL), proportion=0,
- flag=wx.EXPAND | wx.ALL, border=5)
- sizer.Add(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
- self._tmp = tmp
- self._old_colrtable = None
- if mapset == grass.gisenv()['MAPSET']:
- self._old_colrtable = grass.find_file(
- name=name, element='colr')['file']
- else:
- self._old_colrtable = grass.find_file(
- name=name, element='colr2/' + mapset)['file']
- if self._old_colrtable:
- self._colrtemp = utils.GetTempfile()
- shutil.copyfile(self._old_colrtable, self._colrtemp)
- ColorTable.DoPreview(self, ltype, cmdlist)
- def _restoreColorTable(self):
- # restore previous color table
- if self._tmp:
- if self._old_colrtable:
- shutil.copyfile(self._colrtemp, self._old_colrtable)
- os.remove(self._colrtemp)
- del self._colrtemp, self._old_colrtable
- else:
- RunCommand('r.colors',
- parent=self,
- flags='r',
- map=self.inmap)
- del self._tmp
- 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(
- 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(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(self.cp, proportion=0,
- flag=wx.ALL | wx.EXPAND, border=5)
- #
- # set vector attributes
- #
- vectorAttrb = self._createVectorAttrb(parent=scrollPanel)
- sizer.Add(vectorAttrb, proportion=0,
- flag=wx.ALL | wx.EXPAND, border=5)
- #
- # body & preview
- #
- bodySizer = self._createBody(parent=scrollPanel)
- sizer.Add(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(wx.StaticLine(parent=self.panel, id=wx.ID_ANY,
- style=wx.LI_HORIZONTAL), proportion=0,
- flag=wx.EXPAND | wx.ALL, border=5)
- mainsizer.Add(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 = 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()
- self.Map.GetRenderMgr().renderDone.connect(self._updatePreviewFinished)
- 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))
- if not self.render:
- return
- # 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)
- def _updatePreviewFinished(self):
- if not self.render:
- return
- 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')
|