widgets.py 39 KB

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