widgets.py 38 KB

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