colorrules.py 71 KB

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