frame.py 37 KB

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