frame.py 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949
  1. #!/usr/bin/env python
  2. """
  3. @package frame
  4. @brief Temporal Plot Tool
  5. Classes:
  6. - frame::DataCursor
  7. - frame::TplotFrame
  8. - frame::LookUp
  9. (C) 2012-2014 by the GRASS Development Team
  10. This program is free software under the GNU General Public License
  11. (>=v2). Read the file COPYING that comes with GRASS for details.
  12. @author Luca Delucchi
  13. @author start stvds support Matej Krejci
  14. """
  15. from itertools import cycle
  16. import numpy as np
  17. import wx
  18. from grass.pygrass.modules import Module
  19. import grass.script as grass
  20. from core.utils import _
  21. try:
  22. import matplotlib
  23. # The recommended way to use wx with mpl is with the WXAgg
  24. # backend.
  25. matplotlib.use('WXAgg')
  26. from matplotlib.figure import Figure
  27. from matplotlib.backends.backend_wxagg import \
  28. FigureCanvasWxAgg as FigCanvas, \
  29. NavigationToolbar2WxAgg as NavigationToolbar
  30. import matplotlib.dates as mdates
  31. from matplotlib import cbook
  32. except ImportError:
  33. raise ImportError(_('The Temporal Plot Tool needs the "matplotlib" '
  34. '(python-matplotlib) package to be installed.'))
  35. from core.utils import _
  36. import grass.temporal as tgis
  37. from core.gcmd import GMessage, GError, GException, RunCommand
  38. from gui_core.widgets import CoordinatesValidator
  39. from gui_core import gselect
  40. from core import globalvar
  41. from grass.pygrass.vector.geometry import Point
  42. from grass.pygrass.raster import RasterRow
  43. from collections import OrderedDict
  44. from subprocess import PIPE
  45. try:
  46. import wx.lib.agw.flatnotebook as FN
  47. except ImportError:
  48. import wx.lib.flatnotebook as FN
  49. from gui_core.widgets import GNotebook
  50. ALPHA = 0.5
  51. COLORS = ['b', 'g', 'r', 'c', 'm', 'y', 'k']
  52. def check_version(*version):
  53. """Checks if given version or newer is installed"""
  54. versionInstalled = []
  55. for i in matplotlib.__version__.split('.'):
  56. try:
  57. v = int(i)
  58. versionInstalled.append(v)
  59. except ValueError:
  60. versionInstalled.append(0)
  61. if versionInstalled < list(version):
  62. return False
  63. else:
  64. return True
  65. def findBetween(s, first, last):
  66. try:
  67. start = s.rindex(first) + len(first)
  68. end = s.rindex(last, start)
  69. return s[start:end]
  70. except ValueError:
  71. return ""
  72. class TplotFrame(wx.Frame):
  73. """The main frame of the application"""
  74. def __init__(self, parent, giface):
  75. wx.Frame.__init__(self, parent, id=wx.ID_ANY,
  76. title=_("GRASS GIS Temporal Plot Tool"))
  77. tgis.init(True)
  78. self._giface = giface
  79. self.datasetsV = None
  80. self.datasetsR = None
  81. # self.vectorDraw=False
  82. # self.rasterDraw=False
  83. self.init()
  84. self._layout()
  85. # We create a database interface here to speedup the GUI
  86. self.dbif = tgis.SQLDatabaseInterfaceConnection()
  87. self.dbif.connect()
  88. def init(self):
  89. self.timeDataR = OrderedDict()
  90. self.timeDataV = OrderedDict()
  91. self.temporalType = None
  92. self.unit = None
  93. self.listWhereConditions = []
  94. self.plotNameListR = []
  95. self.plotNameListV = []
  96. self.poi = None
  97. def __del__(self):
  98. """Close the database interface and stop the messenger and C-interface
  99. subprocesses.
  100. """
  101. if self.dbif.connected is True:
  102. self.dbif.close()
  103. tgis.stop_subprocesses()
  104. def _layout(self):
  105. """Creates the main panel with all the controls on it:
  106. * mpl canvas
  107. * mpl navigation toolbar
  108. * Control panel for interaction
  109. """
  110. self.mainPanel = wx.Panel(self)
  111. # Create the mpl Figure and FigCanvas objects.
  112. # 5x4 inches, 100 dots-per-inch
  113. #
  114. # color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
  115. # ------------CANVAS AND TOOLBAR------------
  116. self.fig = Figure((5.0, 4.0), facecolor=(1, 1, 1))
  117. self.canvas = FigCanvas(self.mainPanel, wx.ID_ANY, self.fig)
  118. # axes are initialized later
  119. self.axes2d = None
  120. self.axes3d = None
  121. # Create the navigation toolbar, tied to the canvas
  122. #
  123. self.toolbar = NavigationToolbar(self.canvas)
  124. #
  125. # Layout
  126. #
  127. # ------------MAIN VERTICAL SIZER------------
  128. self.vbox = wx.BoxSizer(wx.VERTICAL)
  129. self.vbox.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.EXPAND)
  130. self.vbox.Add(self.toolbar, 0, wx.EXPAND)
  131. # self.vbox.AddSpacer(10)
  132. # ------------ADD NOTEBOOK------------
  133. self.ntb = GNotebook(parent=self.mainPanel, style=FN.FNB_FANCY_TABS)
  134. # ------------ITEMS IN NOTEBOOK PAGE (RASTER)------------------------
  135. self.controlPanelRaster = wx.Panel(parent=self.ntb, id=wx.ID_ANY)
  136. self.datasetSelectLabelR = wx.StaticText(parent=self.controlPanelRaster,
  137. id=wx.ID_ANY,
  138. label=_('Raster temporal '
  139. 'dataset (strds)'))
  140. self.datasetSelectR = gselect.Select(parent=self.controlPanelRaster,
  141. id=wx.ID_ANY,
  142. size=globalvar.DIALOG_GSELECT_SIZE,
  143. type='strds', multiple=True)
  144. self.coor = wx.StaticText(parent=self.controlPanelRaster, id=wx.ID_ANY,
  145. label=_('X and Y coordinates separated by '
  146. 'comma:'))
  147. try:
  148. self._giface.GetMapWindow()
  149. self.coorval = gselect.CoordinatesSelect(parent=self.controlPanelRaster,
  150. giface=self._giface)
  151. except:
  152. self.coorval = wx.TextCtrl(parent=self.controlPanelRaster,
  153. id=wx.ID_ANY,
  154. size=globalvar.DIALOG_TEXTCTRL_SIZE,
  155. validator=CoordinatesValidator())
  156. self.coorval.SetToolTipString(_("Coordinates can be obtained for example"
  157. " by right-clicking on Map Display."))
  158. self.controlPanelSizerRaster = wx.BoxSizer(wx.VERTICAL)
  159. # self.controlPanelSizer.Add(wx.StaticText(self.panel, id=wx.ID_ANY,
  160. # label=_("Select space time raster dataset(s):")),
  161. # pos=(0, 0), flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
  162. self.controlPanelSizerRaster.Add(self.datasetSelectLabelR,
  163. flag=wx.EXPAND)
  164. self.controlPanelSizerRaster.Add(self.datasetSelectR, flag=wx.EXPAND)
  165. self.controlPanelSizerRaster.Add(self.coor, flag=wx.EXPAND)
  166. self.controlPanelSizerRaster.Add(self.coorval, flag=wx.EXPAND)
  167. self.controlPanelRaster.SetSizer(self.controlPanelSizerRaster)
  168. self.controlPanelSizerRaster.Fit(self)
  169. self.ntb.AddPage(page=self.controlPanelRaster, text=_('STRDS'),
  170. name='STRDS')
  171. # ------------ITEMS IN NOTEBOOK PAGE (VECTOR)------------------------
  172. self.controlPanelVector = wx.Panel(parent=self.ntb, id=wx.ID_ANY)
  173. self.datasetSelectLabelV = wx.StaticText(parent=self.controlPanelVector,
  174. id=wx.ID_ANY,
  175. label=_('Vector temporal '
  176. 'dataset (strds)'))
  177. self.datasetSelectV = gselect.Select(parent=self.controlPanelVector,
  178. id=wx.ID_ANY,
  179. size=globalvar.DIALOG_GSELECT_SIZE,
  180. type='stvds', multiple=True)
  181. self.datasetSelectV.Bind(wx.EVT_TEXT, self.OnVectorSelected)
  182. self.attribute = gselect.ColumnSelect(parent=self.controlPanelVector)
  183. self.attributeLabel = wx.StaticText(parent=self.controlPanelVector,
  184. id=wx.ID_ANY,
  185. label=_('Select attribute column'))
  186. # TODO fix the category selection as done for coordinates
  187. try:
  188. self._giface.GetMapWindow()
  189. self.cats = gselect.VectorCategorySelect(parent=self.controlPanelVector,
  190. giface=self._giface)
  191. except:
  192. self.cats = wx.TextCtrl(parent=self.controlPanelVector, id=wx.ID_ANY,
  193. size=globalvar.DIALOG_TEXTCTRL_SIZE)
  194. self.catsLabel = wx.StaticText(parent=self.controlPanelVector,
  195. id=wx.ID_ANY,
  196. label=_('Select category of vector(s)'))
  197. self.controlPanelSizerVector = wx.BoxSizer(wx.VERTICAL)
  198. #self.controlPanelSizer.Add(wx.StaticText(self.panel, id=wx.ID_ANY,
  199. #label=_("Select space time raster dataset(s):")),
  200. #pos=(0, 0), flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
  201. self.controlPanelSizerVector.Add(self.datasetSelectLabelV,
  202. flag=wx.EXPAND)
  203. self.controlPanelSizerVector.Add(self.datasetSelectV, flag=wx.EXPAND)
  204. self.controlPanelSizerVector.Add(self.attributeLabel, flag=wx.EXPAND)
  205. self.controlPanelSizerVector.Add(self.attribute, flag=wx.EXPAND)
  206. self.controlPanelSizerVector.Add(self.catsLabel, flag=wx.EXPAND)
  207. self.controlPanelSizerVector.Add(self.cats, flag=wx.EXPAND)
  208. self.controlPanelVector.SetSizer(self.controlPanelSizerVector)
  209. self.controlPanelSizerVector.Fit(self)
  210. self.ntb.AddPage(page=self.controlPanelVector, text=_('STVDS'),
  211. name='STVDS')
  212. # ------------Buttons on the bottom(draw,help)------------
  213. self.vButtPanel = wx.Panel(self.mainPanel, id=wx.ID_ANY)
  214. self.vButtSizer = wx.BoxSizer(wx.HORIZONTAL)
  215. self.drawButton = wx.Button(self.vButtPanel, id=wx.ID_ANY,
  216. label=_("Draw"))
  217. self.drawButton.Bind(wx.EVT_BUTTON, self.OnRedraw)
  218. self.helpButton = wx.Button(self.vButtPanel, id=wx.ID_ANY,
  219. label=_("Help"))
  220. self.helpButton.Bind(wx.EVT_BUTTON, self.OnHelp)
  221. self.vButtSizer.Add(self.drawButton)
  222. self.vButtSizer.Add(self.helpButton)
  223. self.vButtPanel.SetSizer(self.vButtSizer)
  224. self.mainPanel.SetSizer(self.vbox)
  225. self.vbox.Add(self.ntb, flag=wx.EXPAND)
  226. self.vbox.Add(self.vButtPanel, flag=wx.EXPAND)
  227. self.vbox.Fit(self)
  228. self.mainPanel.Fit()
  229. def _getSTRDdata(self, timeseries):
  230. """Load data and read properties
  231. :param list timeseries: a list of timeseries
  232. """
  233. mode = None
  234. unit = None
  235. columns = ','.join(['name', 'start_time', 'end_time'])
  236. for series in timeseries:
  237. name = series[0]
  238. fullname = name + '@' + series[1]
  239. etype = series[2]
  240. sp = tgis.dataset_factory(etype, fullname)
  241. if not sp.is_in_db(dbif=self.dbif):
  242. GError(message=_("Dataset <%s> not found in temporal "
  243. "database") % (fullname), parent=self)
  244. return
  245. sp.select(dbif=self.dbif)
  246. minmin = sp.metadata.get_min_min()
  247. self.plotNameListR.append(name)
  248. self.timeDataR[name] = OrderedDict()
  249. self.timeDataR[name]['temporalDataType'] = etype
  250. self.timeDataR[name]['temporalType'] = sp.get_temporal_type()
  251. self.timeDataR[name]['granularity'] = sp.get_granularity()
  252. if mode is None:
  253. mode = self.timeDataR[name]['temporalType']
  254. elif self.timeDataR[name]['temporalType'] != mode:
  255. GError(parent=self, message=_("Datasets have different temporal"
  256. " type (absolute x relative), "
  257. "which is not allowed."))
  258. return
  259. # check topology
  260. maps = sp.get_registered_maps_as_objects(dbif=self.dbif)
  261. self.timeDataR[name]['validTopology'] = sp.check_temporal_topology(maps=maps, dbif=self.dbif)
  262. self.timeDataR[name]['unit'] = None # only with relative
  263. if self.timeDataR[name]['temporalType'] == 'relative':
  264. start, end, self.timeDataR[name]['unit'] = sp.get_relative_time()
  265. if unit is None:
  266. unit = self.timeDataR[name]['unit']
  267. elif self.timeDataR[name]['unit'] != unit:
  268. GError(parent=self, message=_("Datasets have different "
  269. "time unit which is not "
  270. "allowed."))
  271. return
  272. rows = sp.get_registered_maps(columns=columns, where=None,
  273. order='start_time', dbif=self.dbif)
  274. for row in rows:
  275. self.timeDataR[name][row[0]] = {}
  276. self.timeDataR[name][row[0]]['start_datetime'] = row[1]
  277. self.timeDataR[name][row[0]]['end_datetime'] = row[2]
  278. r = RasterRow(row[0])
  279. r.open()
  280. val = r.get_value(self.poi)
  281. r.close()
  282. if val == -2147483648 and val < minmin:
  283. self.timeDataR[name][row[0]]['value'] = None
  284. else:
  285. self.timeDataR[name][row[0]]['value'] = val
  286. self.unit = unit
  287. self.temporalType = mode
  288. return
  289. def _parseVDbConn(self, mapp, layerInp):
  290. '''find attribute key according to layer of input map'''
  291. vdb = Module('v.db.connect', map=mapp, flags='g', stdout_=PIPE)
  292. vdb = vdb.outputs.stdout
  293. for line in vdb.splitlines():
  294. lsplit = line.split('|')
  295. layer = lsplit[0].split('/')[0]
  296. if str(layer) == str(layerInp):
  297. return lsplit[2]
  298. return None
  299. def _getSTVDData(self, timeseries):
  300. """Load data and read properties
  301. :param list timeseries: a list of timeseries
  302. """
  303. mode = None
  304. unit = None
  305. cats = None
  306. attribute = self.attribute.GetValue()
  307. if self.cats.GetValue() != '':
  308. cats = self.cats.GetValue().split(',')
  309. if cats and self.poi:
  310. GMessage(message=_("Both coordinates and categories are set, "
  311. "coordinates will be used. The use categories "
  312. "remove text from coordinate form"))
  313. if not attribute or attribute == '':
  314. GError(parent=self, showTraceback=False,
  315. message=_("With Vector temporal dataset you have to select"
  316. " an attribute column"))
  317. return
  318. columns = ','.join(['name', 'start_time', 'end_time', 'id', 'layer'])
  319. for series in timeseries:
  320. name = series[0]
  321. fullname = name + '@' + series[1]
  322. etype = series[2]
  323. sp = tgis.dataset_factory(etype, fullname)
  324. if not sp.is_in_db(dbif=self.dbif):
  325. GError(message=_("Dataset <%s> not found in temporal "
  326. "database") % (fullname), parent=self,
  327. showTraceback=False)
  328. return
  329. sp.select(dbif=self.dbif)
  330. rows = sp.get_registered_maps(dbif=self.dbif, order="start_time",
  331. columns=columns, where=None)
  332. self.timeDataV[name] = OrderedDict()
  333. self.timeDataV[name]['temporalDataType'] = etype
  334. self.timeDataV[name]['temporalType'] = sp.get_temporal_type()
  335. self.timeDataV[name]['granularity'] = sp.get_granularity()
  336. if mode is None:
  337. mode = self.timeDataV[name]['temporalType']
  338. elif self.timeDataV[name]['temporalType'] != mode:
  339. GError(parent=self, showTraceback=False,
  340. message=_("Datasets have different temporal type ("
  341. "absolute x relative), which is not allowed."))
  342. return
  343. self.timeDataV[name]['unit'] = None # only with relative
  344. if self.timeDataV[name]['temporalType'] == 'relative':
  345. start, end, self.timeDataV[name]['unit'] = sp.get_relative_time()
  346. if unit is None:
  347. unit = self.timeDataV[name]['unit']
  348. elif self.timeDataV[name]['unit'] != unit:
  349. GError(message=_("Datasets have different time unit which"
  350. " is not allowed."), parent=self,
  351. showTraceback=False)
  352. return
  353. if self.poi:
  354. self.plotNameListV.append(name)
  355. # TODO set an appropriate distance, right now a big one is set
  356. # to return the closer point to the selected one
  357. out = grass.vector_what(map='pois_srvds',
  358. coord=self.poi.coords(),
  359. distance=10000000000000000)
  360. if len(out) != len(rows):
  361. GError(parent=self, showTraceback=False,
  362. message=_("Difference number of vector layers and "
  363. "maps in the vector temporal dataset"))
  364. return
  365. for i in range(len(rows)):
  366. row = rows[i]
  367. values = out[i]
  368. if str(row['layer']) == str(values['Layer']):
  369. lay = "{map}_{layer}".format(map=row['name'],
  370. layer=values['Layer'])
  371. self.timeDataV[name][lay] = {}
  372. self.timeDataV[name][lay]['start_datetime'] = row['start_time']
  373. self.timeDataV[name][lay]['end_datetime'] = row['start_time']
  374. self.timeDataV[name][lay]['value'] = values['Attributes'][attribute]
  375. else:
  376. wherequery = ''
  377. totcat = len(cats)
  378. ncat = 1
  379. categories = self._getCategories(row[0]['name'])
  380. for cat in cats:
  381. if ncat == 1 and totcat != 1:
  382. wherequery += '{k}={c} or'.format(c=cat, k="{key}")
  383. elif ncat == 1 and totcat == 1:
  384. wherequery += '{k}={c}'.format(c=cat, k="{key}")
  385. elif ncat == totcat:
  386. wherequery += ' {k}={c}'.format(c=cat, k="{key}")
  387. else:
  388. wherequery += ' {k}={c} or'.format(c=cat, k="{key}")
  389. catn = "cat{num}".format(num=cat)
  390. self.plotNameListV.append("{na}+{cat}".format(na=name,
  391. cat=catn))
  392. self.timeDataV[name][catn] = OrderedDict()
  393. ncat += 1
  394. for row in rows:
  395. lay = int(row['layer'])
  396. catkey = self._parseVDbConn(row['name'], lay)
  397. if not catkey:
  398. GError(parent=self, showTraceback=False,
  399. message=_("No connection between vector map {vmap} "
  400. "and layer {la}".format(vmap=row['name'],
  401. la=lay)))
  402. return
  403. vals = grass.vector_db_select(map=row['name'], layer=lay,
  404. where=wherequery.format(key=catkey),
  405. columns=attribute)
  406. layn = "lay{num}".format(num=lay)
  407. for cat in cats:
  408. catn = "cat{num}".format(num=cat)
  409. if layn not in self.timeDataV[name][catn].keys():
  410. self.timeDataV[name][catn][layn] = {}
  411. self.timeDataV[name][catn][layn]['start_datetime'] = row['start_time']
  412. self.timeDataV[name][catn][layn]['end_datetime'] = row['end_time']
  413. self.timeDataV[name][catn][layn]['value'] = vals['values'][int(cat)]
  414. self.unit = unit
  415. self.temporalType = mode
  416. return
  417. def _drawFigure(self):
  418. """Draws or print 2D plot (temporal extents)"""
  419. self.axes2d.clear()
  420. self.axes2d.grid(False)
  421. if self.temporalType == 'absolute':
  422. self.axes2d.xaxis_date()
  423. self.fig.autofmt_xdate()
  424. self.convert = mdates.date2num
  425. self.invconvert = mdates.num2date
  426. else:
  427. self.convert = lambda x: x
  428. self.invconvert = self.convert
  429. self.colors = cycle(COLORS)
  430. self.yticksNames = []
  431. self.yticksPos = []
  432. self.plots = []
  433. if self.datasetsR:
  434. self.lookUp = LookUp(self.timeDataR, self.invconvert)
  435. else:
  436. self.lookUp = LookUp(self.timeDataV, self.invconvert)
  437. if self.datasetsR:
  438. self.drawR()
  439. if self.datasetsV:
  440. if self.poi:
  441. self.drawV()
  442. elif self.cats:
  443. self.drawVCats()
  444. self.canvas.draw()
  445. DataCursor(self.plots, self.lookUp, InfoFormat, self.convert)
  446. def drawR(self):
  447. for i, name in enumerate(self.datasetsR):
  448. name = name[0]
  449. # just name; with mapset it would be long
  450. self.yticksNames.append(name)
  451. self.yticksPos.append(1) # TODO
  452. xdata = []
  453. ydata = []
  454. for keys, values in self.timeDataR[name].iteritems():
  455. if keys in ['temporalType', 'granularity', 'validTopology',
  456. 'unit', 'temporalDataType']:
  457. continue
  458. xdata.append(self.convert(values['start_datetime']))
  459. ydata.append(values['value'])
  460. self.lookUp.AddDataset(yranges=ydata, xranges=xdata,
  461. datasetName=name)
  462. color = self.colors.next()
  463. self.plots.append(self.axes2d.plot(xdata, ydata, marker='o',
  464. color=color,
  465. label=self.plotNameListR[i])[0])
  466. if self.temporalType == 'absolute':
  467. self.axes2d.set_xlabel(_("Temporal resolution: %s" % self.timeDataR[name]['granularity']))
  468. else:
  469. self.axes2d.set_xlabel(_("Time [%s]") % self.unit)
  470. self.axes2d.set_ylabel(', '.join(self.yticksNames))
  471. # legend
  472. handles, labels = self.axes2d.get_legend_handles_labels()
  473. self.axes2d.legend(loc=0)
  474. def drawVCats(self):
  475. for i, name in enumerate(self.plotNameListV):
  476. # just name; with mapset it would be long
  477. labelname = name.replace('+', ' ')
  478. self.yticksNames.append(labelname)
  479. name_cat = name.split('+')
  480. name = name_cat[0]
  481. self.yticksPos.append(1) # TODO
  482. xdata = []
  483. ydata = []
  484. for keys, values in self.timeDataV[name_cat[0]][name_cat[1]].iteritems():
  485. if keys in ['temporalType', 'granularity', 'validTopology',
  486. 'unit', 'temporalDataType']:
  487. continue
  488. xdata.append(self.convert(values['start_datetime']))
  489. ydata.append(values['value'])
  490. self.lookUp.AddDataset(yranges=ydata, xranges=xdata,
  491. datasetName=name)
  492. color = self.colors.next()
  493. self.plots.append(self.axes2d.plot(xdata, ydata, marker='o',
  494. color=color, label=labelname)[0])
  495. # ============================
  496. if self.temporalType == 'absolute':
  497. self.axes2d.set_xlabel(_("Temporal resolution: %s" % self.timeDataV[name]['granularity']))
  498. else:
  499. self.axes2d.set_xlabel(_("Time [%s]") % self.unit)
  500. self.axes2d.set_ylabel(', '.join(self.yticksNames))
  501. # legend
  502. handles, labels = self.axes2d.get_legend_handles_labels()
  503. self.axes2d.legend(loc=0)
  504. self.listWhereConditions = []
  505. def drawV(self):
  506. for i, name in enumerate(self.plotNameListV):
  507. # just name; with mapset it would be long
  508. self.yticksNames.append(self.attribute.GetValue())
  509. self.yticksPos.append(0) # TODO
  510. xdata = []
  511. ydata = []
  512. for keys, values in self.timeDataV[name].iteritems():
  513. if keys in ['temporalType', 'granularity', 'validTopology',
  514. 'unit', 'temporalDataType']:
  515. continue
  516. xdata.append(self.convert(values['start_datetime']))
  517. ydata.append(values['value'])
  518. self.lookUp.AddDataset(yranges=ydata, xranges=xdata,
  519. datasetName=name)
  520. color = self.colors.next()
  521. self.plots.append(self.axes2d.plot(xdata, ydata, marker='o',
  522. color=color, label=name)[0])
  523. # ============================
  524. if self.temporalType == 'absolute':
  525. self.axes2d.set_xlabel(_("Temporal resolution: %s" % self.timeDataV[name]['granularity']))
  526. else:
  527. self.axes2d.set_xlabel(_("Time [%s]") % self.unit)
  528. self.axes2d.set_ylabel(', '.join(self.yticksNames))
  529. # legend
  530. handles, labels = self.axes2d.get_legend_handles_labels()
  531. self.axes2d.legend(loc=0)
  532. self.listWhereConditions = []
  533. def OnRedraw(self, event=None):
  534. """Required redrawing."""
  535. self.init()
  536. datasetsR = self.datasetSelectR.GetValue().strip()
  537. datasetsV = self.datasetSelectV.GetValue().strip()
  538. if not datasetsR and not datasetsV:
  539. return
  540. try:
  541. getcoors = self.coorval.coordsField.GetValue()
  542. except:
  543. try:
  544. getcoors = self.coorval.GetValue()
  545. except:
  546. getcoors = None
  547. if getcoors and getcoors != '':
  548. try:
  549. coordx, coordy = getcoors.split(',')
  550. coordx, coordy = float(coordx), float(coordy)
  551. except (ValueError, AttributeError):
  552. try:
  553. coordx, coordy = self.coorval.GetValue().split(',')
  554. coordx, coordy = float(coordx), float(coordy)
  555. except (ValueError, AttributeError):
  556. GMessage(message=_("Incorrect coordinates format, should "
  557. "be: x,y"), parent=self)
  558. coors = [coordx, coordy]
  559. if coors:
  560. try:
  561. self.poi = Point(float(coors[0]), float(coors[1]))
  562. except GException:
  563. GError(parent=self, message=_("Invalid input coordinates"),
  564. showTraceback=False)
  565. return
  566. # check raster dataset
  567. if datasetsR:
  568. datasetsR = datasetsR.split(',')
  569. try:
  570. datasetsR = self._checkDatasets(datasetsR, 'strds')
  571. if not datasetsR:
  572. return
  573. except GException:
  574. GError(parent=self, message=_("Invalid input raster dataset"),
  575. showTraceback=False)
  576. return
  577. self.datasetsR = datasetsR
  578. # check vector dataset
  579. if datasetsV:
  580. datasetsV = datasetsV.split(',')
  581. try:
  582. datasetsV = self._checkDatasets(datasetsV, 'stvds')
  583. if not datasetsV:
  584. return
  585. except GException:
  586. GError(parent=self, message=_("Invalid input vector dataset"),
  587. showTraceback=False)
  588. return
  589. self.datasetsV = datasetsV
  590. self._redraw()
  591. def _redraw(self):
  592. """Readraw data.
  593. Decides if to draw also 3D and adjusts layout if needed.
  594. """
  595. if self.datasetsR:
  596. self._getSTRDdata(self.datasetsR)
  597. if self.datasetsV:
  598. self._getSTVDData(self.datasetsV)
  599. # axes3d are physically removed
  600. if not self.axes2d:
  601. self.axes2d = self.fig.add_subplot(1, 1, 1)
  602. self._drawFigure()
  603. def _checkDatasets(self, datasets, typ):
  604. """Checks and validates datasets.
  605. Reports also type of dataset (e.g. 'strds').
  606. :param list datasets: list of temporal dataset's name
  607. :return: (mapName, mapset, type)
  608. """
  609. validated = []
  610. tDict = tgis.tlist_grouped(type=typ, group_type=True, dbif=self.dbif)
  611. # nested list with '(map, mapset, etype)' items
  612. allDatasets = [[[(map, mapset, etype) for map in maps]
  613. for etype, maps in etypesDict.iteritems()]
  614. for mapset, etypesDict in tDict.iteritems()]
  615. # flatten this list
  616. if allDatasets:
  617. allDatasets = reduce(lambda x, y: x + y, reduce(lambda x, y: x + y,
  618. allDatasets))
  619. mapsets = tgis.get_tgis_c_library_interface().available_mapsets()
  620. allDatasets = [i for i in sorted(allDatasets,
  621. key=lambda l: mapsets.index(l[1]))]
  622. for dataset in datasets:
  623. errorMsg = _("Space time dataset <%s> not found.") % dataset
  624. if dataset.find("@") >= 0:
  625. nameShort, mapset = dataset.split('@', 1)
  626. indices = [n for n, (mapName, mapsetName, etype) in enumerate(allDatasets)
  627. if nameShort == mapName and mapsetName == mapset]
  628. else:
  629. indices = [n for n, (mapName, mapset, etype) in enumerate(allDatasets)
  630. if dataset == mapName]
  631. if len(indices) == 0:
  632. raise GException(errorMsg)
  633. elif len(indices) >= 2:
  634. dlg = wx.SingleChoiceDialog(self,
  635. message=_("Please specify the "
  636. "space time dataset "
  637. "<%s>." % dataset),
  638. caption=_("Ambiguous dataset name"),
  639. choices=[("%(map)s@%(mapset)s:"
  640. " %(etype)s" % {'map': allDatasets[i][0],
  641. 'mapset': allDatasets[i][1],
  642. 'etype': allDatasets[i][2]})
  643. for i in indices],
  644. style=wx.CHOICEDLG_STYLE | wx.OK)
  645. if dlg.ShowModal() == wx.ID_OK:
  646. index = dlg.GetSelection()
  647. validated.append(allDatasets[indices[index]])
  648. else:
  649. continue
  650. else:
  651. validated.append(allDatasets[indices[0]])
  652. return validated
  653. def OnHelp(self, event):
  654. """Function to show help"""
  655. RunCommand(prog='g.manual', quiet=True, entry='g.gui.tplot')
  656. def SetDatasets(self, rasters, vectors, coors, cats, attr):
  657. """Set the data
  658. #TODO
  659. :param list rasters: a list of temporal raster dataset's name
  660. :param list vectors: a list of temporal vector dataset's name
  661. :param list coors: a list with x/y coordinates
  662. :param list cats: a list with incld. categories of vector
  663. :param str attr: name of atribute of vectror data
  664. """
  665. if not (rasters or vectors) or not (coors or cats):
  666. return
  667. try:
  668. if rasters:
  669. self.datasetsR = self._checkDatasets(rasters, 'strds')
  670. if vectors:
  671. self.datasetsV = self._checkDatasets(vectors, 'stvds')
  672. if not (self.datasetsR or self.datasetsV):
  673. return
  674. except GException:
  675. GError(parent=self, message=_("Invalid input temporal dataset"),
  676. showTraceback=False)
  677. return
  678. if coors:
  679. try:
  680. self.poi = Point(float(coors[0]), float(coors[1]))
  681. except GException:
  682. GError(parent=self, message=_("Invalid input coordinates"),
  683. showTraceback=False)
  684. return
  685. try:
  686. self.coorval.coordsField.SetValue(','.join(coors))
  687. except:
  688. self.coorval.SetValue(','.join(coors))
  689. if self.datasetsV:
  690. vdatas = ','.join(map(lambda x: x[0] + '@' + x[1], self.datasetsV))
  691. self.datasetSelectV.SetValue(vdatas)
  692. if attr:
  693. self.attribute.SetValue(attr)
  694. if cats:
  695. self.cats.SetValue(cats)
  696. if self.datasetsR:
  697. self.datasetSelectR.SetValue(','.join(map(lambda x: x[0] + '@' + x[1],
  698. self.datasetsR)))
  699. self._redraw()
  700. def OnVectorSelected(self, event):
  701. """Update the controlbox related to stvds"""
  702. dataset = self.datasetSelectV.GetValue().strip()
  703. vect_list = grass.read_command('t.vect.list', flags='s', input=dataset,
  704. col='name')
  705. vect_list = list(set(sorted(vect_list.split())))
  706. for vec in vect_list:
  707. self.attribute.InsertColumns(vec, 1)
  708. class LookUp:
  709. """Helper class for searching info by coordinates"""
  710. def __init__(self, timeData, convert):
  711. self.data = {}
  712. self.timeData = timeData
  713. self.convert = convert
  714. def AddDataset(self, yranges, xranges, datasetName):
  715. if len(yranges) != len(xranges):
  716. GError(parent=self, showTraceback=False,
  717. message=_("Datasets have different number of values"))
  718. return
  719. self.data[datasetName] = {}
  720. for i in range(len(xranges)):
  721. self.data[datasetName][xranges[i]] = yranges[i]
  722. def GetInformation(self, x):
  723. values = {}
  724. for key, value in self.data.iteritems():
  725. if value[x]:
  726. values[key] = [self.convert(x), value[x]]
  727. if len(values) == 0:
  728. return None
  729. return self.timeData, values
  730. def InfoFormat(timeData, values):
  731. """Formats information about dataset"""
  732. text = []
  733. for key, val in values.iteritems():
  734. etype = timeData[key]['temporalDataType']
  735. if etype == 'strds':
  736. text.append(_("Space time raster dataset: %s") % key)
  737. elif etype == 'stvds':
  738. text.append(_("Space time vector dataset: %s") % key)
  739. elif etype == 'str3ds':
  740. text.append(_("Space time 3D raster dataset: %s") % key)
  741. text.append(_("Value for {date} is {val}".format(date=val[0],
  742. val=val[1])))
  743. text.append('\n')
  744. text.append(_("Press Del to dismiss."))
  745. return '\n'.join(text)
  746. class DataCursor(object):
  747. """A simple data cursor widget that displays the x,y location of a
  748. matplotlib artist when it is selected.
  749. Source: http://stackoverflow.com/questions/4652439/
  750. is-there-a-matplotlib-equivalent-of-matlabs-datacursormode/4674445
  751. """
  752. def __init__(self, artists, lookUp, formatFunction, convert,
  753. tolerance=5, offsets=(-30, 20), display_all=False):
  754. """Create the data cursor and connect it to the relevant figure.
  755. "artists" is the matplotlib artist or sequence of artists that will be
  756. selected.
  757. "tolerance" is the radius (in points) that the mouse click must be
  758. within to select the artist.
  759. "offsets" is a tuple of (x,y) offsets in points from the selected
  760. point to the displayed annotation box
  761. "display_all" controls whether more than one annotation box will
  762. be shown if there are multiple axes. Only one will be shown
  763. per-axis, regardless.
  764. """
  765. self.lookUp = lookUp
  766. self.formatFunction = formatFunction
  767. self.offsets = offsets
  768. self.display_all = display_all
  769. if not cbook.iterable(artists):
  770. artists = [artists]
  771. self.artists = artists
  772. self.convert = convert
  773. self.axes = tuple(set(art.axes for art in self.artists))
  774. self.figures = tuple(set(ax.figure for ax in self.axes))
  775. self.annotations = {}
  776. for ax in self.axes:
  777. self.annotations[ax] = self.annotate(ax)
  778. for artist in self.artists:
  779. artist.set_picker(tolerance)
  780. for fig in self.figures:
  781. fig.canvas.mpl_connect('pick_event', self)
  782. fig.canvas.mpl_connect('key_press_event', self.keyPressed)
  783. def keyPressed(self, event):
  784. """Key pressed - hide annotation if Delete was pressed"""
  785. if event.key != 'delete':
  786. return
  787. for ax in self.axes:
  788. self.annotations[ax].set_visible(False)
  789. event.canvas.draw()
  790. def annotate(self, ax):
  791. """Draws and hides the annotation box for the given axis "ax"."""
  792. annotation = ax.annotate(self.formatFunction, xy=(0, 0), ha='center',
  793. xytext=self.offsets, va='bottom',
  794. textcoords='offset points',
  795. bbox=dict(boxstyle='round,pad=0.5',
  796. fc='yellow', alpha=0.7),
  797. arrowprops=dict(arrowstyle='->',
  798. connectionstyle='arc3,rad=0'),
  799. annotation_clip=False, multialignment='left')
  800. annotation.set_visible(False)
  801. return annotation
  802. def __call__(self, event):
  803. """Intended to be called through "mpl_connect"."""
  804. # Rather than trying to interpolate, just display the clicked coords
  805. # This will only be called if it's within "tolerance", anyway.
  806. x, y = event.mouseevent.xdata, event.mouseevent.ydata
  807. annotation = self.annotations[event.artist.axes]
  808. if x is not None:
  809. if not self.display_all:
  810. # Hide any other annotation boxes...
  811. for ann in self.annotations.values():
  812. ann.set_visible(False)
  813. if 'Line2D' in str(type(event.artist)):
  814. xData = []
  815. for a in event.artist.get_xdata():
  816. try:
  817. d = self.convert(a)
  818. except:
  819. d = a
  820. xData.append(d)
  821. x = xData[np.argmin(abs(xData - x))]
  822. info = self.lookUp.GetInformation(x)
  823. ys = zip(*info[1].values())[1]
  824. if not info:
  825. return
  826. # Update the annotation in the current axis..
  827. annotation.xy = x, max(ys)
  828. text = self.formatFunction(*info)
  829. annotation.set_text(text)
  830. annotation.set_visible(True)
  831. event.canvas.draw()