widgets.py 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178
  1. """
  2. @package web_services.widgets
  3. @brief Widgets for web services (WMS, WMTS, NasaOnEarh)
  4. List of classes:
  5. - widgets::WSPanel
  6. - widgets::LayersList
  7. (C) 2012-2013 by the GRASS Development Team
  8. This program is free software under the GNU General Public License
  9. (>=v2). Read the file COPYING that comes with GRASS for details.
  10. @author Martin Landa <landa.martin gmail.com>
  11. @author Stepan Turek <stepan.turek seznam.cz>
  12. """
  13. import os
  14. import sys
  15. import shutil
  16. from copy import deepcopy
  17. try:
  18. from xml.etree.ElementTree import ParseError
  19. except ImportError: # < Python 2.7
  20. from xml.parsers.expat import ExpatError as ParseError
  21. import wx
  22. import wx.lib.flatnotebook as FN
  23. import wx.lib.colourselect as csel
  24. import wx.lib.mixins.listctrl as listmix
  25. from wx.lib.newevent import NewEvent
  26. from wx.gizmos import TreeListCtrl
  27. from core import globalvar
  28. from core.debug import Debug
  29. from core.gcmd import GWarning, GMessage
  30. from core.gconsole import CmdThread, GStderr, EVT_CMD_DONE, EVT_CMD_OUTPUT
  31. from core.utils import _
  32. from web_services.cap_interface import WMSCapabilities, WMTSCapabilities, OnEarthCapabilities
  33. from gui_core.widgets import GNotebook
  34. from gui_core.widgets import ManageSettingsWidget
  35. from gui_core.wrap import GSpinCtrl as SpinCtrl
  36. import grass.script as grass
  37. rinwms_path = os.path.join(os.getenv("GISBASE"), "etc", "r.in.wms")
  38. if rinwms_path not in sys.path:
  39. sys.path.append(rinwms_path)
  40. from wms_base import WMSDriversInfo
  41. from srs import Srs
  42. from grass.pydispatch.signal import Signal
  43. class WSPanel(wx.Panel):
  44. def __init__(self, parent, web_service, **kwargs):
  45. """Show data from capabilities file.
  46. Signal: capParsed - this signal is emitted when capabilities file is downloaded
  47. (after ConnectToServer method was called)
  48. :param parent: parent widget
  49. :param web_service: web service to be panel generated for
  50. """
  51. wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
  52. self.parent = parent
  53. self.ws = web_service
  54. self.capParsed = Signal('WSPanel.capParsed')
  55. # stores widgets, which represents parameters/flags of d.wms
  56. self.params = {}
  57. self.flags = {}
  58. self.o_layer_name = ''
  59. # stores err output from r.in.wms during getting capabilities
  60. self.cmd_err_str = ''
  61. # stores selected layer from layer list
  62. self.sel_layers = []
  63. # downloaded and parsed data from server successfully?
  64. self.is_connected = False
  65. # common part of command for r.in.wms -c and d.wms
  66. self.ws_cmdl = None
  67. # provides information about driver parameters
  68. self.drv_info = WMSDriversInfo()
  69. self.drv_props = self.drv_info.GetDrvProperties(self.ws)
  70. self.ws_drvs = {
  71. 'WMS_1.1.1': {
  72. 'cmd': [
  73. 'wms_version=1.1.1',
  74. 'driver=WMS_GRASS'],
  75. 'cap_parser': lambda temp_file: WMSCapabilities(
  76. temp_file,
  77. '1.1.1'),
  78. },
  79. 'WMS_1.3.0': {
  80. 'cmd': [
  81. 'wms_version=1.3.0',
  82. 'driver=WMS_GRASS'],
  83. 'cap_parser': lambda temp_file: WMSCapabilities(
  84. temp_file,
  85. '1.3.0'),
  86. },
  87. 'WMTS': {
  88. 'cmd': ['driver=WMTS_GRASS'],
  89. 'cap_parser': WMTSCapabilities,
  90. },
  91. 'OnEarth': {
  92. 'cmd': ['driver=OnEarth_GRASS'],
  93. 'cap_parser': OnEarthCapabilities,
  94. }}
  95. self.cmdStdErr = GStderr(self)
  96. self.cmd_thread = CmdThread(self)
  97. self.cap_file = grass.tempfile()
  98. reqDataBox = wx.StaticBox(
  99. parent=self, label=_(" Requested data settings "))
  100. self._nb_sizer = wx.StaticBoxSizer(reqDataBox, wx.VERTICAL)
  101. self.notebook = GNotebook(parent=self,
  102. style=FN.FNB_FANCY_TABS | FN.FNB_NO_X_BUTTON)
  103. self._requestPage()
  104. self._advancedSettsPage()
  105. self._layout()
  106. self.layerSelected = self.list.layerSelected
  107. self.Bind(EVT_CMD_DONE, self.OnCapDownloadDone)
  108. self.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput)
  109. def __del__(self):
  110. self.cmd_thread.abort(abortall=True)
  111. grass.try_remove(self.cap_file)
  112. def _layout(self):
  113. self._nb_sizer.Add(item=self.notebook, proportion=1, flag=wx.EXPAND)
  114. self.SetSizer(self._nb_sizer)
  115. def _requestPage(self):
  116. """Create request page"""
  117. self.req_page_panel = wx.Panel(parent=self, id=wx.ID_ANY)
  118. self.notebook.AddPage(page=self.req_page_panel,
  119. text=_('Request'),
  120. name='request')
  121. # list of layers
  122. self.layersBox = wx.StaticBox(parent=self.req_page_panel, id=wx.ID_ANY,
  123. label=_("List of layers "))
  124. style = wx.TR_DEFAULT_STYLE | wx.TR_HAS_BUTTONS | wx.TR_FULL_ROW_HIGHLIGHT
  125. if self.drv_props['req_multiple_layers']:
  126. style = style | wx.TR_MULTIPLE
  127. if 'WMS' not in self.ws:
  128. style = style | wx.TR_HIDE_ROOT
  129. self.list = LayersList(parent=self.req_page_panel,
  130. web_service=self.ws,
  131. style=style)
  132. self.params['format'] = None
  133. self.params['srs'] = None
  134. if 'srs' not in self.drv_props['ignored_params']:
  135. projText = wx.StaticText(
  136. parent=self.req_page_panel, id=wx.ID_ANY,
  137. label=_("Source projection:"))
  138. self.params['srs'] = wx.Choice(
  139. parent=self.req_page_panel, id=wx.ID_ANY)
  140. self.list.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnListSelChanged)
  141. # layout
  142. self.req_page_sizer = wx.BoxSizer(wx.VERTICAL)
  143. layersSizer = wx.StaticBoxSizer(self.layersBox, wx.HORIZONTAL)
  144. layersSizer.Add(
  145. item=self.list,
  146. proportion=1,
  147. flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
  148. border=5)
  149. self.req_page_sizer.Add(
  150. item=layersSizer,
  151. proportion=1,
  152. flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
  153. border=5)
  154. self.source_sizer = wx.BoxSizer(wx.HORIZONTAL)
  155. if self.params['format'] is not None:
  156. self.source_sizer.Add(
  157. item=self.params['format'],
  158. flag=wx.LEFT | wx.RIGHT | wx.BOTTOM,
  159. border=5)
  160. if self.params['srs'] is not None:
  161. self.source_sizer.Add(
  162. item=projText,
  163. flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL,
  164. border=5)
  165. self.source_sizer.Add(
  166. item=self.params['srs'],
  167. flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.TOP | wx.BOTTOM,
  168. border=5)
  169. self.req_page_sizer.Add(
  170. item=self.source_sizer,
  171. flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND,
  172. border=5)
  173. self.req_page_panel.SetSizer(self.req_page_sizer)
  174. def enableButtons(self, enable=True):
  175. """Enable/disable up, down, buttons
  176. """
  177. self.btnUp.Enable(enable)
  178. self.btnDown.Enable(enable)
  179. def _advancedSettsPage(self):
  180. """Create advanced settings page
  181. """
  182. # TODO parse maxcol, maxrow, settings from d.wms module?
  183. # TODO OnEarth driver - add selection of time
  184. adv_setts_panel = wx.Panel(parent=self, id=wx.ID_ANY)
  185. self.notebook.AddPage(page=adv_setts_panel,
  186. text=_('Advanced request settings'),
  187. name='adv_req_setts')
  188. labels = {}
  189. self.l_odrder_list = None
  190. if 'WMS' in self.ws:
  191. labels['l_order'] = wx.StaticBox(
  192. parent=adv_setts_panel, id=wx.ID_ANY,
  193. label=_("Order of layers in raster"))
  194. self.l_odrder_list = wx.ListBox(
  195. adv_setts_panel, id=wx.ID_ANY, choices=[],
  196. style=wx.LB_SINGLE | wx.LB_NEEDED_SB)
  197. self.btnUp = wx.Button(
  198. adv_setts_panel, id=wx.ID_ANY, label=_("Up"))
  199. self.btnDown = wx.Button(
  200. adv_setts_panel, id=wx.ID_ANY, label=_("Down"))
  201. self.btnUp.Bind(wx.EVT_BUTTON, self.OnUp)
  202. self.btnDown.Bind(wx.EVT_BUTTON, self.OnDown)
  203. labels['method'] = wx.StaticText(parent=adv_setts_panel, id=wx.ID_ANY,
  204. label=_("Reprojection method:"))
  205. self.reproj_methods = ['nearest', 'linear', 'cubic', 'cubicspline']
  206. self.params['method'] = wx.Choice(
  207. parent=adv_setts_panel,
  208. id=wx.ID_ANY,
  209. choices=[
  210. _('Nearest neighbor'),
  211. _('Linear interpolation'),
  212. _('Cubic interpolation'),
  213. _('Cubic spline interpolation')])
  214. labels['maxcols'] = wx.StaticText(
  215. parent=adv_setts_panel, id=wx.ID_ANY,
  216. label=_("Maximum columns to request from server at time:"))
  217. self.params['maxcols'] = SpinCtrl(
  218. parent=adv_setts_panel, id=wx.ID_ANY, size=(100, -1))
  219. labels['maxrows'] = wx.StaticText(
  220. parent=adv_setts_panel, id=wx.ID_ANY,
  221. label=_("Maximum rows to request from server at time:"))
  222. self.params['maxrows'] = SpinCtrl(
  223. parent=adv_setts_panel, id=wx.ID_ANY, size=(100, -1))
  224. min = 100
  225. max = 10000
  226. self.params['maxcols'].SetRange(min, max)
  227. self.params['maxrows'].SetRange(min, max)
  228. val = 500
  229. self.params['maxcols'].SetValue(val)
  230. self.params['maxrows'].SetValue(val)
  231. self.flags['o'] = self.params['bgcolor'] = None
  232. if not 'o' in self.drv_props['ignored_flags']:
  233. self.flags['o'] = wx.CheckBox(
  234. parent=adv_setts_panel, id=wx.ID_ANY,
  235. label=_("Do not request transparent data"))
  236. self.flags['o'].Bind(wx.EVT_CHECKBOX, self.OnTransparent)
  237. labels['bgcolor'] = wx.StaticText(
  238. parent=adv_setts_panel, id=wx.ID_ANY,
  239. label=_("Background color:"))
  240. self.params['bgcolor'] = csel.ColourSelect(
  241. parent=adv_setts_panel, id=wx.ID_ANY, colour=(
  242. 255, 255, 255), size=globalvar.DIALOG_COLOR_SIZE)
  243. self.params['bgcolor'].Enable(False)
  244. self.params['urlparams'] = None
  245. if self.params['urlparams'] not in self.drv_props['ignored_params']:
  246. labels['urlparams'] = wx.StaticText(
  247. parent=adv_setts_panel, id=wx.ID_ANY,
  248. label=_("Additional query parameters for server:"))
  249. self.params['urlparams'] = wx.TextCtrl(
  250. parent=adv_setts_panel, id=wx.ID_ANY)
  251. # layout
  252. border = wx.BoxSizer(wx.VERTICAL)
  253. if 'WMS' in self.ws:
  254. boxSizer = wx.StaticBoxSizer(labels['l_order'], wx.VERTICAL)
  255. gridSizer = wx.GridBagSizer(hgap=3, vgap=3)
  256. gridSizer.Add(self.l_odrder_list,
  257. pos=(0, 0),
  258. span=(4, 1),
  259. flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  260. border=0)
  261. gridSizer.Add(self.btnUp,
  262. pos=(0, 1),
  263. flag=wx.ALIGN_CENTER_VERTICAL,
  264. border=0)
  265. gridSizer.Add(self.btnDown,
  266. pos=(1, 1),
  267. flag=wx.ALIGN_CENTER_VERTICAL,
  268. border=0)
  269. gridSizer.AddGrowableCol(0)
  270. boxSizer.Add(gridSizer,
  271. flag=wx.EXPAND | wx.ALL,
  272. border=5)
  273. border.Add(item=boxSizer,
  274. flag=wx.LEFT | wx.RIGHT | wx.UP | wx.EXPAND,
  275. border=5)
  276. gridSizer = wx.GridBagSizer(hgap=3, vgap=3)
  277. row = 0
  278. for k in ['method', 'maxcols', 'maxrows', 'o', 'bgcolor']:
  279. if k in self.params:
  280. param = self.params[k]
  281. elif k in self.flags:
  282. param = self.flags[k]
  283. if param is None:
  284. continue
  285. if k in labels or k == 'o':
  286. if k != 'o':
  287. label = labels[k]
  288. else:
  289. label = param
  290. gridSizer.Add(label,
  291. flag=wx.ALIGN_LEFT |
  292. wx.ALIGN_CENTER_VERTICAL,
  293. pos=(row, 0))
  294. if k != 'o':
  295. gridSizer.Add(item=param,
  296. flag=wx.ALIGN_RIGHT |
  297. wx.ALIGN_CENTER_VERTICAL,
  298. pos=(row, 1))
  299. row += 1
  300. gridSizer.AddGrowableCol(0)
  301. border.Add(item=gridSizer,
  302. flag=wx.LEFT | wx.RIGHT | wx.TOP | wx.EXPAND,
  303. border=5)
  304. if self.params['urlparams']:
  305. gridSizer = wx.GridBagSizer(hgap=3, vgap=3)
  306. row = 0
  307. gridSizer.Add(labels['urlparams'],
  308. flag=wx.ALIGN_LEFT |
  309. wx.ALIGN_CENTER_VERTICAL,
  310. pos=(row, 0))
  311. gridSizer.Add(item=self.params['urlparams'],
  312. flag=wx.ALIGN_RIGHT |
  313. wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
  314. pos=(row, 1))
  315. gridSizer.AddGrowableCol(1)
  316. border.Add(item=gridSizer,
  317. flag=wx.LEFT | wx.RIGHT | wx.TOP | wx.EXPAND,
  318. border=5)
  319. adv_setts_panel.SetSizer(border)
  320. def OnUp(self, event):
  321. """Move selected layer up
  322. """
  323. if self.l_odrder_list.GetSelections():
  324. pos = self.l_odrder_list.GetSelection()
  325. if pos:
  326. self.sel_layers.insert(pos - 1, self.sel_layers.pop(pos))
  327. if pos > 0:
  328. self._updateLayerOrderList(selected=(pos - 1))
  329. else:
  330. self._updateLayerOrderList(selected=0)
  331. def OnDown(self, event):
  332. """Move selected to down
  333. """
  334. if self.l_odrder_list.GetSelections():
  335. pos = self.l_odrder_list.GetSelection()
  336. if pos != len(self.sel_layers) - 1:
  337. self.sel_layers.insert(pos + 1, self.sel_layers.pop(pos))
  338. if pos < len(self.sel_layers) - 1:
  339. self._updateLayerOrderList(selected=(pos + 1))
  340. else:
  341. self._updateLayerOrderList(selected=len(self.sel_layers) - 1)
  342. def _updateLayerOrderList(self, selected=None):
  343. """Update order in list.
  344. """
  345. def getlayercaption(layer):
  346. if l['title']:
  347. cap = (l['title'])
  348. else:
  349. cap = (l['name'])
  350. if l['style']:
  351. if l['style']['title']:
  352. cap += ' / ' + l['style']['title']
  353. else:
  354. cap += ' / ' + l['style']['name']
  355. return cap
  356. layer_capts = [getlayercaption(l) for l in self.sel_layers]
  357. self.l_odrder_list.Set(layer_capts)
  358. if self.l_odrder_list.IsEmpty():
  359. self.enableButtons(False)
  360. else:
  361. self.enableButtons(True)
  362. if selected is not None:
  363. self.l_odrder_list.SetSelection(selected)
  364. self.l_odrder_list.EnsureVisible(selected)
  365. def OnTransparent(self, event):
  366. checked = event.IsChecked()
  367. if checked:
  368. self.params['bgcolor'].Enable(True)
  369. else:
  370. self.params['bgcolor'].Enable(False)
  371. def ConnectToServer(self, url, username, password):
  372. """Download and parse data from capabilities file.
  373. :param url: server url
  374. :type url: str
  375. :param username: username for connection
  376. :type username: str
  377. :param password: password for connection
  378. :type password: str
  379. """
  380. self._prepareForNewConn(url, username, password)
  381. cap_cmd = [
  382. 'r.in.wms',
  383. '-c',
  384. ('capfile_output=%s' % self.cap_file),
  385. '--overwrite'] + self.ws_cmdl
  386. self.currentPid = self.cmd_thread.GetId()
  387. self.cmd_thread.RunCmd(cap_cmd, stderr=self.cmdStdErr)
  388. def OnCmdOutput(self, event):
  389. """Manage cmd output.
  390. """
  391. if Debug.GetLevel() != 0:
  392. Debug.msg(1, event.text)
  393. elif event.type != 'message' and event.type != 'warning':
  394. self.cmd_err_str += event.text + os.linesep
  395. def _prepareForNewConn(self, url, username, password):
  396. """Prepare panel for new connection
  397. """
  398. self.is_connected = False
  399. self.sel_layers = []
  400. self.formats_list = []
  401. self.projs_list = []
  402. self.conn = {
  403. 'url': url,
  404. 'password': password,
  405. 'username': username
  406. }
  407. conn_cmd = []
  408. for k, v in self.conn.iteritems():
  409. if v:
  410. conn_cmd.append("%s=%s" % (k, v))
  411. self.ws_cmdl = self.ws_drvs[self.ws]['cmd'] + conn_cmd
  412. def OnCapDownloadDone(self, event):
  413. """Process donwloaded capabilities file and emits capParsed
  414. signal (see class constructor).
  415. """
  416. if event.pid != self.currentPid:
  417. return
  418. if event.returncode != 0:
  419. if self.cmd_err_str:
  420. self.cmd_err_str = _(
  421. "Unable to download %s capabilities file\nfrom <%s>:\n" %
  422. (self.ws.replace('_', ' '),
  423. self.conn['url'])) + self.cmd_err_str
  424. self._postCapParsedEvt(error_msg=self.cmd_err_str)
  425. self.cmd_err_str = ''
  426. return
  427. self._parseCapFile(self.cap_file)
  428. def _parseCapFile(self, cap_file):
  429. """Parse capabilities data and emits capParsed signal
  430. (see class constructor).
  431. """
  432. try:
  433. self.cap = self.ws_drvs[self.ws]['cap_parser'](cap_file)
  434. except (IOError, ParseError) as error:
  435. error_msg = _(
  436. "%s web service was not found in fetched capabilities file from <%s>:\n%s\n" %
  437. (self.ws, self.conn['url'], str(error)))
  438. if Debug.GetLevel() != 0:
  439. Debug.msg(1, error_msg)
  440. self._postCapParsedEvt(None)
  441. else:
  442. self._postCapParsedEvt(error_msg=error_msg)
  443. return
  444. self.is_connected = True
  445. # WMS standard has formats defined for all layers
  446. if 'WMS' in self.ws:
  447. self.formats_list = sorted(self._getFormats())
  448. self._updateFormatRadioBox(self.formats_list)
  449. self._setDefaultFormatVal()
  450. self.list.LoadData(self.cap)
  451. self.OnListSelChanged(event=None)
  452. self._postCapParsedEvt(None)
  453. def ParseCapFile(self, url, username, password, cap_file=None,):
  454. """Parse capabilities data and emits capParsed signal
  455. (see class constructor).
  456. """
  457. self._prepareForNewConn(url, username, password)
  458. if cap_file is None or not url:
  459. self._postCapParsedEvt(None)
  460. return
  461. shutil.copyfile(cap_file, self.cap_file)
  462. self._parseCapFile(self.cap_file)
  463. def UpdateWidgetsByCmd(self, cmd):
  464. """Update panel widgets accordnig to passed cmd tuple
  465. :param cmd: cmd in tuple
  466. """
  467. dcmd = cmd[1]
  468. layers = []
  469. if 'layers' in dcmd:
  470. layers = dcmd['layers']
  471. styles = []
  472. if 'styles' in dcmd:
  473. styles = dcmd['styles']
  474. if 'WMS' in self.ws:
  475. layers = layers.split(',')
  476. styles = styles.split(',')
  477. else:
  478. layers = [layers]
  479. styles = [styles]
  480. if len(layers) != len(styles):
  481. styles = [''] * len(layers)
  482. l_st_list = []
  483. for i in range(len(layers)):
  484. l_st_list.append({'style': styles[i],
  485. 'layer': layers[i]})
  486. # WMS standard - first layer in params is most bottom...
  487. # therefore layers order need to be reversed
  488. l_st_list = [l for l in reversed(l_st_list)]
  489. self.list.SelectLayers(l_st_list)
  490. params = {}
  491. if 'format' in dcmd:
  492. params['format'] = dcmd['format']
  493. if 'srs' in dcmd:
  494. params['srs'] = 'EPSG:' + dcmd['srs']
  495. if 'method' in dcmd:
  496. params['method'] = dcmd['method']
  497. for p, v in params.iteritems():
  498. if self.params[p]:
  499. self.params[p].SetStringSelection(v)
  500. for p, conv_f in [
  501. ('urlparams', None),
  502. ('maxcols', int),
  503. ('maxrows', int)]:
  504. if p in dcmd:
  505. v = dcmd[p]
  506. if conv_f:
  507. v = conv_f(v)
  508. self.params[p].SetValue(v)
  509. if 'flags' in dcmd and \
  510. 'o' in dcmd['flags']:
  511. self.flags['o'].SetValue(1)
  512. self.params['bgcolor'].Enable(True)
  513. if 'bgcolor' in dcmd and \
  514. self.params['bgcolor']:
  515. bgcolor = dcmd['bgcolor'].strip().lower()
  516. if len(bgcolor) == 8 and \
  517. '0x' == bgcolor[:2]:
  518. colour = '#' + bgcolor[2:]
  519. self.params['bgcolor'].SetColour(colour)
  520. def IsConnected(self):
  521. """Was successful in downloading and parsing capabilities data?
  522. """
  523. return self.is_connected
  524. def _postCapParsedEvt(self, error_msg):
  525. """Helper function
  526. """
  527. self.capParsed.emit(error_msg=error_msg)
  528. def CreateCmd(self):
  529. """Create d.wms cmd from values of panels widgets
  530. :return: cmd list
  531. :return: None if required widgets do not have selected/filled values.
  532. """
  533. # check required widgets
  534. if not self._checkImportValues():
  535. return None
  536. # create d.wms command
  537. lcmd = self.ws_cmdl
  538. lcmd = ['d.wms'] + lcmd
  539. layers = "layers="
  540. styles = 'styles='
  541. first = True
  542. # WMS standard - first layer in params is most bottom...
  543. # therefore layers order need to be reversed
  544. for layer in reversed(self.sel_layers):
  545. if not first:
  546. layers += ','
  547. styles += ','
  548. first = False
  549. layers += layer['name']
  550. if layer['style'] is not None:
  551. styles += layer['style']['name']
  552. lcmd.append(layers)
  553. lcmd.append(styles)
  554. if 'format' not in self.drv_props['ignored_params']:
  555. i_format = self.params['format'].GetSelection()
  556. lcmd.append("format=%s" % self.formats_list[i_format])
  557. if 'srs' not in self.drv_props['ignored_params']:
  558. i_srs = self.params['srs'].GetSelection()
  559. epsg_num = int(self.projs_list[i_srs].split(':')[-1])
  560. lcmd.append("srs=%s" % epsg_num)
  561. for k in ['maxcols', 'maxrows', 'urlparams']:
  562. lcmd.append(k + '=' + str(self.params[k].GetValue()))
  563. i_method = self.params['method'].GetSelection()
  564. lcmd.append('method=' + self.reproj_methods[i_method])
  565. if not 'o' in self.drv_props['ignored_flags'] and \
  566. self.flags['o'].IsChecked():
  567. lcmd.append('-o')
  568. c = self.params['bgcolor'].GetColour()
  569. hex_color = wx.Colour(
  570. c[0],
  571. c[1],
  572. c[2]).GetAsString(
  573. wx.C2S_HTML_SYNTAX)
  574. lcmd.append("bgcolor=" + '0x' + hex_color[1:])
  575. lcmd.append("map=" + self.o_layer_name)
  576. return lcmd
  577. def OnListSelChanged(self, event):
  578. """Update widgets according to selected layer in list.
  579. """
  580. curr_sel_ls = self.list.GetSelectedLayers()
  581. # update self.sel_layers (selected layer list)
  582. if 'WMS' in self.ws:
  583. for sel_l in self.sel_layers[:]:
  584. if sel_l not in curr_sel_ls:
  585. self.sel_layers.remove(sel_l)
  586. for l in curr_sel_ls:
  587. if l not in self.sel_layers:
  588. self.sel_layers.append(l)
  589. self._updateLayerOrderList()
  590. else:
  591. self.sel_layers = curr_sel_ls
  592. # update projection
  593. self.projs_list = []
  594. projs_list = []
  595. intersect_proj = []
  596. first = True
  597. for l in curr_sel_ls:
  598. layer_projs = l['cap_intf_l'].GetLayerData('srs')
  599. if first:
  600. projs_list = layer_projs
  601. first = False
  602. continue
  603. projs_list = set(projs_list).intersection(layer_projs)
  604. if 'srs' not in self.drv_props['ignored_params']:
  605. for proj in projs_list:
  606. proj_code = Srs(proj.strip()).getcode()
  607. proj_spl = proj_code.split(':')
  608. if proj_spl[0].strip().lower() in self.drv_info.GetSrs():
  609. try:
  610. int(proj_spl[1])
  611. self.projs_list.append(proj_code)
  612. except ValueError as IndexError:
  613. continue
  614. cur_sel = self.params['srs'].GetStringSelection()
  615. self.projs_list = sorted(self.projs_list)
  616. self.params['srs'].SetItems(self.projs_list)
  617. if cur_sel:
  618. self.params['srs'].SetStringSelection(cur_sel)
  619. else:
  620. try:
  621. i = self.projs_list.index('EPSG:4326')
  622. self.params['srs'].SetSelection(i)
  623. except ValueError:
  624. if len(self.projs_list) > 0:
  625. self.params['srs'].SetSelection(0)
  626. # update format
  627. if 'WMS' not in self.ws and \
  628. 'format' not in self.drv_props['ignored_params']:
  629. self.formats_list = []
  630. cur_sel = None
  631. if self.params['format'] is not None:
  632. cur_sel = self.params['format'].GetStringSelection()
  633. if len(curr_sel_ls) > 0:
  634. self.formats_list = sorted(
  635. self._getFormats(
  636. curr_sel_ls[0]['cap_intf_l']))
  637. self._updateFormatRadioBox(self.formats_list)
  638. if cur_sel:
  639. self.params['format'].SetStringSelection(cur_sel)
  640. else:
  641. self._setDefaultFormatVal()
  642. self.Layout()
  643. def _setDefaultFormatVal(self):
  644. """Set default format value.
  645. """
  646. try:
  647. i = self.formats_list.index('png')
  648. self.params['format'].SetSelection(i)
  649. except ValueError:
  650. pass
  651. def _updateFormatRadioBox(self, formats_list):
  652. """Helper function
  653. """
  654. if self.params['format'] is not None:
  655. self.req_page_sizer.Detach(self.params['format'])
  656. self.params['format'].Destroy()
  657. if len(self.formats_list) > 0:
  658. self.params['format'] = wx.RadioBox(
  659. parent=self.req_page_panel,
  660. id=wx.ID_ANY,
  661. label=_("Source image format"),
  662. pos=wx.DefaultPosition,
  663. choices=formats_list,
  664. majorDimension=4,
  665. style=wx.RA_SPECIFY_COLS)
  666. self.source_sizer.Insert(item=self.params['format'], before=2,
  667. flag=wx.LEFT | wx.RIGHT | wx.BOTTOM,
  668. border=5)
  669. def _getFormats(self, layer=None):
  670. """Get formats
  671. WMS has formats defined generally for whole cap.
  672. In WMTS and NASA OnEarh formats are defined for layer.
  673. """
  674. formats_label = []
  675. if layer is None:
  676. formats_list = self.cap.GetFormats()
  677. else:
  678. formats_list = layer.GetLayerData('format')
  679. for frmt in formats_list:
  680. frmt = frmt.strip()
  681. label = self.drv_info.GetFormatLabel(frmt)
  682. if label:
  683. formats_label.append(label)
  684. return formats_label
  685. def _checkImportValues(self,):
  686. """Check if required widgets are selected/filled
  687. """
  688. warning_str = ""
  689. show_war = False
  690. if not self.list or not self.list.GetSelectedLayers():
  691. warning_str += _("Select layer in layer list.\n")
  692. show_war = True
  693. if self.params['format'] is not None and \
  694. self.params['format'].GetSelection() == -1:
  695. warning_str += _("Select source image format.\n")
  696. show_war = True
  697. if self.params['srs'] is not None and \
  698. self.params['srs'].GetSelection() == -1:
  699. warning_str += _("Select source projection.\n")
  700. show_war = True
  701. if not self.o_layer_name:
  702. warning_str += _("Choose output layer name.\n")
  703. show_war = True
  704. if show_war:
  705. GMessage(parent=self.parent, message=warning_str)
  706. return False
  707. return True
  708. def SetOutputLayerName(self, name):
  709. """Set name of layer to be added to layer tree
  710. """
  711. self.o_layer_name = name
  712. def GetOutputLayerName(self):
  713. return self.o_layer_name
  714. def GetCapFile(self):
  715. """Get path to file where capabilities are saved
  716. """
  717. return self.cap_file
  718. def GetWebService(self):
  719. """Get web service
  720. """
  721. return self.ws
  722. class LayersList(TreeListCtrl, listmix.ListCtrlAutoWidthMixin):
  723. def __init__(self, parent, web_service, style, pos=wx.DefaultPosition):
  724. """List of layers and styles available in capabilities file
  725. """
  726. self.parent = parent
  727. self.ws = web_service
  728. TreeListCtrl.__init__(self, parent=parent, id=wx.ID_ANY, style=style)
  729. # setup mixins
  730. listmix.ListCtrlAutoWidthMixin.__init__(self)
  731. if self.ws != 'OnEarth':
  732. self.AddColumn(_('Name'))
  733. self.AddColumn(_('Type'))
  734. else:
  735. self.AddColumn(_('Layer name'))
  736. self.SetMainColumn(0) # column with the tree
  737. self.setResizeColumn(0)
  738. self.root = None
  739. self.Bind(wx.EVT_TREE_SEL_CHANGING, self.OnListSelChanging)
  740. self.layerSelected = Signal('LayersList.layerSelected')
  741. def LoadData(self, cap=None):
  742. """Load data into list
  743. """
  744. # detete first all items
  745. self.DeleteAllItems()
  746. if not cap:
  747. return
  748. def AddLayerChildrenToTree(parent_layer, parent_item):
  749. """Recursive function which adds all capabilities
  750. layers/styles to the LayersList.
  751. """
  752. def gettitle(layer):
  753. """Helper function"""
  754. if layer.GetLayerData('title') is not None:
  755. layer_title = layer.GetLayerData('title')
  756. elif layer.GetLayerData('name') is not None:
  757. layer_title = layer.GetLayerData('name')
  758. else:
  759. layer_title = str(layer.GetId())
  760. return layer_title
  761. def addlayer(layer, item):
  762. if self.ws != 'OnEarth':
  763. self.SetItemText(item, _('layer'), 1)
  764. styles = layer.GetLayerData('styles')
  765. def_st = None
  766. for st in styles:
  767. if st['name']:
  768. style_name = st['name']
  769. else:
  770. continue
  771. if st['title']:
  772. style_name = st['title']
  773. if st['isDefault']:
  774. def_st = st
  775. style_item = self.AppendItem(item, style_name)
  776. if self.ws != 'OnEarth':
  777. self.SetItemText(style_item, _('style'), 1)
  778. self.SetPyData(style_item, {'type': 'style',
  779. 'layer': layer, # it is parent layer of style
  780. 'style': st})
  781. self.SetPyData(item, {'type': 'layer', # is it layer or style?
  782. 'layer': layer, # Layer instance from web_services.cap_interface
  783. 'style': def_st}) # layer can have assigned default style
  784. if parent_layer is None:
  785. parent_layer = cap.GetRootLayer()
  786. layer_title = gettitle(parent_layer)
  787. parent_item = self.AddRoot(layer_title)
  788. addlayer(parent_layer, parent_item)
  789. for layer in parent_layer.GetChildren():
  790. item = self.AppendItem(parent_item, gettitle(layer))
  791. addlayer(layer, item)
  792. AddLayerChildrenToTree(layer, item)
  793. AddLayerChildrenToTree(None, None)
  794. # self.ExpandAll(self.GetRootItem())
  795. def GetSelectedLayers(self):
  796. """Get selected layers/styles in LayersList
  797. :return: dict with these items:
  798. * 'name' : layer name used for request
  799. if it is style, it is name of parent layer
  800. * 'title' : layer title
  801. * 'style' : {'name' : 'style name', title : 'style title'}
  802. * 'cap_intf_l' : \*Layer instance from web_services.cap_interface
  803. """
  804. sel_layers = self.GetSelections()
  805. sel_layers_dict = []
  806. for s in sel_layers:
  807. try:
  808. layer = self.GetPyData(s)['layer']
  809. except ValueError:
  810. continue
  811. sel_layers_dict.append({
  812. 'name': layer.GetLayerData('name'),
  813. 'title': layer.GetLayerData('title'),
  814. 'style': self.GetPyData(s)['style'],
  815. 'cap_intf_l': layer
  816. })
  817. return sel_layers_dict
  818. def OnListSelChanging(self, event):
  819. """Do not allow selecting items, which cannot be requested from server.
  820. """
  821. def _emitSelected(layer):
  822. title = layer.GetLayerData('title')
  823. self.layerSelected.emit(title=title)
  824. def _selectRequestableChildren(item, list_to_check, items_to_sel):
  825. self.Expand(item)
  826. child_item, cookie = self.GetFirstChild(item)
  827. while child_item.IsOk():
  828. if self.GetPyData(child_item)['layer'].IsRequestable() \
  829. and not self.IsSelected(child_item):
  830. items_to_sel.append(child_item)
  831. elif not self.GetPyData(child_item)['layer'].IsRequestable():
  832. list_to_check.append(child_item)
  833. child_item, cookie = self.GetNextChild(item, cookie)
  834. cur_item = event.GetItem()
  835. if not self.GetPyData(cur_item)['layer'].IsRequestable():
  836. event.Veto()
  837. if not self.HasFlag(wx.TR_MULTIPLE):
  838. return
  839. _emitSelected(self.GetPyData(cur_item)['layer'])
  840. items_to_chck = []
  841. items_to_sel = []
  842. chck_item = cur_item
  843. while True:
  844. _selectRequestableChildren(
  845. chck_item, items_to_chck, items_to_sel)
  846. if items_to_chck:
  847. chck_item = items_to_chck.pop()
  848. else:
  849. break
  850. while items_to_sel:
  851. self.SelectItem(items_to_sel.pop(), unselect_others=False)
  852. else:
  853. _emitSelected(self.GetPyData(cur_item)['layer'])
  854. def GetItemCount(self):
  855. """Required for listmix.ListCtrlAutoWidthMixin
  856. """
  857. return 0
  858. def GetCountPerPage(self):
  859. """Required for listmix.ListCtrlAutoWidthMixin
  860. """
  861. return 0
  862. def SelectLayers(self, l_st_list):
  863. """Select layers/styles in LayersList
  864. :param l_st_list: [{style : 'style_name', layer : 'layer_name'}, ...]
  865. :return: items from l_st_list which were not found
  866. """
  867. def checknext(item, l_st_list, items_to_sel):
  868. def compare(item, l_name, st_name):
  869. it_l_name = self.GetPyData(item)['layer'].GetLayerData('name')
  870. it_st = self.GetPyData(item)['style']
  871. it_type = self.GetPyData(item)['type']
  872. if it_l_name == l_name and ((not it_st and not st_name) or (
  873. it_st and it_st['name'] == st_name and it_type == 'style')):
  874. return True
  875. return False
  876. for i, l_st in enumerate(l_st_list):
  877. l_name = l_st['layer']
  878. st_name = l_st['style']
  879. if compare(item, l_name, st_name):
  880. items_to_sel[i] = [item, l_st]
  881. break
  882. if len(items_to_sel) == len(l_st_list):
  883. item = self.GetNext(item)
  884. if not item.IsOk():
  885. return
  886. checknext(item, l_st_list, items_to_sel)
  887. self.UnselectAll()
  888. l_st_list = deepcopy(l_st_list)
  889. root_item = self.GetRootItem()
  890. items_to_sel = [None] * len(l_st_list)
  891. checknext(root_item, l_st_list, items_to_sel)
  892. # items are selected according to position in l_st_list
  893. # to be added to Layers order list in right order
  894. for i in items_to_sel:
  895. if not i:
  896. continue
  897. item, l_st = i
  898. un_o = True
  899. if self.HasFlag(wx.TR_MULTIPLE):
  900. un_o = False
  901. self.SelectItem(item, unselect_others=un_o)
  902. l_st_list.remove(l_st)
  903. return l_st_list
  904. class WSManageSettingsWidget(ManageSettingsWidget):
  905. def __init__(self, parent, settingsFile, default_servers):
  906. ManageSettingsWidget.__init__(self, parent, settingsFile)
  907. self.default_servers = default_servers
  908. def _layout(self):
  909. self.btnAddDefaultServers = wx.Button(parent=self, id=wx.ID_ANY,
  910. label=_("Add default"))
  911. self.btnAddDefaultServers.Bind(wx.EVT_BUTTON, self.OnAddDefaultServers)
  912. ManageSettingsWidget._layout(self)
  913. self.settingsSizer.Add(item=self.btnAddDefaultServers,
  914. flag=wx.RIGHT,
  915. border=5)
  916. def OnAddDefaultServers(self, event):
  917. setts = self.GetSettings()
  918. self.servers_to_add = {}
  919. for k, v in self.default_servers.iteritems():
  920. if k not in setts.iterkeys():
  921. self.servers_to_add[k] = v
  922. elif v != setts[k]:
  923. GMessage(parent=self,
  924. message=_("User defined server with same name "
  925. "as default server <%s> already exists.\n"
  926. "Keeping user defined server") % (k))
  927. if self.servers_to_add:
  928. self.AddSettings(self.servers_to_add)