widgets.py 39 KB

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