colorrules.py 71 KB

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