dialogs.py 36 KB

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