frame.py 44 KB

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