dialogs.py 34 KB

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