widgets.py 38 KB

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