dialogs.py 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117
  1. """
  2. @package web_services.dialogs
  3. @brief Dialogs for web services.
  4. List of classes:
  5. - dialogs::WSDialogBase
  6. - dialogs::AddWSDialog
  7. - dialogs::WSPropertiesDialog
  8. - dialogs::SaveWMSLayerDialog
  9. (C) 2009-2013 by the GRASS Development Team
  10. This program is free software under the GNU General Public License
  11. (>=v2). Read the file COPYING that comes with GRASS for details.
  12. @author Martin Landa <landa.martin gmail.com>
  13. @author Stepan Turek <stepan.turek seznam.cz>
  14. """
  15. import wx
  16. import os
  17. import sys
  18. import shutil
  19. from copy import deepcopy
  20. import grass.script as grass
  21. from grass.script.task import cmdlist_to_tuple, cmdtuple_to_list
  22. from core import globalvar
  23. from core.debug import Debug
  24. from core.gcmd import GMessage, GWarning, GError, RunCommand
  25. from core.utils import GetSettingsPath, _
  26. from core.gconsole import CmdThread, GStderr, EVT_CMD_DONE, EVT_CMD_OUTPUT
  27. from gui_core.gselect import Select
  28. from web_services.widgets import WSPanel, WSManageSettingsWidget
  29. class WSDialogBase(wx.Dialog):
  30. """Base class for web service dialogs.
  31. """
  32. def __init__(self, parent, id=wx.ID_ANY,
  33. style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
  34. wx.Dialog.__init__(self, parent, id, style=style, **kwargs)
  35. self.parent = parent
  36. # contains panel for every web service on server
  37. self.ws_panels = {'WMS_1.1.1': {'panel': None,
  38. 'label': 'WMS 1.1.1'},
  39. 'WMS_1.3.0': {'panel': None,
  40. 'label': 'WMS 1.3.0'},
  41. 'WMTS': {'panel': None,
  42. 'label': 'WMTS'},
  43. 'OnEarth': {'panel': None,
  44. 'label': 'OnEarth'},
  45. }
  46. # TODO: should be in file
  47. self.default_servers = {
  48. 'OSM-WMS-EUROPE':
  49. ['http://watzmann-geog.urz.uni-heidelberg.de/cached/osm', '', ''],
  50. 'irs.gis-lab.info (OSM)': ['http://irs.gis-lab.info', '', ''],
  51. 'NASA GIBS WMTS': ['http://gibs.earthdata.nasa.gov/wmts/epsg4326/best/wmts.cgi', '', '']}
  52. # holds reference to web service panel which is showed
  53. self.active_ws_panel = None
  54. # buttons which are disabled when the dialog is not connected
  55. self.run_btns = []
  56. # stores error messages for GError dialog showed when all web service
  57. # connections were unsuccessful
  58. self.error_msgs = ''
  59. self._createWidgets()
  60. self._doLayout()
  61. def _createWidgets(self):
  62. settingsFile = os.path.join(GetSettingsPath(), 'wxWS')
  63. self.settsManager = WSManageSettingsWidget(
  64. parent=self, settingsFile=settingsFile,
  65. default_servers=self.default_servers)
  66. self.settingsBox = wx.StaticBox(parent=self,
  67. id=wx.ID_ANY,
  68. label=_(" Server settings "))
  69. self.serverText = wx.StaticText(parent=self,
  70. id=wx.ID_ANY, label=_("Server:"))
  71. self.server = wx.TextCtrl(parent=self, id=wx.ID_ANY)
  72. self.btn_connect = wx.Button(parent=self,
  73. id=wx.ID_ANY, label=_("&Connect"))
  74. self.btn_connect.SetToolTipString(_("Connect to the server"))
  75. if not self.server.GetValue():
  76. self.btn_connect.Enable(False)
  77. self.infoCollapseLabelExp = _('Show advanced connection settings')
  78. self.infoCollapseLabelCol = _('Hide advanced connection settings')
  79. self.adv_conn = wx.CollapsiblePane(parent=self,
  80. label=self.infoCollapseLabelExp,
  81. style=wx.CP_DEFAULT_STYLE |
  82. wx.CP_NO_TLW_RESIZE | wx.EXPAND)
  83. self.MakeAdvConnPane(pane=self.adv_conn.GetPane())
  84. self.adv_conn.Collapse(True)
  85. self.Bind(
  86. wx.EVT_COLLAPSIBLEPANE_CHANGED,
  87. self.OnAdvConnPaneChanged,
  88. self.adv_conn)
  89. self.reqDataPanel = wx.Panel(parent=self, id=wx.ID_ANY)
  90. self.layerNameBox = wx.StaticBox(parent=self.reqDataPanel,
  91. id=wx.ID_ANY,
  92. label=_(" Layer Manager Settings "))
  93. self.layerNameText = wx.StaticText(
  94. parent=self.reqDataPanel, id=wx.ID_ANY,
  95. label=_("Output layer name:"))
  96. self.layerName = wx.TextCtrl(parent=self.reqDataPanel, id=wx.ID_ANY)
  97. for ws in self.ws_panels.iterkeys():
  98. # set class WSPanel argument layerNameTxtCtrl
  99. self.ws_panels[ws]['panel'] = WSPanel(parent=self.reqDataPanel,
  100. web_service=ws)
  101. self.ws_panels[ws]['panel'].capParsed.connect(
  102. self.OnPanelCapParsed)
  103. self.ws_panels[ws]['panel'].layerSelected.connect(
  104. self.OnLayerSelected)
  105. # buttons
  106. self.btn_close = wx.Button(parent=self, id=wx.ID_CLOSE)
  107. self.btn_close.SetToolTipString(_("Close dialog"))
  108. # statusbar
  109. self.statusbar = wx.StatusBar(parent=self, id=wx.ID_ANY)
  110. # bindings
  111. self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose)
  112. self.Bind(wx.EVT_CLOSE, self.OnClose)
  113. self.btn_connect.Bind(wx.EVT_BUTTON, self.OnConnect)
  114. self.server.Bind(wx.EVT_TEXT, self.OnServer)
  115. self.layerName.Bind(wx.EVT_TEXT, self.OnOutputLayerName)
  116. self.settsManager.settingsChanged.connect(self.OnSettingsChanged)
  117. self.settsManager.settingsSaving.connect(self.OnSettingsSaving)
  118. def OnLayerSelected(self, title):
  119. if not self.layerName.GetValue().strip():
  120. self.layerName.SetValue(title)
  121. def _doLayout(self):
  122. dialogSizer = wx.BoxSizer(wx.VERTICAL)
  123. dialogSizer.Add(item=self.settsManager, proportion=0,
  124. flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border=5)
  125. # connectin settings
  126. settingsSizer = wx.StaticBoxSizer(self.settingsBox, wx.VERTICAL)
  127. serverSizer = wx.FlexGridSizer(cols=3, vgap=5, hgap=5)
  128. serverSizer.Add(item=self.serverText,
  129. flag=wx.ALIGN_CENTER_VERTICAL)
  130. serverSizer.AddGrowableCol(1)
  131. serverSizer.Add(item=self.server,
  132. flag=wx.EXPAND | wx.ALL)
  133. serverSizer.Add(item=self.btn_connect)
  134. settingsSizer.Add(item=serverSizer, proportion=0,
  135. flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5)
  136. settingsSizer.Add(item=self.adv_conn,
  137. flag=wx.ALL | wx.EXPAND, border=5)
  138. dialogSizer.Add(item=settingsSizer, proportion=0,
  139. flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border=5)
  140. # layer name, parsed capabilites
  141. reqDataSizer = wx.BoxSizer(wx.VERTICAL)
  142. layerNameSizer = wx.StaticBoxSizer(self.layerNameBox, wx.HORIZONTAL)
  143. layerNameSizer.Add(item=self.layerNameText,
  144. flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
  145. layerNameSizer.Add(item=self.layerName,
  146. flag=wx.EXPAND, proportion=1)
  147. reqDataSizer.Add(item=layerNameSizer, flag=wx.TOP |
  148. wx.LEFT | wx.RIGHT | wx.EXPAND, border=5)
  149. self.ch_ws_sizer = wx.BoxSizer(wx.VERTICAL)
  150. reqDataSizer.Add(item=self.ch_ws_sizer, proportion=0,
  151. flag=wx.TOP | wx.EXPAND, border=5)
  152. for ws in self.ws_panels.iterkeys():
  153. reqDataSizer.Add(
  154. item=self.ws_panels[ws]['panel'],
  155. proportion=1,
  156. flag=wx.TOP | wx.LEFT | wx.RIGHT | wx.EXPAND,
  157. border=5)
  158. self.ws_panels[ws]['panel'].Hide()
  159. dialogSizer.Add(item=self.reqDataPanel, proportion=1,
  160. flag=wx.EXPAND)
  161. self.reqDataPanel.SetSizer(reqDataSizer)
  162. self.reqDataPanel.Hide()
  163. # buttons
  164. self.btnsizer = wx.BoxSizer(orient=wx.HORIZONTAL)
  165. self.btnsizer.Add(item=self.btn_close, proportion=0,
  166. flag=wx.ALL | wx.ALIGN_CENTER,
  167. border=10)
  168. dialogSizer.Add(item=self.btnsizer, proportion=0,
  169. flag=wx.ALIGN_CENTER)
  170. # expand wxWidget wx.StatusBar
  171. statusbarSizer = wx.BoxSizer(wx.HORIZONTAL)
  172. statusbarSizer.Add(item=self.statusbar, proportion=1, flag=wx.EXPAND)
  173. dialogSizer.Add(item=statusbarSizer,
  174. proportion=0,
  175. flag=wx.EXPAND)
  176. self.SetSizer(dialogSizer)
  177. self.Layout()
  178. self.SetMinSize((550, -1))
  179. self.SetMaxSize((-1, self.GetBestSize()[1]))
  180. self.Fit()
  181. def MakeAdvConnPane(self, pane):
  182. """Create advanced connection settings pane
  183. """
  184. self.usernameText = wx.StaticText(parent=pane,
  185. id=wx.ID_ANY, label=_("Username:"))
  186. self.username = wx.TextCtrl(parent=pane, id=wx.ID_ANY)
  187. self.passwText = wx.StaticText(parent=pane,
  188. id=wx.ID_ANY, label=_("Password:"))
  189. self.password = wx.TextCtrl(parent=pane, id=wx.ID_ANY,
  190. style=wx.TE_PASSWORD)
  191. # pane layout
  192. adv_conn_sizer = wx.BoxSizer(wx.VERTICAL)
  193. usernameSizer = wx.BoxSizer(wx.HORIZONTAL)
  194. usernameSizer.Add(item=self.usernameText,
  195. flag=wx.ALIGN_CENTER_VERTICAL, border=5)
  196. usernameSizer.Add(item=self.username, proportion=1,
  197. flag=wx.EXPAND, border=5)
  198. adv_conn_sizer.Add(item=usernameSizer,
  199. flag=wx.ALL | wx.EXPAND, border=5)
  200. passwSizer = wx.BoxSizer(wx.HORIZONTAL)
  201. passwSizer.Add(item=self.passwText,
  202. flag=wx.ALIGN_CENTER_VERTICAL, border=5)
  203. passwSizer.Add(item=self.password, proportion=1,
  204. flag=wx.EXPAND, border=5)
  205. adv_conn_sizer.Add(item=passwSizer,
  206. flag=wx.ALL | wx.EXPAND, border=5)
  207. pane.SetSizer(adv_conn_sizer)
  208. adv_conn_sizer.Fit(pane)
  209. pane.SetSizer(adv_conn_sizer)
  210. adv_conn_sizer.Fit(pane)
  211. def OnSettingsSaving(self, name):
  212. """Check if required data are filled before setting save is performed.
  213. """
  214. server = self.server.GetValue().strip()
  215. if not server:
  216. GMessage(parent=self, message=_(
  217. "No data source defined, settings are not saved."))
  218. return
  219. self.settsManager.SetDataToSave((server,
  220. self.username.GetValue(),
  221. self.password.GetValue()))
  222. self.settsManager.SaveSettings(name)
  223. def OnSettingsChanged(self, data):
  224. """Update widgets according to chosen settings"""
  225. # data list: [server, username, password]
  226. if len < 3:
  227. return
  228. self.server.SetValue(data[0])
  229. self.username.SetValue(data[1])
  230. self.password.SetValue(data[2])
  231. if data[1] or data[2]:
  232. self.adv_conn.Expand()
  233. else:
  234. self.adv_conn.Collapse(True)
  235. # clear content of the wxWidget wx.TextCtrl (Output layer
  236. # name:), based on changing default server selection in the
  237. # wxWidget wx.Choice
  238. if len(self.layerName.GetValue()) > 0:
  239. self.layerName.Clear()
  240. def OnClose(self, event):
  241. """Close the dialog
  242. """
  243. """Close dialog"""
  244. if not self.IsModal():
  245. self.Destroy()
  246. event.Skip()
  247. def _getCapFiles(self):
  248. ws_cap_files = {}
  249. for v in self.ws_panels.itervalues():
  250. ws_cap_files[v['panel'].GetWebService()] = v['panel'].GetCapFile()
  251. return ws_cap_files
  252. def OnServer(self, event):
  253. """Server settings edited
  254. """
  255. value = event.GetString()
  256. if value:
  257. self.btn_connect.Enable(True)
  258. else:
  259. self.btn_connect.Enable(False)
  260. # clear content of the wxWidget wx.TextCtrl (Output Layer
  261. # name:), based on changing content of the wxWidget
  262. # wx.TextCtrl (Server:)
  263. self.layerName.Clear()
  264. def OnOutputLayerName(self, event):
  265. """Update layer name to web service panel
  266. """
  267. lname = event.GetString()
  268. lname = lname.encode('ascii', 'replace')
  269. for v in self.ws_panels.itervalues():
  270. v['panel'].SetOutputLayerName(lname.strip())
  271. def OnConnect(self, event):
  272. """Connect to the server
  273. """
  274. server = self.server.GetValue().strip()
  275. self.ch_ws_sizer.Clear(deleteWindows=True)
  276. if self.active_ws_panel is not None:
  277. self.reqDataPanel.Hide()
  278. for btn in self.run_btns:
  279. btn.Enable(False)
  280. self.active_ws_panel = None
  281. self.Layout()
  282. self.Fit()
  283. self.statusbar.SetStatusText(
  284. _("Connecting to <%s>..." % self.server.GetValue().strip()))
  285. # number of panels already connected
  286. self.finished_panels_num = 0
  287. for ws in self.ws_panels.iterkeys():
  288. self.ws_panels[ws]['panel'].ConnectToServer(
  289. url=server, username=self.username.GetValue(),
  290. password=self.password.GetValue())
  291. self.ws_panels[ws]['panel'].Hide()
  292. def OnPanelCapParsed(self, error_msg):
  293. """Called when panel has downloaded and parsed capabilities file.
  294. """
  295. # how many web service panels are finished
  296. self.finished_panels_num += 1
  297. if error_msg:
  298. self.error_msgs += '\n' + error_msg
  299. # if all are finished, show panels, which succeeded in connection
  300. if self.finished_panels_num == len(self.ws_panels):
  301. self.UpdateDialogAfterConnection()
  302. # show error dialog only if connections to all web services were
  303. # unsuccessful
  304. if not self._getConnectedWS() and self.error_msgs:
  305. GError(self.error_msgs, parent=self)
  306. self.error_msgs = ''
  307. self.Layout()
  308. self.Fit()
  309. def _getConnectedWS(self):
  310. """
  311. :return: list of found web services on server (identified as keys in self.ws_panels)
  312. """
  313. conn_ws = []
  314. for ws, data in self.ws_panels.iteritems():
  315. if data['panel'].IsConnected():
  316. conn_ws.append(ws)
  317. return conn_ws
  318. def UpdateDialogAfterConnection(self):
  319. """Update dialog after all web service panels downloaded and parsed capabilities data.
  320. """
  321. avail_ws = {}
  322. conn_ws = self._getConnectedWS()
  323. for ws in conn_ws:
  324. avail_ws[ws] = self.ws_panels[ws]
  325. self.web_service_sel = []
  326. self.rb_choices = []
  327. # at least one web service found on server
  328. if len(avail_ws) > 0:
  329. self.reqDataPanel.Show()
  330. self.rb_order = ['WMS_1.1.1', 'WMS_1.3.0', 'WMTS', 'OnEarth']
  331. for ws in self.rb_order:
  332. if ws in avail_ws:
  333. self.web_service_sel.append(ws)
  334. self.rb_choices.append(avail_ws[ws]['label'])
  335. self.choose_ws_rb = wx.RadioBox(
  336. parent=self.reqDataPanel, id=wx.ID_ANY,
  337. label=_("Available web services"),
  338. pos=wx.DefaultPosition, choices=self.rb_choices,
  339. majorDimension=1, style=wx.RA_SPECIFY_ROWS)
  340. self.Bind(wx.EVT_RADIOBOX, self.OnChooseWs, self.choose_ws_rb)
  341. self.ch_ws_sizer.Add(
  342. item=self.choose_ws_rb,
  343. flag=wx.TOP | wx.LEFT | wx.RIGHT | wx.EXPAND,
  344. border=5)
  345. self._showWsPanel(
  346. self.web_service_sel[
  347. self.choose_ws_rb.GetSelection()])
  348. self.statusbar.SetStatusText(
  349. _("Connected to <%s>" % self.server.GetValue().strip()))
  350. for btn in self.run_btns:
  351. btn.Enable(True)
  352. # no web service found on server
  353. else:
  354. self.statusbar.SetStatusText(
  355. _("Unable to connect to <%s>" % self.server.GetValue().strip()))
  356. for btn in self.run_btns:
  357. btn.Enable(False)
  358. self.reqDataPanel.Hide()
  359. self.active_ws_panel = None
  360. def OnChooseWs(self, event):
  361. """Show panel corresponding to selected web service.
  362. """
  363. choosen_r = event.GetInt()
  364. self._showWsPanel(self.web_service_sel[choosen_r])
  365. def _showWsPanel(self, ws):
  366. """Helper function
  367. """
  368. if self.active_ws_panel is not None:
  369. self.active_ws_panel.Hide()
  370. self.active_ws_panel = self.ws_panels[ws]['panel']
  371. self.active_ws_panel.Show()
  372. self.SetMaxSize((-1, -1))
  373. self.active_ws_panel.GetContainingSizer().Layout()
  374. def OnAdvConnPaneChanged(self, event):
  375. """Collapse search module box
  376. """
  377. if self.adv_conn.IsExpanded():
  378. self.adv_conn.SetLabel(self.infoCollapseLabelCol)
  379. else:
  380. self.adv_conn.SetLabel(self.infoCollapseLabelExp)
  381. self.Layout()
  382. self.SetMaxSize((-1, self.GetBestSize()[1]))
  383. self.SendSizeEvent()
  384. self.Fit()
  385. class AddWSDialog(WSDialogBase):
  386. """Dialog for adding web service layer."""
  387. def __init__(self, parent, giface, id=wx.ID_ANY,
  388. style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
  389. WSDialogBase.__init__(
  390. self,
  391. parent,
  392. id=wx.ID_ANY,
  393. style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER,
  394. **kwargs)
  395. self.SetTitle(_("Add web service layer"))
  396. self.parent = parent
  397. self.giface = giface
  398. self.btn_connect.SetDefault()
  399. def _createWidgets(self):
  400. WSDialogBase._createWidgets(self)
  401. self.btn_add = wx.Button(
  402. parent=self,
  403. id=wx.ID_ANY,
  404. label=_("&Add layer"))
  405. self.btn_add.SetToolTipString(
  406. _("Add selected web service layers as map layer into layer tree"))
  407. self.btn_add.Enable(False)
  408. self.run_btns.append(self.btn_add)
  409. def _doLayout(self):
  410. WSDialogBase._doLayout(self)
  411. self.btnsizer.Add(item=self.btn_add, proportion=0,
  412. flag=wx.ALL | wx.ALIGN_CENTER,
  413. border=10)
  414. # bindings
  415. self.btn_add.Bind(wx.EVT_BUTTON, self.OnAddLayer)
  416. def UpdateDialogAfterConnection(self):
  417. """Connect to the server
  418. """
  419. WSDialogBase.UpdateDialogAfterConnection(self)
  420. if self._getConnectedWS():
  421. self.btn_add.SetDefault()
  422. else:
  423. self.btn_connect.SetDefault()
  424. def OnAddLayer(self, event):
  425. """Add web service layer.
  426. """
  427. # add layer
  428. if self.active_ws_panel is None:
  429. return
  430. lcmd = self.active_ws_panel.CreateCmd()
  431. if not lcmd:
  432. return None
  433. # TODO: It is not clear how to do GetOptData in giface
  434. # knowing what GetOptData is doing might help
  435. # (maybe Get... is not the right name)
  436. # please fix giface if you know
  437. # tree -> giface
  438. # GetLayerTree -> GetLayerList
  439. # AddLayer -> AddLayer (but tree ones returns some layer,
  440. # giface ones nothing)
  441. # GetLayerInfo -> Layer object can by used instead
  442. # GetOptData -> unknown
  443. ltree = self.giface.GetLayerTree()
  444. active_ws = self.active_ws_panel.GetWebService()
  445. if 'WMS' not in active_ws:
  446. cap_file = self.active_ws_panel.GetCapFile()
  447. cmd_cap_file = grass.tempfile()
  448. shutil.copyfile(cap_file, cmd_cap_file)
  449. lcmd.append('capfile=' + cmd_cap_file)
  450. layer = ltree.AddLayer(ltype='wms',
  451. lname=self.active_ws_panel.GetOutputLayerName(),
  452. lchecked=True, lcmd=lcmd)
  453. ws_cap_files = self._getCapFiles()
  454. # create properties dialog
  455. cmd_list = ltree.GetLayerInfo(layer, 'cmd')
  456. cmd = cmdlist_to_tuple(cmd_list)
  457. prop_win = WSPropertiesDialog(parent=self.parent,
  458. giface=self.giface,
  459. id=wx.ID_ANY,
  460. layer=layer,
  461. ws_cap_files=ws_cap_files,
  462. cmd=cmd)
  463. prop_win.Hide()
  464. ltree.GetOptData(dcmd=None, layer=layer,
  465. params=None, propwin=prop_win)
  466. class WSPropertiesDialog(WSDialogBase):
  467. """Dialog for editing web service properties."""
  468. def __init__(self, parent, giface, layer, ws_cap_files, cmd, id=wx.ID_ANY,
  469. style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
  470. """
  471. :param giface: grass interface
  472. :param layer: layer tree item
  473. :param ws_cap_files: dict web service('WMS_1.1.1', 'WMS_1.3.0',
  474. 'WMTS', 'OnEarth') : cap file path cap files, which will be parsed
  475. :param cmd: cmd to which dialog widgets will be initialized if
  476. it is possible (cmp parameters exists in parsed web service cap_file)
  477. """
  478. WSDialogBase.__init__(
  479. self,
  480. parent,
  481. id=wx.ID_ANY,
  482. style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER,
  483. **kwargs)
  484. self.SetTitle(_("Web service layer properties"))
  485. self.layer = layer
  486. self.giface = giface
  487. # after web service panels are connected, set dialog widgets
  488. # according to cmd in this variable (if it is not None)
  489. self.cmd_to_set = None
  490. # store data needed for reverting
  491. self.revert_ws_cap_files = {}
  492. self.revert_cmd = cmd
  493. ws_cap = self._getWSfromCmd(cmd)
  494. for ws in self.ws_panels.iterkeys():
  495. # cap file used in cmd will be deleted, thnaks to the dialogs
  496. # destructor
  497. if ws == ws_cap and 'capfile' in cmd[1]:
  498. self.revert_ws_cap_files[ws] = cmd[1]['capfile']
  499. del ws_cap_files[ws]
  500. else:
  501. self.revert_ws_cap_files[ws] = grass.tempfile()
  502. self._setRevertCapFiles(ws_cap_files)
  503. self.LoadCapFiles(ws_cap_files=self.revert_ws_cap_files, cmd=cmd)
  504. self.btn_ok.SetDefault()
  505. def __del__(self):
  506. for f in self.revert_ws_cap_files.itervalues():
  507. grass.try_remove(f)
  508. def _setRevertCapFiles(self, ws_cap_files):
  509. for ws, f in ws_cap_files.iteritems():
  510. if os.path.isfile(ws_cap_files[ws]):
  511. shutil.copyfile(f, self.revert_ws_cap_files[ws])
  512. else:
  513. # delete file content
  514. f_o = open(f, 'w')
  515. f_o.close()
  516. def _createWidgets(self):
  517. WSDialogBase._createWidgets(self)
  518. self.btn_apply = wx.Button(
  519. parent=self, id=wx.ID_ANY, label=_("&Apply"))
  520. self.btn_apply.SetToolTipString(_("Apply changes"))
  521. self.btn_apply.Enable(False)
  522. self.run_btns.append(self.btn_apply)
  523. self.btn_ok = wx.Button(parent=self, id=wx.ID_ANY, label=_("&OK"))
  524. self.btn_ok.SetToolTipString(_("Apply changes and close dialog"))
  525. self.btn_ok.Enable(False)
  526. self.run_btns.append(self.btn_ok)
  527. def _doLayout(self):
  528. WSDialogBase._doLayout(self)
  529. self.btnsizer.Add(item=self.btn_apply, proportion=0,
  530. flag=wx.ALL | wx.ALIGN_CENTER,
  531. border=10)
  532. self.btnsizer.Add(item=self.btn_ok, proportion=0,
  533. flag=wx.ALL | wx.ALIGN_CENTER,
  534. border=10)
  535. # bindings
  536. self.btn_apply.Bind(wx.EVT_BUTTON, self.OnApply)
  537. self.btn_ok.Bind(wx.EVT_BUTTON, self.OnSave)
  538. def LoadCapFiles(self, ws_cap_files, cmd):
  539. """Parse cap files and update dialog.
  540. For parameters description, see the constructor.
  541. """
  542. self.ch_ws_sizer.Clear(deleteWindows=True)
  543. self.cmd_to_set = cmd
  544. self.finished_panels_num = 0
  545. conn = self._getServerConnFromCmd(cmd)
  546. self.server.SetValue(conn['url'])
  547. self.password.SetValue(conn['password'])
  548. self.username.SetValue(conn['username'])
  549. self.layerName.SetValue(cmd[1]['map'])
  550. for ws, data in self.ws_panels.iteritems():
  551. cap_file = None
  552. if ws in ws_cap_files:
  553. cap_file = ws_cap_files[ws]
  554. data['panel'].ParseCapFile(url=conn['url'],
  555. username=conn['password'],
  556. password=conn['username'],
  557. cap_file=cap_file)
  558. def _getServerConnFromCmd(self, cmd):
  559. """Get url/server/passwod from cmd tuple
  560. """
  561. conn = {'url': '', 'username': '', 'password': ''}
  562. for k in conn.iterkeys():
  563. if k in cmd[1]:
  564. conn[k] = cmd[1][k]
  565. return conn
  566. def _apply(self):
  567. """Apply chosen values from widgets to web service layer."""
  568. lcmd = self.active_ws_panel.CreateCmd()
  569. if not lcmd:
  570. return
  571. active_ws = self.active_ws_panel.GetWebService()
  572. if 'WMS' not in active_ws:
  573. lcmd.append('capfile=' + self.revert_ws_cap_files[active_ws])
  574. self.giface.GetLayerTree().GetOptData(dcmd=lcmd,
  575. layer=self.layer,
  576. params=None,
  577. propwin=self)
  578. # TODO use just list or tuple
  579. cmd = cmdlist_to_tuple(lcmd)
  580. self.revert_cmd = cmd
  581. self._setRevertCapFiles(self._getCapFiles())
  582. self.giface.updateMap.emit()
  583. def UpdateDialogAfterConnection(self):
  584. """Connect to the server
  585. """
  586. WSDialogBase.UpdateDialogAfterConnection(self)
  587. if self._getConnectedWS():
  588. self.btn_ok.SetDefault()
  589. else:
  590. self.btn_connect.SetDefault()
  591. def OnApply(self, event):
  592. self._apply()
  593. def OnSave(self, event):
  594. self._apply()
  595. self._close()
  596. def OnClose(self, event):
  597. """Close dialog"""
  598. self._close()
  599. def _close(self):
  600. """Hide dialog"""
  601. self.Hide()
  602. self.LoadCapFiles(cmd=self.revert_cmd,
  603. ws_cap_files=self.revert_ws_cap_files)
  604. def OnPanelCapParsed(self, error_msg):
  605. """Called when panel has downloaded and parsed capabilities file.
  606. """
  607. WSDialogBase.OnPanelCapParsed(self, error_msg)
  608. if self.finished_panels_num == len(self.ws_panels):
  609. if self.cmd_to_set:
  610. self._updateWsPanelWidgetsByCmd(self.cmd_to_set)
  611. self.cmd_to_set = None
  612. def _updateWsPanelWidgetsByCmd(self, cmd):
  613. """Set values of widgets according to parameters in cmd.
  614. """
  615. ws = self._getWSfromCmd(cmd)
  616. if self.ws_panels[ws]['panel'].IsConnected():
  617. self.ws_panels[ws]['panel'].UpdateWidgetsByCmd(cmd)
  618. self.choose_ws_rb.SetStringSelection(self.ws_panels[ws]['label'])
  619. self._showWsPanel(ws)
  620. def _getWSfromCmd(self, cmd):
  621. driver = cmd[1]['driver']
  622. ws = driver.split('_')[0]
  623. if ws == 'WMS':
  624. ws += '_' + cmd[1]['wms_version']
  625. return ws
  626. class SaveWMSLayerDialog(wx.Dialog):
  627. """Dialog for saving web service layer into GRASS vector/raster layer.
  628. .. todo::
  629. Implement saving data in region of map display.
  630. """
  631. def __init__(self, parent, layer, giface):
  632. wx.Dialog.__init__(
  633. self, parent=parent,
  634. title=("Save web service layer as raster map"),
  635. id=wx.ID_ANY)
  636. self.layer = layer
  637. self._giface = giface
  638. self.cmd = self.layer.GetCmd()
  639. self.thread = CmdThread(self)
  640. self.cmdStdErr = GStderr(self)
  641. self._createWidgets()
  642. def _createWidgets(self):
  643. self.labels = {}
  644. self.params = {}
  645. self.labels['output'] = wx.StaticText(
  646. parent=self, id=wx.ID_ANY, label=_("Name for output raster map:"))
  647. self.params['output'] = Select(
  648. parent=self,
  649. type='raster',
  650. mapsets=[
  651. grass.gisenv()['MAPSET']],
  652. size=globalvar.DIALOG_GSELECT_SIZE)
  653. self.regionStBoxLabel = wx.StaticBox(parent=self, id=wx.ID_ANY,
  654. label=" %s " % _("Export region"))
  655. self.region_types_order = ['display', 'comp', 'named']
  656. self.region_types = {}
  657. self.region_types['display'] = wx.RadioButton(
  658. parent=self, label=_("Map display"), style=wx.RB_GROUP)
  659. self.region_types['comp'] = wx.RadioButton(
  660. parent=self, label=_("Computational region"))
  661. self.region_types['named'] = wx.RadioButton(
  662. parent=self, label=_("Named region"))
  663. self.region_types['display'].SetToolTipString(
  664. _("Extent and resolution" " are based on Map Display geometry."))
  665. self.region_types['comp'].SetToolTipString(
  666. _("Extent and resolution" " are based on computational region."))
  667. self.region_types['named'].SetToolTipString(
  668. _("Extent and resolution" " are based on named region."))
  669. self.region_types['display'].SetValue(
  670. True) # set default as map display
  671. self.overwrite = wx.CheckBox(parent=self, id=wx.ID_ANY,
  672. label=_("Overwrite existing raster map"))
  673. self.named_reg_panel = wx.Panel(parent=self, id=wx.ID_ANY)
  674. self.labels['region'] = wx.StaticText(
  675. parent=self.named_reg_panel, id=wx.ID_ANY,
  676. label=_("Choose named region:"))
  677. self.params['region'] = Select(
  678. parent=self.named_reg_panel, type='region',
  679. size=globalvar.DIALOG_GSELECT_SIZE)
  680. # buttons
  681. self.btn_close = wx.Button(parent=self, id=wx.ID_CLOSE)
  682. self.btn_close.SetToolTipString(_("Close dialog"))
  683. self.btn_ok = wx.Button(
  684. parent=self,
  685. id=wx.ID_OK,
  686. label=_("&Save layer"))
  687. self.btn_ok.SetToolTipString(_("Save web service layer as raster map"))
  688. # statusbar
  689. self.statusbar = wx.StatusBar(parent=self, id=wx.ID_ANY)
  690. self._layout()
  691. def _layout(self):
  692. self._border = wx.BoxSizer(wx.VERTICAL)
  693. dialogSizer = wx.BoxSizer(wx.VERTICAL)
  694. regionSizer = wx.BoxSizer(wx.HORIZONTAL)
  695. dialogSizer.Add(item=self._addSelectSizer(title=self.labels['output'],
  696. sel=self.params['output']))
  697. regionSizer = wx.StaticBoxSizer(self.regionStBoxLabel, wx.VERTICAL)
  698. regionTypeSizer = wx.BoxSizer(wx.HORIZONTAL)
  699. for r_type in self.region_types_order:
  700. regionTypeSizer.Add(
  701. item=self.region_types[r_type],
  702. flag=wx.RIGHT, border=8)
  703. regionSizer.Add(item=regionTypeSizer)
  704. self.named_reg_panel.SetSizer(
  705. self._addSelectSizer(
  706. title=self.labels['region'],
  707. sel=self.params['region']))
  708. regionSizer.Add(item=self.named_reg_panel)
  709. self.named_reg_panel.Hide()
  710. dialogSizer.Add(item=regionSizer, flag=wx.EXPAND)
  711. dialogSizer.Add(item=self.overwrite, flag=wx.TOP, border=10)
  712. # buttons
  713. self.btnsizer = wx.BoxSizer(orient=wx.HORIZONTAL)
  714. self.btnsizer.Add(item=self.btn_close, proportion=0,
  715. flag=wx.ALL | wx.ALIGN_CENTER,
  716. border=10)
  717. self.btnsizer.Add(item=self.btn_ok, proportion=0,
  718. flag=wx.ALL | wx.ALIGN_CENTER,
  719. border=10)
  720. dialogSizer.Add(item=self.btnsizer, proportion=0,
  721. flag=wx.ALIGN_CENTER)
  722. self._border.Add(item=dialogSizer, proportion=0,
  723. flag=wx.ALL, border=5)
  724. self._border.Add(item=self.statusbar, proportion=0)
  725. self.SetSizer(self._border)
  726. self.Layout()
  727. self.Fit()
  728. # bindings
  729. self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose)
  730. self.btn_ok.Bind(wx.EVT_BUTTON, self.OnSave)
  731. self.Bind(EVT_CMD_DONE, self.OnCmdDone)
  732. self.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput)
  733. for r_type in self.region_types_order:
  734. self.Bind(
  735. wx.EVT_RADIOBUTTON,
  736. self.OnRegionType,
  737. self.region_types[r_type])
  738. def _addSelectSizer(self, title, sel):
  739. """Helper layout function.
  740. """
  741. selSizer = wx.BoxSizer(orient=wx.VERTICAL)
  742. selTitleSizer = wx.BoxSizer(wx.HORIZONTAL)
  743. selTitleSizer.Add(item=title, proportion=1,
  744. flag=wx.LEFT | wx.TOP | wx.EXPAND, border=5)
  745. selSizer.Add(item=selTitleSizer, proportion=0,
  746. flag=wx.EXPAND)
  747. selSizer.Add(item=sel, proportion=1,
  748. flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER_VERTICAL,
  749. border=5)
  750. return selSizer
  751. def OnClose(self, event):
  752. """Close dialog
  753. """
  754. if not self.IsModal():
  755. self.Destroy()
  756. event.Skip()
  757. def OnRegionType(self, event):
  758. selected = event.GetEventObject()
  759. if selected == self.region_types['named']:
  760. self.named_reg_panel.Show()
  761. else:
  762. self.named_reg_panel.Hide()
  763. self._border.Layout()
  764. self.Fit()
  765. def OnSave(self, event):
  766. """Import WMS raster data into GRASS as raster layer.
  767. """
  768. self.thread.abort(abortall=True)
  769. currmapset = grass.gisenv()['MAPSET']
  770. self.output = self.params['output'].GetValue().strip()
  771. l_spl = self.output.strip().split("@")
  772. # check output layer
  773. msg = None
  774. if not self.output:
  775. msg = _('Missing output raster.')
  776. elif len(l_spl) > 1 and \
  777. l_spl[1] != currmapset:
  778. msg = _('Output map can be added only to current mapset.')
  779. elif not self.overwrite.IsChecked() and\
  780. grass.find_file(self.output, 'cell', '.')['fullname']:
  781. msg = _('Output map <%s> already exists' % self.output)
  782. if msg:
  783. GMessage(parent=self,
  784. message=msg)
  785. return
  786. self.output = l_spl[0]
  787. # check region
  788. region = self.params['region'].GetValue().strip()
  789. reg_spl = region.strip().split("@")
  790. reg_mapset = '.'
  791. if len(reg_spl) > 1:
  792. reg_mapset = reg_spl[1]
  793. if self.region_types['named'].GetValue():
  794. if not grass.find_file(reg_spl[0], 'windows', reg_mapset)[
  795. 'fullname']:
  796. msg = _(
  797. 'Region <%s> does not exist.' %
  798. self.params['region'].GetValue())
  799. GWarning(parent=self,
  800. message=msg)
  801. return
  802. # create r.in.wms command
  803. cmd = ('r.in.wms', deepcopy(self.cmd[1]))
  804. if 'map' in cmd[1]:
  805. del cmd[1]['map']
  806. cmd[1]['output'] = self.output
  807. if self.overwrite.IsChecked():
  808. cmd[1]['overwrite'] = True
  809. env = os.environ.copy()
  810. if self.region_types['named'].GetValue():
  811. cmd[1]['region'] = region
  812. elif self.region_types['display'].GetValue():
  813. region = self._giface.GetMapWindow().GetMap().SetRegion()
  814. env['GRASS_REGION'] = region
  815. cmdList = cmdtuple_to_list(cmd)
  816. self.currentPid = self.thread.GetId()
  817. self.thread.RunCmd(cmdList, env=env, stderr=self.cmdStdErr)
  818. self.statusbar.SetStatusText(_("Downloading data..."))
  819. def OnCmdDone(self, event):
  820. """When data are fetched.
  821. """
  822. if event.pid != self.currentPid:
  823. return
  824. self._addLayer()
  825. self.statusbar.SetStatusText("")
  826. def _addLayer(self):
  827. """Add layer into layer tree.
  828. """
  829. llist = self._giface.GetLayerList()
  830. if len(llist.GetLayersByName(self.output)) == 0:
  831. cmd = ['d.rast', 'map=' + self.output]
  832. llist.AddLayer(ltype='raster',
  833. name=self.output,
  834. cmd=cmd,
  835. checked=True)
  836. def OnCmdOutput(self, event):
  837. """Handle cmd output according to debug level.
  838. """
  839. if Debug.GetLevel() == 0:
  840. if event.type == 'error':
  841. msg = _('Unable to fetch data.\n')
  842. msg += event.text
  843. GWarning(parent=self,
  844. message=msg)
  845. else:
  846. Debug.msg(1, event.text)