widgets.py 38 KB

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