widgets.py 39 KB

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