dialogs.py 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118
  1. """
  2. @package web_services.dialogs
  3. @brief Dialogs for web services.
  4. List of classes:
  5. - dialogs::WSDialogBase
  6. - dialogs::AddWSDialog
  7. - dialogs::WSPropertiesDialog
  8. - dialogs::SaveWMSLayerDialog
  9. (C) 2009-2013 by the GRASS Development Team
  10. This program is free software under the GNU General Public License
  11. (>=v2). Read the file COPYING that comes with GRASS for details.
  12. @author Martin Landa <landa.martin gmail.com>
  13. @author Stepan Turek <stepan.turek seznam.cz>
  14. """
  15. import wx
  16. import os
  17. import sys
  18. import shutil
  19. from copy import deepcopy
  20. import grass.script as grass
  21. from grass.script.task import cmdlist_to_tuple, cmdtuple_to_list
  22. from core import globalvar
  23. from core.debug import Debug
  24. from core.gcmd import GMessage, GWarning, GError, RunCommand
  25. from core.utils import GetSettingsPath, _
  26. from core.gconsole import CmdThread, GStderr, EVT_CMD_DONE, EVT_CMD_OUTPUT
  27. from gui_core.gselect import Select
  28. from gui_core.wrap import Button
  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 = wx.StaticBox(parent=self,
  68. id=wx.ID_ANY,
  69. label=_(" Server settings "))
  70. self.serverText = wx.StaticText(parent=self,
  71. id=wx.ID_ANY, label=_("Server:"))
  72. self.server = wx.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 = wx.StaticBox(parent=self.reqDataPanel,
  92. id=wx.ID_ANY,
  93. label=_(" Layer Manager Settings "))
  94. self.layerNameText = wx.StaticText(
  95. parent=self.reqDataPanel, id=wx.ID_ANY,
  96. label=_("Output layer name:"))
  97. self.layerName = wx.TextCtrl(parent=self.reqDataPanel, id=wx.ID_ANY)
  98. for ws in self.ws_panels.iterkeys():
  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. if not self.layerName.GetValue().strip():
  121. self.layerName.SetValue(title)
  122. def _doLayout(self):
  123. dialogSizer = wx.BoxSizer(wx.VERTICAL)
  124. dialogSizer.Add(self.settsManager, proportion=0,
  125. flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border=5)
  126. # connectin settings
  127. settingsSizer = wx.StaticBoxSizer(self.settingsBox, wx.VERTICAL)
  128. serverSizer = wx.FlexGridSizer(cols=3, vgap=5, hgap=5)
  129. serverSizer.Add(self.serverText,
  130. flag=wx.ALIGN_CENTER_VERTICAL)
  131. serverSizer.AddGrowableCol(1)
  132. serverSizer.Add(self.server,
  133. flag=wx.EXPAND | wx.ALL)
  134. serverSizer.Add(self.btn_connect)
  135. settingsSizer.Add(serverSizer, proportion=0,
  136. flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5)
  137. settingsSizer.Add(self.adv_conn,
  138. flag=wx.ALL | wx.EXPAND, border=5)
  139. dialogSizer.Add(settingsSizer, proportion=0,
  140. flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border=5)
  141. # layer name, parsed capabilites
  142. reqDataSizer = wx.BoxSizer(wx.VERTICAL)
  143. layerNameSizer = wx.StaticBoxSizer(self.layerNameBox, wx.HORIZONTAL)
  144. layerNameSizer.Add(self.layerNameText,
  145. flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL, border=5)
  146. layerNameSizer.Add(self.layerName,
  147. flag=wx.EXPAND, proportion=1)
  148. reqDataSizer.Add(layerNameSizer, flag=wx.TOP |
  149. wx.LEFT | wx.RIGHT | wx.EXPAND, border=5)
  150. self.ch_ws_sizer = wx.BoxSizer(wx.VERTICAL)
  151. reqDataSizer.Add(self.ch_ws_sizer, proportion=0,
  152. flag=wx.TOP | wx.EXPAND, border=5)
  153. for ws in self.ws_panels.iterkeys():
  154. reqDataSizer.Add(
  155. self.ws_panels[ws]['panel'],
  156. proportion=1,
  157. flag=wx.TOP | wx.LEFT | wx.RIGHT | wx.EXPAND,
  158. border=5)
  159. self.ws_panels[ws]['panel'].Hide()
  160. dialogSizer.Add(self.reqDataPanel, proportion=1,
  161. flag=wx.EXPAND)
  162. self.reqDataPanel.SetSizer(reqDataSizer)
  163. self.reqDataPanel.Hide()
  164. # buttons
  165. self.btnsizer = wx.BoxSizer(orient=wx.HORIZONTAL)
  166. self.btnsizer.Add(self.btn_close, proportion=0,
  167. flag=wx.ALL | wx.ALIGN_CENTER,
  168. border=10)
  169. dialogSizer.Add(self.btnsizer, proportion=0,
  170. flag=wx.ALIGN_CENTER)
  171. # expand wxWidget wx.StatusBar
  172. statusbarSizer = wx.BoxSizer(wx.HORIZONTAL)
  173. statusbarSizer.Add(self.statusbar, proportion=1, flag=wx.EXPAND)
  174. dialogSizer.Add(statusbarSizer,
  175. proportion=0,
  176. flag=wx.EXPAND)
  177. self.SetSizer(dialogSizer)
  178. self.Layout()
  179. self.SetMinSize((550, -1))
  180. self.SetMaxSize((-1, self.GetBestSize()[1]))
  181. self.Fit()
  182. def MakeAdvConnPane(self, pane):
  183. """Create advanced connection settings pane
  184. """
  185. self.usernameText = wx.StaticText(parent=pane,
  186. id=wx.ID_ANY, label=_("Username:"))
  187. self.username = wx.TextCtrl(parent=pane, id=wx.ID_ANY)
  188. self.passwText = wx.StaticText(parent=pane,
  189. id=wx.ID_ANY, label=_("Password:"))
  190. self.password = wx.TextCtrl(parent=pane, id=wx.ID_ANY,
  191. style=wx.TE_PASSWORD)
  192. # pane layout
  193. adv_conn_sizer = wx.BoxSizer(wx.VERTICAL)
  194. usernameSizer = wx.BoxSizer(wx.HORIZONTAL)
  195. usernameSizer.Add(self.usernameText,
  196. flag=wx.ALIGN_CENTER_VERTICAL, border=5)
  197. usernameSizer.Add(self.username, proportion=1,
  198. flag=wx.EXPAND, border=5)
  199. adv_conn_sizer.Add(usernameSizer,
  200. flag=wx.ALL | wx.EXPAND, border=5)
  201. passwSizer = wx.BoxSizer(wx.HORIZONTAL)
  202. passwSizer.Add(self.passwText,
  203. flag=wx.ALIGN_CENTER_VERTICAL, border=5)
  204. passwSizer.Add(self.password, proportion=1,
  205. flag=wx.EXPAND, border=5)
  206. adv_conn_sizer.Add(passwSizer,
  207. 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. """
  215. server = self.server.GetValue().strip()
  216. if not server:
  217. GMessage(parent=self, message=_(
  218. "No data source defined, settings are not saved."))
  219. return
  220. self.settsManager.SetDataToSave((server,
  221. self.username.GetValue(),
  222. self.password.GetValue()))
  223. self.settsManager.SaveSettings(name)
  224. def OnSettingsChanged(self, data):
  225. """Update widgets according to chosen settings"""
  226. # data list: [server, username, password]
  227. if len < 3:
  228. return
  229. self.server.SetValue(data[0])
  230. self.username.SetValue(data[1])
  231. self.password.SetValue(data[2])
  232. if data[1] or data[2]:
  233. self.adv_conn.Expand()
  234. else:
  235. self.adv_conn.Collapse(True)
  236. # clear content of the wxWidget wx.TextCtrl (Output layer
  237. # name:), based on changing default server selection in the
  238. # wxWidget wx.Choice
  239. if len(self.layerName.GetValue()) > 0:
  240. self.layerName.Clear()
  241. def OnClose(self, event):
  242. """Close the dialog
  243. """
  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 self.ws_panels.itervalues():
  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. """
  256. value = event.GetString()
  257. if value:
  258. self.btn_connect.Enable(True)
  259. else:
  260. self.btn_connect.Enable(False)
  261. # clear content of the wxWidget wx.TextCtrl (Output Layer
  262. # name:), based on changing content of the wxWidget
  263. # wx.TextCtrl (Server:)
  264. self.layerName.Clear()
  265. def OnOutputLayerName(self, event):
  266. """Update layer name to web service panel
  267. """
  268. lname = event.GetString()
  269. lname = lname.encode('ascii', 'replace')
  270. for v in self.ws_panels.itervalues():
  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 self.ws_panels.iterkeys():
  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 self.ws_panels.iteritems():
  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. self.active_ws_panel.Show()
  373. self.SetMaxSize((-1, -1))
  374. self.active_ws_panel.GetContainingSizer().Layout()
  375. def OnAdvConnPaneChanged(self, event):
  376. """Collapse search module box
  377. """
  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__(self, parent, giface, id=wx.ID_ANY,
  389. style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
  390. WSDialogBase.__init__(
  391. self,
  392. parent,
  393. id=wx.ID_ANY,
  394. style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER,
  395. **kwargs)
  396. self.SetTitle(_("Add web service layer"))
  397. self.parent = parent
  398. self.giface = giface
  399. self.btn_connect.SetDefault()
  400. def _createWidgets(self):
  401. WSDialogBase._createWidgets(self)
  402. self.btn_add = Button(
  403. parent=self,
  404. id=wx.ID_ANY,
  405. label=_("&Add layer"))
  406. self.btn_add.SetToolTip(
  407. _("Add selected web service layers as map layer into layer tree"))
  408. self.btn_add.Enable(False)
  409. self.run_btns.append(self.btn_add)
  410. def _doLayout(self):
  411. WSDialogBase._doLayout(self)
  412. self.btnsizer.Add(self.btn_add, proportion=0,
  413. flag=wx.ALL | wx.ALIGN_CENTER,
  414. border=10)
  415. # bindings
  416. self.btn_add.Bind(wx.EVT_BUTTON, self.OnAddLayer)
  417. def UpdateDialogAfterConnection(self):
  418. """Connect to the server
  419. """
  420. WSDialogBase.UpdateDialogAfterConnection(self)
  421. if self._getConnectedWS():
  422. self.btn_add.SetDefault()
  423. else:
  424. self.btn_connect.SetDefault()
  425. def OnAddLayer(self, event):
  426. """Add web service layer.
  427. """
  428. # add layer
  429. if self.active_ws_panel is None:
  430. return
  431. lcmd = self.active_ws_panel.CreateCmd()
  432. if not lcmd:
  433. return None
  434. # TODO: It is not clear how to do GetOptData in giface
  435. # knowing what GetOptData is doing might help
  436. # (maybe Get... is not the right name)
  437. # please fix giface if you know
  438. # tree -> giface
  439. # GetLayerTree -> GetLayerList
  440. # AddLayer -> AddLayer (but tree ones returns some layer,
  441. # giface ones nothing)
  442. # GetLayerInfo -> Layer object can by used instead
  443. # GetOptData -> unknown
  444. ltree = self.giface.GetLayerTree()
  445. active_ws = self.active_ws_panel.GetWebService()
  446. if 'WMS' not in active_ws:
  447. cap_file = self.active_ws_panel.GetCapFile()
  448. cmd_cap_file = grass.tempfile()
  449. shutil.copyfile(cap_file, cmd_cap_file)
  450. lcmd.append('capfile=' + cmd_cap_file)
  451. layer = ltree.AddLayer(ltype='wms',
  452. lname=self.active_ws_panel.GetOutputLayerName(),
  453. lchecked=True, lcmd=lcmd)
  454. ws_cap_files = self._getCapFiles()
  455. # create properties dialog
  456. cmd_list = ltree.GetLayerInfo(layer, 'cmd')
  457. cmd = cmdlist_to_tuple(cmd_list)
  458. prop_win = WSPropertiesDialog(parent=self.parent,
  459. giface=self.giface,
  460. id=wx.ID_ANY,
  461. layer=layer,
  462. ws_cap_files=ws_cap_files,
  463. cmd=cmd)
  464. prop_win.Hide()
  465. ltree.GetOptData(dcmd=None, layer=layer,
  466. params=None, propwin=prop_win)
  467. class WSPropertiesDialog(WSDialogBase):
  468. """Dialog for editing web service properties."""
  469. def __init__(self, parent, giface, layer, ws_cap_files, cmd, id=wx.ID_ANY,
  470. style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
  471. """
  472. :param giface: grass interface
  473. :param layer: layer tree item
  474. :param ws_cap_files: dict web service('WMS_1.1.1', 'WMS_1.3.0',
  475. 'WMTS', 'OnEarth') : cap file path cap files, which will be parsed
  476. :param cmd: cmd to which dialog widgets will be initialized if
  477. it is possible (cmp parameters exists in parsed web service cap_file)
  478. """
  479. WSDialogBase.__init__(
  480. self,
  481. parent,
  482. id=wx.ID_ANY,
  483. style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER,
  484. **kwargs)
  485. self.SetTitle(_("Web service layer properties"))
  486. self.layer = layer
  487. self.giface = giface
  488. # after web service panels are connected, set dialog widgets
  489. # according to cmd in this variable (if it is not None)
  490. self.cmd_to_set = None
  491. # store data needed for reverting
  492. self.revert_ws_cap_files = {}
  493. self.revert_cmd = cmd
  494. ws_cap = self._getWSfromCmd(cmd)
  495. for ws in self.ws_panels.iterkeys():
  496. # cap file used in cmd will be deleted, thnaks to the dialogs
  497. # destructor
  498. if ws == ws_cap and 'capfile' in cmd[1]:
  499. self.revert_ws_cap_files[ws] = cmd[1]['capfile']
  500. del ws_cap_files[ws]
  501. else:
  502. self.revert_ws_cap_files[ws] = grass.tempfile()
  503. self._setRevertCapFiles(ws_cap_files)
  504. self.LoadCapFiles(ws_cap_files=self.revert_ws_cap_files, cmd=cmd)
  505. self.btn_ok.SetDefault()
  506. def __del__(self):
  507. for f in self.revert_ws_cap_files.itervalues():
  508. grass.try_remove(f)
  509. def _setRevertCapFiles(self, ws_cap_files):
  510. for ws, f in ws_cap_files.iteritems():
  511. if os.path.isfile(ws_cap_files[ws]):
  512. shutil.copyfile(f, self.revert_ws_cap_files[ws])
  513. else:
  514. # delete file content
  515. f_o = open(f, 'w')
  516. f_o.close()
  517. def _createWidgets(self):
  518. WSDialogBase._createWidgets(self)
  519. self.btn_apply = Button(
  520. parent=self, id=wx.ID_ANY, label=_("&Apply"))
  521. self.btn_apply.SetToolTip(_("Apply changes"))
  522. self.btn_apply.Enable(False)
  523. self.run_btns.append(self.btn_apply)
  524. self.btn_ok = Button(parent=self, id=wx.ID_ANY, label=_("&OK"))
  525. self.btn_ok.SetToolTip(_("Apply changes and close dialog"))
  526. self.btn_ok.Enable(False)
  527. self.run_btns.append(self.btn_ok)
  528. def _doLayout(self):
  529. WSDialogBase._doLayout(self)
  530. self.btnsizer.Add(self.btn_apply, proportion=0,
  531. flag=wx.ALL | wx.ALIGN_CENTER,
  532. border=10)
  533. self.btnsizer.Add(self.btn_ok, proportion=0,
  534. flag=wx.ALL | wx.ALIGN_CENTER,
  535. border=10)
  536. # bindings
  537. self.btn_apply.Bind(wx.EVT_BUTTON, self.OnApply)
  538. self.btn_ok.Bind(wx.EVT_BUTTON, self.OnSave)
  539. def LoadCapFiles(self, ws_cap_files, cmd):
  540. """Parse cap files and update dialog.
  541. For parameters description, see the constructor.
  542. """
  543. self.ch_ws_sizer.Clear(True)
  544. self.cmd_to_set = cmd
  545. self.finished_panels_num = 0
  546. conn = self._getServerConnFromCmd(cmd)
  547. self.server.SetValue(conn['url'])
  548. self.password.SetValue(conn['password'])
  549. self.username.SetValue(conn['username'])
  550. self.layerName.SetValue(cmd[1]['map'])
  551. for ws, data in self.ws_panels.iteritems():
  552. cap_file = None
  553. if ws in ws_cap_files:
  554. cap_file = ws_cap_files[ws]
  555. data['panel'].ParseCapFile(url=conn['url'],
  556. username=conn['password'],
  557. password=conn['username'],
  558. cap_file=cap_file)
  559. def _getServerConnFromCmd(self, cmd):
  560. """Get url/server/passwod from cmd tuple
  561. """
  562. conn = {'url': '', 'username': '', 'password': ''}
  563. for k in conn.iterkeys():
  564. if k in cmd[1]:
  565. conn[k] = cmd[1][k]
  566. return conn
  567. def _apply(self):
  568. """Apply chosen values from widgets to web service layer."""
  569. lcmd = self.active_ws_panel.CreateCmd()
  570. if not lcmd:
  571. return
  572. active_ws = self.active_ws_panel.GetWebService()
  573. if 'WMS' not in active_ws:
  574. lcmd.append('capfile=' + self.revert_ws_cap_files[active_ws])
  575. self.giface.GetLayerTree().GetOptData(dcmd=lcmd,
  576. layer=self.layer,
  577. params=None,
  578. propwin=self)
  579. # TODO use just list or tuple
  580. cmd = cmdlist_to_tuple(lcmd)
  581. self.revert_cmd = cmd
  582. self._setRevertCapFiles(self._getCapFiles())
  583. self.giface.updateMap.emit()
  584. def UpdateDialogAfterConnection(self):
  585. """Connect to the server
  586. """
  587. WSDialogBase.UpdateDialogAfterConnection(self)
  588. if self._getConnectedWS():
  589. self.btn_ok.SetDefault()
  590. else:
  591. self.btn_connect.SetDefault()
  592. def OnApply(self, event):
  593. self._apply()
  594. def OnSave(self, event):
  595. self._apply()
  596. self._close()
  597. def OnClose(self, event):
  598. """Close dialog"""
  599. self._close()
  600. def _close(self):
  601. """Hide dialog"""
  602. self.Hide()
  603. self.LoadCapFiles(cmd=self.revert_cmd,
  604. ws_cap_files=self.revert_ws_cap_files)
  605. def OnPanelCapParsed(self, error_msg):
  606. """Called when panel has downloaded and parsed capabilities file.
  607. """
  608. WSDialogBase.OnPanelCapParsed(self, error_msg)
  609. if self.finished_panels_num == len(self.ws_panels):
  610. if self.cmd_to_set:
  611. self._updateWsPanelWidgetsByCmd(self.cmd_to_set)
  612. self.cmd_to_set = None
  613. def _updateWsPanelWidgetsByCmd(self, cmd):
  614. """Set values of widgets according to parameters in cmd.
  615. """
  616. ws = self._getWSfromCmd(cmd)
  617. if self.ws_panels[ws]['panel'].IsConnected():
  618. self.ws_panels[ws]['panel'].UpdateWidgetsByCmd(cmd)
  619. self.choose_ws_rb.SetStringSelection(self.ws_panels[ws]['label'])
  620. self._showWsPanel(ws)
  621. def _getWSfromCmd(self, cmd):
  622. driver = cmd[1]['driver']
  623. ws = driver.split('_')[0]
  624. if ws == 'WMS':
  625. ws += '_' + cmd[1]['wms_version']
  626. return ws
  627. class SaveWMSLayerDialog(wx.Dialog):
  628. """Dialog for saving web service layer into GRASS vector/raster layer.
  629. .. todo::
  630. Implement saving data in region of map display.
  631. """
  632. def __init__(self, parent, layer, giface):
  633. wx.Dialog.__init__(
  634. self, parent=parent,
  635. title=("Save web service layer as raster map"),
  636. id=wx.ID_ANY)
  637. self.layer = layer
  638. self._giface = giface
  639. self.cmd = self.layer.GetCmd()
  640. self.thread = CmdThread(self)
  641. self.cmdStdErr = GStderr(self)
  642. self._createWidgets()
  643. def _createWidgets(self):
  644. self.labels = {}
  645. self.params = {}
  646. self.labels['output'] = wx.StaticText(
  647. parent=self, id=wx.ID_ANY, label=_("Name for output raster map:"))
  648. self.params['output'] = Select(
  649. parent=self,
  650. type='raster',
  651. mapsets=[
  652. grass.gisenv()['MAPSET']],
  653. size=globalvar.DIALOG_GSELECT_SIZE)
  654. self.regionStBoxLabel = wx.StaticBox(parent=self, id=wx.ID_ANY,
  655. label=" %s " % _("Export region"))
  656. self.region_types_order = ['display', 'comp', 'named']
  657. self.region_types = {}
  658. self.region_types['display'] = wx.RadioButton(
  659. parent=self, label=_("Map display"), style=wx.RB_GROUP)
  660. self.region_types['comp'] = wx.RadioButton(
  661. parent=self, label=_("Computational region"))
  662. self.region_types['named'] = wx.RadioButton(
  663. parent=self, label=_("Named region"))
  664. self.region_types['display'].SetToolTipString(
  665. _("Extent and resolution" " are based on Map Display geometry."))
  666. self.region_types['comp'].SetToolTipString(
  667. _("Extent and resolution" " are based on computational region."))
  668. self.region_types['named'].SetToolTipString(
  669. _("Extent and resolution" " are based on named region."))
  670. self.region_types['display'].SetValue(
  671. True) # set default as map display
  672. self.overwrite = wx.CheckBox(parent=self, id=wx.ID_ANY,
  673. label=_("Overwrite existing raster map"))
  674. self.named_reg_panel = wx.Panel(parent=self, id=wx.ID_ANY)
  675. self.labels['region'] = wx.StaticText(
  676. parent=self.named_reg_panel, id=wx.ID_ANY,
  677. label=_("Choose named region:"))
  678. self.params['region'] = Select(
  679. parent=self.named_reg_panel, type='region',
  680. size=globalvar.DIALOG_GSELECT_SIZE)
  681. # buttons
  682. self.btn_close = Button(parent=self, id=wx.ID_CLOSE)
  683. self.btn_close.SetToolTip(_("Close dialog"))
  684. self.btn_ok = Button(
  685. parent=self,
  686. id=wx.ID_OK,
  687. label=_("&Save layer"))
  688. self.btn_ok.SetToolTip(_("Save web service layer as raster map"))
  689. # statusbar
  690. self.statusbar = wx.StatusBar(parent=self, id=wx.ID_ANY)
  691. self._layout()
  692. def _layout(self):
  693. self._border = wx.BoxSizer(wx.VERTICAL)
  694. dialogSizer = wx.BoxSizer(wx.VERTICAL)
  695. regionSizer = wx.BoxSizer(wx.HORIZONTAL)
  696. dialogSizer.Add(self._addSelectSizer(title=self.labels['output'],
  697. sel=self.params['output']))
  698. regionSizer = wx.StaticBoxSizer(self.regionStBoxLabel, wx.VERTICAL)
  699. regionTypeSizer = wx.BoxSizer(wx.HORIZONTAL)
  700. for r_type in self.region_types_order:
  701. regionTypeSizer.Add(
  702. self.region_types[r_type],
  703. flag=wx.RIGHT, border=8)
  704. regionSizer.Add(regionTypeSizer)
  705. self.named_reg_panel.SetSizer(
  706. self._addSelectSizer(
  707. title=self.labels['region'],
  708. sel=self.params['region']))
  709. regionSizer.Add(self.named_reg_panel)
  710. self.named_reg_panel.Hide()
  711. dialogSizer.Add(regionSizer, flag=wx.EXPAND)
  712. dialogSizer.Add(self.overwrite, flag=wx.TOP, border=10)
  713. # buttons
  714. self.btnsizer = wx.BoxSizer(orient=wx.HORIZONTAL)
  715. self.btnsizer.Add(self.btn_close, proportion=0,
  716. flag=wx.ALL | wx.ALIGN_CENTER,
  717. border=10)
  718. self.btnsizer.Add(self.btn_ok, proportion=0,
  719. flag=wx.ALL | wx.ALIGN_CENTER,
  720. border=10)
  721. dialogSizer.Add(self.btnsizer, proportion=0,
  722. flag=wx.ALIGN_CENTER)
  723. self._border.Add(dialogSizer, proportion=0,
  724. flag=wx.ALL, border=5)
  725. self._border.Add(self.statusbar, proportion=0)
  726. self.SetSizer(self._border)
  727. self.Layout()
  728. self.Fit()
  729. # bindings
  730. self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose)
  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 OnClose(self, event):
  753. """Close dialog
  754. """
  755. if not self.IsModal():
  756. self.Destroy()
  757. event.Skip()
  758. def OnRegionType(self, event):
  759. selected = event.GetEventObject()
  760. if selected == self.region_types['named']:
  761. self.named_reg_panel.Show()
  762. else:
  763. self.named_reg_panel.Hide()
  764. self._border.Layout()
  765. self.Fit()
  766. def OnSave(self, event):
  767. """Import WMS raster data into GRASS as raster layer.
  768. """
  769. self.thread.abort(abortall=True)
  770. currmapset = grass.gisenv()['MAPSET']
  771. self.output = self.params['output'].GetValue().strip()
  772. l_spl = self.output.strip().split("@")
  773. # check output layer
  774. msg = None
  775. if not self.output:
  776. msg = _('Missing output raster.')
  777. elif len(l_spl) > 1 and \
  778. l_spl[1] != currmapset:
  779. msg = _('Output map can be added only to current mapset.')
  780. elif not self.overwrite.IsChecked() and\
  781. grass.find_file(self.output, 'cell', '.')['fullname']:
  782. msg = _('Output map <%s> already exists' % self.output)
  783. if msg:
  784. GMessage(parent=self,
  785. message=msg)
  786. return
  787. self.output = l_spl[0]
  788. # check region
  789. region = self.params['region'].GetValue().strip()
  790. reg_spl = region.strip().split("@")
  791. reg_mapset = '.'
  792. if len(reg_spl) > 1:
  793. reg_mapset = reg_spl[1]
  794. if self.region_types['named'].GetValue():
  795. if not grass.find_file(reg_spl[0], 'windows', reg_mapset)[
  796. 'fullname']:
  797. msg = _(
  798. 'Region <%s> does not exist.' %
  799. self.params['region'].GetValue())
  800. GWarning(parent=self,
  801. message=msg)
  802. return
  803. # create r.in.wms command
  804. cmd = ('r.in.wms', deepcopy(self.cmd[1]))
  805. if 'map' in cmd[1]:
  806. del cmd[1]['map']
  807. cmd[1]['output'] = self.output
  808. if self.overwrite.IsChecked():
  809. cmd[1]['overwrite'] = True
  810. env = os.environ.copy()
  811. if self.region_types['named'].GetValue():
  812. cmd[1]['region'] = region
  813. elif self.region_types['display'].GetValue():
  814. region = self._giface.GetMapWindow().GetMap().SetRegion()
  815. env['GRASS_REGION'] = region
  816. cmdList = cmdtuple_to_list(cmd)
  817. self.currentPid = self.thread.GetId()
  818. self.thread.RunCmd(cmdList, env=env, stderr=self.cmdStdErr)
  819. self.statusbar.SetStatusText(_("Downloading data..."))
  820. def OnCmdDone(self, event):
  821. """When data are fetched.
  822. """
  823. if event.pid != self.currentPid:
  824. return
  825. self._addLayer()
  826. self.statusbar.SetStatusText("")
  827. def _addLayer(self):
  828. """Add layer into layer tree.
  829. """
  830. llist = self._giface.GetLayerList()
  831. if len(llist.GetLayersByName(self.output)) == 0:
  832. cmd = ['d.rast', 'map=' + self.output]
  833. llist.AddLayer(ltype='raster',
  834. name=self.output,
  835. cmd=cmd,
  836. checked=True)
  837. def OnCmdOutput(self, event):
  838. """Handle cmd output according to debug level.
  839. """
  840. if Debug.GetLevel() == 0:
  841. if event.type == 'error':
  842. msg = _('Unable to fetch data.\n')
  843. msg += event.text
  844. GWarning(parent=self,
  845. message=msg)
  846. else:
  847. Debug.msg(1, event.text)