dialogs.py 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107
  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. """
  33. def __init__(self, parent, id=wx.ID_ANY,
  34. style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
  35. wx.Dialog.__init__(self, parent, id, style=style, **kwargs)
  36. self.parent = parent
  37. # contains panel for every web service on server
  38. self.ws_panels = {'WMS_1.1.1': {'panel': None,
  39. 'label': 'WMS 1.1.1'},
  40. 'WMS_1.3.0': {'panel': None,
  41. 'label': 'WMS 1.3.0'},
  42. 'WMTS': {'panel': None,
  43. 'label': 'WMTS'},
  44. 'OnEarth': {'panel': None,
  45. 'label': 'OnEarth'},
  46. }
  47. # TODO: should be in file
  48. self.default_servers = {
  49. 'OSM-WMS-EUROPE':
  50. ['http://watzmann-geog.urz.uni-heidelberg.de/cached/osm', '', ''],
  51. 'irs.gis-lab.info (OSM)': ['http://irs.gis-lab.info', '', ''],
  52. 'NASA GIBS WMTS': ['http://gibs.earthdata.nasa.gov/wmts/epsg4326/best/wmts.cgi', '', '']}
  53. # holds reference to web service panel which is showed
  54. self.active_ws_panel = None
  55. # buttons which are disabled when the dialog is not connected
  56. self.run_btns = []
  57. # stores error messages for GError dialog showed when all web service
  58. # connections were unsuccessful
  59. self.error_msgs = ''
  60. self._createWidgets()
  61. self._doLayout()
  62. def _createWidgets(self):
  63. settingsFile = os.path.join(GetSettingsPath(), 'wxWS')
  64. self.settsManager = WSManageSettingsWidget(
  65. parent=self, settingsFile=settingsFile,
  66. default_servers=self.default_servers)
  67. self.settingsBox = StaticBox(parent=self,
  68. id=wx.ID_ANY,
  69. label=_(" Server settings "))
  70. self.serverText = StaticText(parent=self,
  71. id=wx.ID_ANY, label=_("Server:"))
  72. self.server = TextCtrl(parent=self, id=wx.ID_ANY)
  73. self.btn_connect = Button(parent=self,
  74. id=wx.ID_ANY, label=_("&Connect"))
  75. self.btn_connect.SetToolTip(_("Connect to the server"))
  76. if not self.server.GetValue():
  77. self.btn_connect.Enable(False)
  78. self.infoCollapseLabelExp = _('Show advanced connection settings')
  79. self.infoCollapseLabelCol = _('Hide advanced connection settings')
  80. self.adv_conn = wx.CollapsiblePane(parent=self,
  81. label=self.infoCollapseLabelExp,
  82. style=wx.CP_DEFAULT_STYLE |
  83. wx.CP_NO_TLW_RESIZE | wx.EXPAND)
  84. self.MakeAdvConnPane(pane=self.adv_conn.GetPane())
  85. self.adv_conn.Collapse(True)
  86. self.Bind(
  87. wx.EVT_COLLAPSIBLEPANE_CHANGED,
  88. self.OnAdvConnPaneChanged,
  89. self.adv_conn)
  90. self.reqDataPanel = wx.Panel(parent=self, id=wx.ID_ANY)
  91. self.layerNameBox = StaticBox(parent=self.reqDataPanel,
  92. id=wx.ID_ANY,
  93. label=_(" Layer Manager Settings "))
  94. self.layerNameText = StaticText(
  95. parent=self.reqDataPanel, id=wx.ID_ANY,
  96. label=_("Output layer name:"))
  97. self.layerName = TextCtrl(parent=self.reqDataPanel, id=wx.ID_ANY)
  98. for ws in six.iterkeys(self.ws_panels):
  99. # set class WSPanel argument layerNameTxtCtrl
  100. self.ws_panels[ws]['panel'] = WSPanel(parent=self.reqDataPanel,
  101. web_service=ws)
  102. self.ws_panels[ws]['panel'].capParsed.connect(
  103. self.OnPanelCapParsed)
  104. self.ws_panels[ws]['panel'].layerSelected.connect(
  105. self.OnLayerSelected)
  106. # buttons
  107. self.btn_close = Button(parent=self, id=wx.ID_CLOSE)
  108. self.btn_close.SetToolTip(_("Close dialog"))
  109. # statusbar
  110. self.statusbar = wx.StatusBar(parent=self, id=wx.ID_ANY)
  111. # bindings
  112. self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose)
  113. self.Bind(wx.EVT_CLOSE, self.OnClose)
  114. self.btn_connect.Bind(wx.EVT_BUTTON, self.OnConnect)
  115. self.server.Bind(wx.EVT_TEXT, self.OnServer)
  116. self.layerName.Bind(wx.EVT_TEXT, self.OnOutputLayerName)
  117. self.settsManager.settingsChanged.connect(self.OnSettingsChanged)
  118. self.settsManager.settingsSaving.connect(self.OnSettingsSaving)
  119. def OnLayerSelected(self, title):
  120. self.layerName.SetValue(title)
  121. def _doLayout(self):
  122. dialogSizer = wx.BoxSizer(wx.VERTICAL)
  123. dialogSizer.Add(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(self.serverText,
  129. flag=wx.ALIGN_CENTER_VERTICAL)
  130. serverSizer.AddGrowableCol(1)
  131. serverSizer.Add(self.server,
  132. flag=wx.EXPAND | wx.ALL)
  133. serverSizer.Add(self.btn_connect)
  134. settingsSizer.Add(serverSizer, proportion=0,
  135. flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5)
  136. settingsSizer.Add(self.adv_conn,
  137. flag=wx.ALL | wx.EXPAND, border=5)
  138. dialogSizer.Add(settingsSizer, proportion=0,
  139. flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border=5)
  140. # layer name, parsed capabilities
  141. reqDataSizer = wx.BoxSizer(wx.VERTICAL)
  142. layerNameSizer = wx.StaticBoxSizer(self.layerNameBox, wx.HORIZONTAL)
  143. layerNameSizer.Add(self.layerNameText,
  144. flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
  145. layerNameSizer.Add(self.layerName,
  146. flag=wx.EXPAND, proportion=1)
  147. reqDataSizer.Add(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(self.ch_ws_sizer, proportion=0,
  151. flag=wx.TOP | wx.EXPAND, border=5)
  152. for ws in six.iterkeys(self.ws_panels):
  153. reqDataSizer.Add(
  154. 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(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(self.btn_close, proportion=0,
  166. flag=wx.ALL | wx.ALIGN_CENTER,
  167. border=10)
  168. dialogSizer.Add(self.btnsizer, proportion=0,
  169. flag=wx.ALIGN_CENTER)
  170. # expand wxWidget wx.StatusBar
  171. statusbarSizer = wx.BoxSizer(wx.HORIZONTAL)
  172. statusbarSizer.Add(self.statusbar, proportion=1, flag=wx.EXPAND)
  173. dialogSizer.Add(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 = StaticText(parent=pane,
  185. id=wx.ID_ANY, label=_("Username:"))
  186. self.username = TextCtrl(parent=pane, id=wx.ID_ANY)
  187. self.passwText = StaticText(parent=pane,
  188. id=wx.ID_ANY, label=_("Password:"))
  189. self.password = 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(self.usernameText,
  195. flag=wx.ALIGN_CENTER_VERTICAL, border=5)
  196. usernameSizer.Add(self.username, proportion=1,
  197. flag=wx.EXPAND, border=5)
  198. adv_conn_sizer.Add(usernameSizer,
  199. flag=wx.ALL | wx.EXPAND, border=5)
  200. passwSizer = wx.BoxSizer(wx.HORIZONTAL)
  201. passwSizer.Add(self.passwText,
  202. flag=wx.ALIGN_CENTER_VERTICAL, border=5)
  203. passwSizer.Add(self.password, proportion=1,
  204. flag=wx.EXPAND, border=5)
  205. adv_conn_sizer.Add(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(data) < 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 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. """
  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. for v in six.itervalues(self.ws_panels):
  269. v['panel'].SetOutputLayerName(lname.strip())
  270. def OnConnect(self, event):
  271. """Connect to the server
  272. """
  273. server = self.server.GetValue().strip()
  274. self.ch_ws_sizer.Clear(True)
  275. if self.active_ws_panel is not None:
  276. self.reqDataPanel.Hide()
  277. for btn in self.run_btns:
  278. btn.Enable(False)
  279. self.active_ws_panel = None
  280. self.Layout()
  281. self.Fit()
  282. self.statusbar.SetStatusText(
  283. _("Connecting to <%s>..." % self.server.GetValue().strip()))
  284. # number of panels already connected
  285. self.finished_panels_num = 0
  286. for ws in six.iterkeys(self.ws_panels):
  287. self.ws_panels[ws]['panel'].ConnectToServer(
  288. url=server, username=self.username.GetValue(),
  289. password=self.password.GetValue())
  290. self.ws_panels[ws]['panel'].Hide()
  291. def OnPanelCapParsed(self, error_msg):
  292. """Called when panel has downloaded and parsed capabilities file.
  293. """
  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. """
  320. avail_ws = {}
  321. conn_ws = self._getConnectedWS()
  322. for ws in conn_ws:
  323. avail_ws[ws] = self.ws_panels[ws]
  324. self.web_service_sel = []
  325. self.rb_choices = []
  326. # at least one web service found on server
  327. if len(avail_ws) > 0:
  328. self.reqDataPanel.Show()
  329. self.rb_order = ['WMS_1.1.1', 'WMS_1.3.0', 'WMTS', 'OnEarth']
  330. for ws in self.rb_order:
  331. if ws in avail_ws:
  332. self.web_service_sel.append(ws)
  333. self.rb_choices.append(avail_ws[ws]['label'])
  334. self.choose_ws_rb = wx.RadioBox(
  335. parent=self.reqDataPanel, id=wx.ID_ANY,
  336. label=_("Available web services"),
  337. pos=wx.DefaultPosition, choices=self.rb_choices,
  338. majorDimension=1, style=wx.RA_SPECIFY_ROWS)
  339. self.Bind(wx.EVT_RADIOBOX, self.OnChooseWs, self.choose_ws_rb)
  340. self.ch_ws_sizer.Add(
  341. self.choose_ws_rb,
  342. flag=wx.TOP | wx.LEFT | wx.RIGHT | wx.EXPAND,
  343. border=5)
  344. self._showWsPanel(
  345. self.web_service_sel[
  346. self.choose_ws_rb.GetSelection()])
  347. self.statusbar.SetStatusText(
  348. _("Connected to <%s>" % self.server.GetValue().strip()))
  349. for btn in self.run_btns:
  350. btn.Enable(True)
  351. # no web service found on server
  352. else:
  353. self.statusbar.SetStatusText(
  354. _("Unable to connect to <%s>" % self.server.GetValue().strip()))
  355. for btn in self.run_btns:
  356. btn.Enable(False)
  357. self.reqDataPanel.Hide()
  358. self.active_ws_panel = None
  359. def OnChooseWs(self, event):
  360. """Show panel corresponding to selected web service.
  361. """
  362. choosen_r = event.GetInt()
  363. self._showWsPanel(self.web_service_sel[choosen_r])
  364. def _showWsPanel(self, ws):
  365. """Helper function
  366. """
  367. if self.active_ws_panel is not None:
  368. self.active_ws_panel.Hide()
  369. self.active_ws_panel = self.ws_panels[ws]['panel']
  370. if not self.active_ws_panel.IsShown():
  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 = Button(
  402. parent=self,
  403. id=wx.ID_ANY,
  404. label=_("&Add layer"))
  405. self.btn_add.SetToolTip(
  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(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 six.iterkeys(self.ws_panels):
  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 six.itervalues(self.revert_ws_cap_files):
  507. grass.try_remove(f)
  508. def _setRevertCapFiles(self, ws_cap_files):
  509. for ws, f in six.iteritems(ws_cap_files):
  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 = Button(
  519. parent=self, id=wx.ID_ANY, label=_("&Apply"))
  520. self.btn_apply.SetToolTip(_("Apply changes"))
  521. self.btn_apply.Enable(False)
  522. self.run_btns.append(self.btn_apply)
  523. self.btn_ok = Button(parent=self, id=wx.ID_ANY, label=_("&OK"))
  524. self.btn_ok.SetToolTip(_("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(self.btn_apply, proportion=0,
  530. flag=wx.ALL | wx.ALIGN_CENTER,
  531. border=10)
  532. self.btnsizer.Add(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(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 six.iteritems(self.ws_panels):
  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 six.iterkeys(conn):
  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=True,
  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.choose_ws_rb.SetStringSelection(self.ws_panels[ws]['label'])
  618. self._showWsPanel(ws)
  619. self.ws_panels[ws]['panel'].UpdateWidgetsByCmd(cmd)
  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'] = 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 = 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'] = RadioButton(
  658. parent=self, label=_("Map display"), style=wx.RB_GROUP)
  659. self.region_types['comp'] = RadioButton(
  660. parent=self, label=_("Computational region"))
  661. self.region_types['named'] = RadioButton(
  662. parent=self, label=_("Named region"))
  663. self.region_types['display'].SetToolTip(
  664. _("Extent and resolution" " are based on Map Display geometry."))
  665. self.region_types['comp'].SetToolTip(
  666. _("Extent and resolution" " are based on computational region."))
  667. self.region_types['named'].SetToolTip(
  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'] = 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 = Button(parent=self, id=wx.ID_CLOSE)
  682. self.SetEscapeId(self.btn_close.GetId())
  683. self.btn_close.SetToolTip(_("Close dialog"))
  684. self.btn_ok = Button(
  685. parent=self,
  686. label=_("&Save layer"))
  687. self.btn_ok.SetToolTip(_("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(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. self.region_types[r_type],
  702. flag=wx.RIGHT, border=8)
  703. regionSizer.Add(regionTypeSizer)
  704. self.named_reg_panel.SetSizer(
  705. self._addSelectSizer(
  706. title=self.labels['region'],
  707. sel=self.params['region']))
  708. regionSizer.Add(self.named_reg_panel)
  709. self.named_reg_panel.Hide()
  710. dialogSizer.Add(regionSizer, flag=wx.EXPAND)
  711. dialogSizer.Add(self.overwrite, flag=wx.TOP, border=10)
  712. # buttons
  713. self.btnsizer = wx.BoxSizer(orient=wx.HORIZONTAL)
  714. self.btnsizer.Add(self.btn_close, proportion=0,
  715. flag=wx.ALL | wx.ALIGN_CENTER,
  716. border=10)
  717. self.btnsizer.Add(self.btn_ok, proportion=0,
  718. flag=wx.ALL | wx.ALIGN_CENTER,
  719. border=10)
  720. dialogSizer.Add(self.btnsizer, proportion=0,
  721. flag=wx.ALIGN_CENTER)
  722. self._border.Add(dialogSizer, proportion=0,
  723. flag=wx.ALL, border=5)
  724. self._border.Add(self.statusbar, proportion=0)
  725. self.SetSizer(self._border)
  726. self.Layout()
  727. self.Fit()
  728. # bindings
  729. self.btn_ok.Bind(wx.EVT_BUTTON, self.OnSave)
  730. self.Bind(EVT_CMD_DONE, self.OnCmdDone)
  731. self.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput)
  732. for r_type in self.region_types_order:
  733. self.Bind(
  734. wx.EVT_RADIOBUTTON,
  735. self.OnRegionType,
  736. self.region_types[r_type])
  737. def _addSelectSizer(self, title, sel):
  738. """Helper layout function.
  739. """
  740. selSizer = wx.BoxSizer(orient=wx.VERTICAL)
  741. selTitleSizer = wx.BoxSizer(wx.HORIZONTAL)
  742. selTitleSizer.Add(title, proportion=1,
  743. flag=wx.LEFT | wx.TOP | wx.EXPAND, border=5)
  744. selSizer.Add(selTitleSizer, proportion=0,
  745. flag=wx.EXPAND)
  746. selSizer.Add(sel, proportion=1,
  747. flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER_VERTICAL,
  748. border=5)
  749. return selSizer
  750. def OnRegionType(self, event):
  751. selected = event.GetEventObject()
  752. if selected == self.region_types['named']:
  753. self.named_reg_panel.Show()
  754. else:
  755. self.named_reg_panel.Hide()
  756. self._border.Layout()
  757. self.Fit()
  758. def OnSave(self, event):
  759. """Import WMS raster data into GRASS as raster layer.
  760. """
  761. self.thread.abort(abortall=True)
  762. currmapset = grass.gisenv()['MAPSET']
  763. self.output = self.params['output'].GetValue().strip()
  764. l_spl = self.output.strip().split("@")
  765. # check output layer
  766. msg = None
  767. if not self.output:
  768. msg = _('Missing output raster.')
  769. elif len(l_spl) > 1 and \
  770. l_spl[1] != currmapset:
  771. msg = _('Output map can be added only to current mapset.')
  772. elif not self.overwrite.IsChecked() and\
  773. grass.find_file(self.output, 'cell', '.')['fullname']:
  774. msg = _('Output map <%s> already exists' % self.output)
  775. if msg:
  776. GMessage(parent=self,
  777. message=msg)
  778. return
  779. self.output = l_spl[0]
  780. # check region
  781. region = self.params['region'].GetValue().strip()
  782. reg_spl = region.strip().split("@")
  783. reg_mapset = '.'
  784. if len(reg_spl) > 1:
  785. reg_mapset = reg_spl[1]
  786. if self.region_types['named'].GetValue():
  787. if not grass.find_file(reg_spl[0], 'windows', reg_mapset)[
  788. 'fullname']:
  789. msg = _(
  790. 'Region <%s> does not exist.' %
  791. self.params['region'].GetValue())
  792. GWarning(parent=self,
  793. message=msg)
  794. return
  795. # create r.in.wms command
  796. cmd = ('r.in.wms', deepcopy(self.cmd[1]))
  797. if 'map' in cmd[1]:
  798. del cmd[1]['map']
  799. cmd[1]['output'] = self.output
  800. if self.overwrite.IsChecked():
  801. cmd[1]['overwrite'] = True
  802. env = os.environ.copy()
  803. if self.region_types['named'].GetValue():
  804. cmd[1]['region'] = region
  805. elif self.region_types['display'].GetValue():
  806. region = self._giface.GetMapWindow().GetMap().SetRegion()
  807. env['GRASS_REGION'] = region
  808. cmdList = cmdtuple_to_list(cmd)
  809. self.currentPid = self.thread.GetId()
  810. self.thread.RunCmd(cmdList, env=env, stderr=self.cmdStdErr)
  811. self.statusbar.SetStatusText(_("Downloading data..."))
  812. def OnCmdDone(self, event):
  813. """When data are fetched.
  814. """
  815. if event.pid != self.currentPid:
  816. return
  817. self._addLayer()
  818. self.statusbar.SetStatusText("")
  819. def _addLayer(self):
  820. """Add layer into layer tree.
  821. """
  822. llist = self._giface.GetLayerList()
  823. if len(llist.GetLayersByName(self.output)) == 0:
  824. cmd = ['d.rast', 'map=' + self.output]
  825. llist.AddLayer(ltype='raster',
  826. name=self.output,
  827. cmd=cmd,
  828. checked=True)
  829. def OnCmdOutput(self, event):
  830. """Handle cmd output according to debug level.
  831. """
  832. if Debug.GetLevel() == 0:
  833. if event.type == 'error':
  834. msg = _('Unable to fetch data.\n')
  835. msg += event.text
  836. GWarning(parent=self,
  837. message=msg)
  838. else:
  839. Debug.msg(1, event.text)