dialogs.py 34 KB

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