colorrules.py 72 KB

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