colorrules.py 71 KB

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