frame.py 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487
  1. #!/usr/bin/env python3
  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. import os
  16. import six
  17. from itertools import cycle
  18. import numpy as np
  19. import wx
  20. from grass.pygrass.modules import Module
  21. import grass.script as grass
  22. from functools import reduce
  23. try:
  24. import matplotlib
  25. # The recommended way to use wx with mpl is with the WXAgg
  26. # backend.
  27. matplotlib.use("WXAgg")
  28. from matplotlib.figure import Figure
  29. from matplotlib.backends.backend_wxagg import (
  30. FigureCanvasWxAgg as FigCanvas,
  31. NavigationToolbar2WxAgg as NavigationToolbar,
  32. )
  33. import matplotlib.dates as mdates
  34. except ImportError as e:
  35. raise ImportError(
  36. _(
  37. 'The Temporal Plot Tool needs the "matplotlib" '
  38. "(python-matplotlib) package to be installed. {0}"
  39. ).format(e)
  40. )
  41. import grass.temporal as tgis
  42. from core.gcmd import GMessage, GError, GException, RunCommand
  43. from gui_core.widgets import CoordinatesValidator
  44. from gui_core import gselect
  45. from core import globalvar
  46. from grass.pygrass.vector.geometry import Point
  47. from grass.pygrass.raster import RasterRow
  48. from grass.pygrass.gis.region import Region
  49. from collections import OrderedDict
  50. from subprocess import PIPE
  51. try:
  52. import wx.lib.agw.flatnotebook as FN
  53. except ImportError:
  54. import wx.lib.flatnotebook as FN
  55. import wx.lib.filebrowsebutton as filebrowse
  56. from gui_core.widgets import GNotebook
  57. from gui_core.wrap import CheckBox, TextCtrl, Button, StaticText
  58. ALPHA = 0.5
  59. COLORS = ["b", "g", "r", "c", "m", "y", "k"]
  60. LINEAR_REG_LINE_COLOR = (0.56, 0.00, 1.00)
  61. def check_version(*version):
  62. """Checks if given version or newer is installed"""
  63. versionInstalled = []
  64. for i in matplotlib.__version__.split("."):
  65. try:
  66. v = int(i)
  67. versionInstalled.append(v)
  68. except ValueError:
  69. versionInstalled.append(0)
  70. if versionInstalled < list(version):
  71. return False
  72. else:
  73. return True
  74. def findBetween(s, first, last):
  75. try:
  76. start = s.rindex(first) + len(first)
  77. end = s.rindex(last, start)
  78. return s[start:end]
  79. except ValueError:
  80. return ""
  81. class TplotFrame(wx.Frame):
  82. """The main frame of the application"""
  83. def __init__(self, parent, giface, title=_("Temporal Plot Tool")):
  84. wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=title)
  85. tgis.init(True)
  86. self._giface = giface
  87. self.datasetsV = None
  88. self.datasetsR = None
  89. self.overwrite = False
  90. # self.vectorDraw=False
  91. # self.rasterDraw=False
  92. self.init()
  93. self._layout()
  94. # We create a database interface here to speedup the GUI
  95. self.dbif = tgis.SQLDatabaseInterfaceConnection()
  96. self.dbif.connect()
  97. self.Bind(wx.EVT_CLOSE, self.onClose)
  98. self.region = Region()
  99. def init(self):
  100. self.timeDataR = OrderedDict()
  101. self.timeDataV = OrderedDict()
  102. self.temporalType = None
  103. self.unit = None
  104. self.listWhereConditions = []
  105. self.plotNameListR = []
  106. self.plotNameListV = []
  107. self.poi = None
  108. self.csvpath = None
  109. def __del__(self):
  110. """Close the database interface and stop the messenger and C-interface
  111. subprocesses.
  112. """
  113. if self.dbif.connected is True:
  114. self.dbif.close()
  115. tgis.stop_subprocesses()
  116. def onClose(self, evt):
  117. if self._giface.GetMapDisplay():
  118. self.coorval.OnClose()
  119. self.cats.OnClose()
  120. self.__del__()
  121. self.Destroy()
  122. def _layout(self):
  123. """Creates the main panel with all the controls on it:
  124. * mpl canvas
  125. * mpl navigation toolbar
  126. * Control panel for interaction
  127. """
  128. self.mainPanel = wx.Panel(self)
  129. # Create the mpl Figure and FigCanvas objects.
  130. # 5x4 inches, 100 dots-per-inch
  131. #
  132. # color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
  133. # ------------CANVAS AND TOOLBAR------------
  134. self.fig = Figure((5.0, 4.0), facecolor=(1, 1, 1))
  135. self.canvas = FigCanvas(self.mainPanel, wx.ID_ANY, self.fig)
  136. # axes are initialized later
  137. self.axes2d = None
  138. # Create the navigation toolbar, tied to the canvas
  139. #
  140. self.toolbar = NavigationToolbar(self.canvas)
  141. #
  142. # Layout
  143. #
  144. # ------------MAIN VERTICAL SIZER------------
  145. self.vbox = wx.BoxSizer(wx.VERTICAL)
  146. self.vbox.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.EXPAND)
  147. self.vbox.Add(self.toolbar, 0, wx.EXPAND)
  148. # self.vbox.AddSpacer(10)
  149. # ------------ADD NOTEBOOK------------
  150. self.ntb = GNotebook(
  151. parent=self.mainPanel, style=FN.FNB_FANCY_TABS | FN.FNB_NODRAG
  152. )
  153. # ------------ITEMS IN NOTEBOOK PAGE (RASTER)------------------------
  154. self.controlPanelRaster = wx.Panel(parent=self.ntb, id=wx.ID_ANY)
  155. self.datasetSelectLabelR = StaticText(
  156. parent=self.controlPanelRaster,
  157. id=wx.ID_ANY,
  158. label=_(
  159. "Raster temporal "
  160. "dataset (strds)\n"
  161. "Press ENTER after"
  162. " typing the name or select"
  163. " with the combobox"
  164. ),
  165. )
  166. self.datasetSelectR = gselect.Select(
  167. parent=self.controlPanelRaster,
  168. id=wx.ID_ANY,
  169. size=globalvar.DIALOG_GSELECT_SIZE,
  170. type="strds",
  171. multiple=True,
  172. )
  173. self.coor = StaticText(
  174. parent=self.controlPanelRaster,
  175. id=wx.ID_ANY,
  176. label=_("X and Y coordinates separated by " "comma:"),
  177. )
  178. try:
  179. self._giface.GetMapWindow()
  180. self.coorval = gselect.CoordinatesSelect(
  181. parent=self.controlPanelRaster, giface=self._giface
  182. )
  183. except:
  184. self.coorval = TextCtrl(
  185. parent=self.controlPanelRaster,
  186. id=wx.ID_ANY,
  187. size=globalvar.DIALOG_TEXTCTRL_SIZE,
  188. validator=CoordinatesValidator(),
  189. )
  190. self.coorval.SetToolTip(
  191. _(
  192. "Coordinates can be obtained for example"
  193. " by right-clicking on Map Display."
  194. )
  195. )
  196. self.linRegRaster = CheckBox(
  197. parent=self.controlPanelRaster,
  198. id=wx.ID_ANY,
  199. label=_("Show simple linear regression line"),
  200. )
  201. self.controlPanelSizerRaster = wx.BoxSizer(wx.VERTICAL)
  202. # self.controlPanelSizer.Add(wx.StaticText(self.panel, id=wx.ID_ANY,
  203. # label=_("Select space time raster dataset(s):")),
  204. # pos=(0, 0), flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
  205. self.controlPanelSizerRaster.Add(self.datasetSelectLabelR, flag=wx.EXPAND)
  206. self.controlPanelSizerRaster.Add(self.datasetSelectR, flag=wx.EXPAND)
  207. self.controlPanelSizerRaster.Add(self.coor, flag=wx.EXPAND)
  208. self.controlPanelSizerRaster.Add(self.coorval, flag=wx.EXPAND)
  209. self.controlPanelSizerRaster.Add(self.linRegRaster, flag=wx.EXPAND)
  210. self.controlPanelRaster.SetSizer(self.controlPanelSizerRaster)
  211. self.controlPanelSizerRaster.Fit(self)
  212. self.ntb.AddPage(page=self.controlPanelRaster, text=_("STRDS"), name="STRDS")
  213. # ------------ITEMS IN NOTEBOOK PAGE (VECTOR)------------------------
  214. self.controlPanelVector = wx.Panel(parent=self.ntb, id=wx.ID_ANY)
  215. self.datasetSelectLabelV = StaticText(
  216. parent=self.controlPanelVector,
  217. id=wx.ID_ANY,
  218. label=_(
  219. "Vector temporal "
  220. "dataset (stvds)\n"
  221. "Press ENTER after"
  222. " typing the name or select"
  223. " with the combobox"
  224. ),
  225. )
  226. self.datasetSelectV = gselect.Select(
  227. parent=self.controlPanelVector,
  228. id=wx.ID_ANY,
  229. size=globalvar.DIALOG_GSELECT_SIZE,
  230. type="stvds",
  231. multiple=True,
  232. )
  233. self.datasetSelectV.Bind(wx.EVT_TEXT, self.OnVectorSelected)
  234. self.attribute = gselect.ColumnSelect(parent=self.controlPanelVector)
  235. self.attributeLabel = StaticText(
  236. parent=self.controlPanelVector,
  237. id=wx.ID_ANY,
  238. label=_("Select attribute column"),
  239. )
  240. # TODO fix the category selection as done for coordinates
  241. try:
  242. self._giface.GetMapWindow()
  243. self.cats = gselect.VectorCategorySelect(
  244. parent=self.controlPanelVector, giface=self._giface
  245. )
  246. except:
  247. self.cats = TextCtrl(
  248. parent=self.controlPanelVector,
  249. id=wx.ID_ANY,
  250. size=globalvar.DIALOG_TEXTCTRL_SIZE,
  251. )
  252. self.catsLabel = StaticText(
  253. parent=self.controlPanelVector,
  254. id=wx.ID_ANY,
  255. label=_("Select category of vector(s)"),
  256. )
  257. self.linRegVector = CheckBox(
  258. parent=self.controlPanelVector,
  259. id=wx.ID_ANY,
  260. label=_("Show simple linear regression line"),
  261. )
  262. self.controlPanelSizerVector = wx.BoxSizer(wx.VERTICAL)
  263. # self.controlPanelSizer.Add(wx.StaticText(self.panel, id=wx.ID_ANY,
  264. # label=_("Select space time raster dataset(s):")),
  265. # pos=(0, 0), flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
  266. self.controlPanelSizerVector.Add(self.datasetSelectLabelV, flag=wx.EXPAND)
  267. self.controlPanelSizerVector.Add(self.datasetSelectV, flag=wx.EXPAND)
  268. self.controlPanelSizerVector.Add(self.attributeLabel, flag=wx.EXPAND)
  269. self.controlPanelSizerVector.Add(self.attribute, flag=wx.EXPAND)
  270. self.controlPanelSizerVector.Add(self.catsLabel, flag=wx.EXPAND)
  271. self.controlPanelSizerVector.Add(self.cats, flag=wx.EXPAND)
  272. self.controlPanelSizerVector.Add(self.linRegVector, flag=wx.EXPAND)
  273. self.controlPanelVector.SetSizer(self.controlPanelSizerVector)
  274. self.controlPanelSizerVector.Fit(self)
  275. self.ntb.AddPage(page=self.controlPanelVector, text=_("STVDS"), name="STVDS")
  276. # ------------ITEMS IN NOTEBOOK PAGE (LABELS)------------------------
  277. self.controlPanelLabels = wx.Panel(parent=self.ntb, id=wx.ID_ANY)
  278. self.titleLabel = StaticText(
  279. parent=self.controlPanelLabels,
  280. id=wx.ID_ANY,
  281. label=_("Set title for the plot"),
  282. )
  283. self.title = TextCtrl(
  284. parent=self.controlPanelLabels,
  285. id=wx.ID_ANY,
  286. size=globalvar.DIALOG_TEXTCTRL_SIZE,
  287. )
  288. self.xLabel = StaticText(
  289. parent=self.controlPanelLabels,
  290. id=wx.ID_ANY,
  291. label=_("Set label for X axis"),
  292. )
  293. self.x = TextCtrl(
  294. parent=self.controlPanelLabels,
  295. id=wx.ID_ANY,
  296. size=globalvar.DIALOG_TEXTCTRL_SIZE,
  297. )
  298. self.yLabel = StaticText(
  299. parent=self.controlPanelLabels,
  300. id=wx.ID_ANY,
  301. label=_("Set label for Y axis"),
  302. )
  303. self.y = TextCtrl(
  304. parent=self.controlPanelLabels,
  305. id=wx.ID_ANY,
  306. size=globalvar.DIALOG_TEXTCTRL_SIZE,
  307. )
  308. self.controlPanelSizerLabels = wx.BoxSizer(wx.VERTICAL)
  309. self.controlPanelSizerLabels.Add(self.titleLabel, flag=wx.EXPAND)
  310. self.controlPanelSizerLabels.Add(self.title, flag=wx.EXPAND)
  311. self.controlPanelSizerLabels.Add(self.xLabel, flag=wx.EXPAND)
  312. self.controlPanelSizerLabels.Add(self.x, flag=wx.EXPAND)
  313. self.controlPanelSizerLabels.Add(self.yLabel, flag=wx.EXPAND)
  314. self.controlPanelSizerLabels.Add(self.y, flag=wx.EXPAND)
  315. self.controlPanelLabels.SetSizer(self.controlPanelSizerLabels)
  316. self.controlPanelSizerLabels.Fit(self)
  317. self.ntb.AddPage(page=self.controlPanelLabels, text=_("Labels"), name="Labels")
  318. # ------------ITEMS IN NOTEBOOK PAGE (EXPORT)------------------------
  319. self.controlPanelExport = wx.Panel(parent=self.ntb, id=wx.ID_ANY)
  320. self.csvLabel = StaticText(
  321. parent=self.controlPanelExport,
  322. id=wx.ID_ANY,
  323. label=_("Path for output CSV file " "with plotted data"),
  324. )
  325. self.csvButton = filebrowse.FileBrowseButton(
  326. parent=self.controlPanelExport,
  327. id=wx.ID_ANY,
  328. size=globalvar.DIALOG_GSELECT_SIZE,
  329. labelText="",
  330. dialogTitle=_("CVS path"),
  331. buttonText=_("Browse"),
  332. startDirectory=os.getcwd(),
  333. fileMode=wx.FD_SAVE,
  334. )
  335. self.headerLabel = StaticText(
  336. parent=self.controlPanelExport,
  337. id=wx.ID_ANY,
  338. label=_("Do you want the CSV header?"),
  339. )
  340. self.headerCheck = wx.CheckBox(parent=self.controlPanelExport, id=wx.ID_ANY)
  341. self.controlPanelSizerCheck = wx.BoxSizer(wx.HORIZONTAL)
  342. self.controlPanelSizerCheck.Add(self.headerCheck)
  343. self.controlPanelSizerCheck.Add(self.headerLabel)
  344. self.controlPanelSizerExport = wx.BoxSizer(wx.VERTICAL)
  345. self.controlPanelSizerExport.Add(self.csvLabel)
  346. self.controlPanelSizerExport.Add(self.csvButton)
  347. self.controlPanelSizerExport.Add(self.controlPanelSizerCheck)
  348. self.controlPanelExport.SetSizer(self.controlPanelSizerExport)
  349. self.controlPanelSizerCheck.Fit(self)
  350. self.controlPanelSizerExport.Fit(self)
  351. self.ntb.AddPage(page=self.controlPanelExport, text=_("Export"), name="Export")
  352. # ------------Buttons on the bottom(draw,help)------------
  353. self.vButtPanel = wx.Panel(self.mainPanel, id=wx.ID_ANY)
  354. self.vButtSizer = wx.BoxSizer(wx.HORIZONTAL)
  355. self.drawButton = Button(self.vButtPanel, id=wx.ID_ANY, label=_("Draw"))
  356. self.drawButton.Bind(wx.EVT_BUTTON, self.OnRedraw)
  357. self.helpButton = Button(self.vButtPanel, id=wx.ID_ANY, label=_("Help"))
  358. self.helpButton.Bind(wx.EVT_BUTTON, self.OnHelp)
  359. self.vButtSizer.Add(self.drawButton)
  360. self.vButtSizer.Add(self.helpButton)
  361. self.vButtPanel.SetSizer(self.vButtSizer)
  362. self.mainPanel.SetSizer(self.vbox)
  363. self.vbox.Add(self.ntb, flag=wx.EXPAND)
  364. self.vbox.Add(self.vButtPanel, flag=wx.EXPAND)
  365. self.vbox.Fit(self)
  366. self.mainPanel.Fit()
  367. def _getSTRDdata(self, timeseries):
  368. """Load data and read properties
  369. :param list timeseries: a list of timeseries
  370. """
  371. if not self.poi:
  372. GError(
  373. parent=self, message=_("Invalid input coordinates"), showTraceback=False
  374. )
  375. return
  376. mode = None
  377. unit = None
  378. columns = ",".join(["name", "start_time", "end_time"])
  379. for series in timeseries:
  380. name = series[0]
  381. fullname = name + "@" + series[1]
  382. etype = series[2]
  383. sp = tgis.dataset_factory(etype, fullname)
  384. if not sp.is_in_db(dbif=self.dbif):
  385. GError(
  386. message=_("Dataset <%s> not found in temporal " "database")
  387. % (fullname),
  388. parent=self,
  389. )
  390. return
  391. sp.select(dbif=self.dbif)
  392. minmin = sp.metadata.get_min_min()
  393. self.plotNameListR.append(name)
  394. self.timeDataR[name] = OrderedDict()
  395. self.timeDataR[name]["temporalDataType"] = etype
  396. self.timeDataR[name]["temporalType"] = sp.get_temporal_type()
  397. self.timeDataR[name]["granularity"] = sp.get_granularity()
  398. if mode is None:
  399. mode = self.timeDataR[name]["temporalType"]
  400. elif self.timeDataR[name]["temporalType"] != mode:
  401. GError(
  402. parent=self,
  403. message=_(
  404. "Datasets have different temporal"
  405. " type (absolute x relative), "
  406. "which is not allowed."
  407. ),
  408. )
  409. return
  410. # check topology
  411. maps = sp.get_registered_maps_as_objects(dbif=self.dbif)
  412. self.timeDataR[name]["validTopology"] = sp.check_temporal_topology(
  413. maps=maps, dbif=self.dbif
  414. )
  415. self.timeDataR[name]["unit"] = None # only with relative
  416. if self.timeDataR[name]["temporalType"] == "relative":
  417. start, end, self.timeDataR[name]["unit"] = sp.get_relative_time()
  418. if unit is None:
  419. unit = self.timeDataR[name]["unit"]
  420. elif self.timeDataR[name]["unit"] != unit:
  421. GError(
  422. parent=self,
  423. message=_(
  424. "Datasets have different "
  425. "time unit which is not "
  426. "allowed."
  427. ),
  428. )
  429. return
  430. rows = sp.get_registered_maps(
  431. columns=columns, where=None, order="start_time", dbif=self.dbif
  432. )
  433. for row in rows:
  434. self.timeDataR[name][row[0]] = {}
  435. self.timeDataR[name][row[0]]["start_datetime"] = row[1]
  436. self.timeDataR[name][row[0]]["end_datetime"] = row[2]
  437. r = RasterRow(row[0])
  438. r.open()
  439. val = r.get_value(self.poi)
  440. r.close()
  441. if val == -2147483648 and val < minmin:
  442. self.timeDataR[name][row[0]]["value"] = None
  443. else:
  444. self.timeDataR[name][row[0]]["value"] = val
  445. self.unit = unit
  446. self.temporalType = mode
  447. return
  448. def _parseVDbConn(self, mapp, layerInp):
  449. """find attribute key according to layer of input map"""
  450. vdb = Module("v.db.connect", map=mapp, flags="g", stdout_=PIPE)
  451. vdb = vdb.outputs.stdout
  452. for line in vdb.splitlines():
  453. lsplit = line.split("|")
  454. layer = lsplit[0].split("/")[0]
  455. if str(layer) == str(layerInp):
  456. return lsplit[2]
  457. return None
  458. def _getExistingCategories(self, mapp, cats):
  459. """Get a list of categories for a vector map"""
  460. vdb = grass.read_command("v.category", input=mapp, option="print")
  461. categories = vdb.splitlines()
  462. if not cats:
  463. return categories
  464. for cat in cats:
  465. if str(cat) not in categories:
  466. GMessage(
  467. message=_(
  468. "Category {ca} is not on vector map"
  469. " {ma} and it will be not used"
  470. ).format(ma=mapp, ca=cat),
  471. parent=self,
  472. )
  473. cats.remove(cat)
  474. return cats
  475. def _getSTVDData(self, timeseries):
  476. """Load data and read properties
  477. :param list timeseries: a list of timeseries
  478. """
  479. mode = None
  480. unit = None
  481. cats = None
  482. attribute = self.attribute.GetValue()
  483. if self.cats.GetValue() != "":
  484. cats = self.cats.GetValue().split(",")
  485. if cats and self.poi:
  486. GMessage(
  487. message=_(
  488. "Both coordinates and categories are set, "
  489. "coordinates will be used. The use categories "
  490. "remove text from coordinate form"
  491. )
  492. )
  493. if not attribute or attribute == "":
  494. GError(
  495. parent=self,
  496. showTraceback=False,
  497. message=_(
  498. "With Vector temporal dataset you have to select"
  499. " an attribute column"
  500. ),
  501. )
  502. return
  503. columns = ",".join(["name", "start_time", "end_time", "id", "layer"])
  504. for series in timeseries:
  505. name = series[0]
  506. fullname = name + "@" + series[1]
  507. etype = series[2]
  508. sp = tgis.dataset_factory(etype, fullname)
  509. if not sp.is_in_db(dbif=self.dbif):
  510. GError(
  511. message=_("Dataset <%s> not found in temporal " "database")
  512. % (fullname),
  513. parent=self,
  514. showTraceback=False,
  515. )
  516. return
  517. sp.select(dbif=self.dbif)
  518. rows = sp.get_registered_maps(
  519. dbif=self.dbif, order="start_time", columns=columns, where=None
  520. )
  521. self.timeDataV[name] = OrderedDict()
  522. self.timeDataV[name]["temporalDataType"] = etype
  523. self.timeDataV[name]["temporalType"] = sp.get_temporal_type()
  524. self.timeDataV[name]["granularity"] = sp.get_granularity()
  525. if mode is None:
  526. mode = self.timeDataV[name]["temporalType"]
  527. elif self.timeDataV[name]["temporalType"] != mode:
  528. GError(
  529. parent=self,
  530. showTraceback=False,
  531. message=_(
  532. "Datasets have different temporal type ("
  533. "absolute x relative), which is not allowed."
  534. ),
  535. )
  536. return
  537. self.timeDataV[name]["unit"] = None # only with relative
  538. if self.timeDataV[name]["temporalType"] == "relative":
  539. start, end, self.timeDataV[name]["unit"] = sp.get_relative_time()
  540. if unit is None:
  541. unit = self.timeDataV[name]["unit"]
  542. elif self.timeDataV[name]["unit"] != unit:
  543. GError(
  544. message=_(
  545. "Datasets have different time unit which" " is not allowed."
  546. ),
  547. parent=self,
  548. showTraceback=False,
  549. )
  550. return
  551. if self.poi:
  552. self.plotNameListV.append(name)
  553. # TODO set an appropriate distance, right now a big one is set
  554. # to return the closer point to the selected one
  555. out = grass.vector_what(
  556. map="pois_srvds",
  557. coord=self.poi.coords(),
  558. distance=10000000000000000,
  559. )
  560. if len(out) != len(rows):
  561. GError(
  562. parent=self,
  563. showTraceback=False,
  564. message=_(
  565. "Difference number of vector layers and "
  566. "maps in the vector temporal dataset"
  567. ),
  568. )
  569. return
  570. for i in range(len(rows)):
  571. row = rows[i]
  572. values = out[i]
  573. if str(row["layer"]) == str(values["Layer"]):
  574. lay = "{map}_{layer}".format(
  575. map=row["name"], layer=values["Layer"]
  576. )
  577. self.timeDataV[name][lay] = {}
  578. self.timeDataV[name][lay]["start_datetime"] = row["start_time"]
  579. self.timeDataV[name][lay]["end_datetime"] = row["start_time"]
  580. self.timeDataV[name][lay]["value"] = values["Attributes"][
  581. attribute
  582. ]
  583. else:
  584. wherequery = ""
  585. cats = self._getExistingCategories(rows[0]["name"], cats)
  586. totcat = len(cats)
  587. ncat = 1
  588. for cat in cats:
  589. if ncat == 1 and totcat != 1:
  590. wherequery += "{k}={c} or".format(c=cat, k="{key}")
  591. elif ncat == 1 and totcat == 1:
  592. wherequery += "{k}={c}".format(c=cat, k="{key}")
  593. elif ncat == totcat:
  594. wherequery += " {k}={c}".format(c=cat, k="{key}")
  595. else:
  596. wherequery += " {k}={c} or".format(c=cat, k="{key}")
  597. catn = "cat{num}".format(num=cat)
  598. self.plotNameListV.append("{na}+{cat}".format(na=name, cat=catn))
  599. self.timeDataV[name][catn] = OrderedDict()
  600. ncat += 1
  601. for row in rows:
  602. lay = int(row["layer"])
  603. catkey = self._parseVDbConn(row["name"], lay)
  604. if not catkey:
  605. GError(
  606. parent=self,
  607. showTraceback=False,
  608. message=_(
  609. "No connection between vector map {vmap} "
  610. "and layer {la}".format(vmap=row["name"], la=lay)
  611. ),
  612. )
  613. return
  614. vals = grass.vector_db_select(
  615. map=row["name"],
  616. layer=lay,
  617. where=wherequery.format(key=catkey),
  618. columns=attribute,
  619. )
  620. layn = "lay{num}".format(num=lay)
  621. for cat in cats:
  622. catn = "cat{num}".format(num=cat)
  623. if layn not in self.timeDataV[name][catn].keys():
  624. self.timeDataV[name][catn][layn] = {}
  625. self.timeDataV[name][catn][layn]["start_datetime"] = row[
  626. "start_time"
  627. ]
  628. self.timeDataV[name][catn][layn]["end_datetime"] = row[
  629. "end_time"
  630. ]
  631. self.timeDataV[name][catn][layn]["value"] = vals["values"][
  632. int(cat)
  633. ][0]
  634. self.unit = unit
  635. self.temporalType = mode
  636. return
  637. def _drawFigure(self):
  638. """Draws or print 2D plot (temporal extents)"""
  639. self.axes2d.clear()
  640. self.axes2d.grid(False)
  641. if self.temporalType == "absolute":
  642. self.axes2d.xaxis_date()
  643. self.fig.autofmt_xdate()
  644. self.convert = mdates.date2num
  645. self.invconvert = mdates.num2date
  646. else:
  647. self.convert = lambda x: x
  648. self.invconvert = self.convert
  649. self.colors = cycle(COLORS)
  650. self.yticksNames = []
  651. self.yticksPos = []
  652. self.plots = []
  653. self.drawTitle = self.title.GetValue()
  654. self.drawX = self.x.GetValue()
  655. self.drawY = self.y.GetValue()
  656. if self.datasetsR:
  657. self.lookUp = LookUp(self.timeDataR, self.invconvert)
  658. else:
  659. self.lookUp = LookUp(self.timeDataV, self.invconvert)
  660. if self.datasetsR:
  661. self.drawR()
  662. if self.datasetsV:
  663. if self.poi:
  664. self.drawV()
  665. elif self.cats:
  666. self.drawVCats()
  667. self.canvas.draw()
  668. DataCursor(self.plots, self.lookUp, InfoFormat, self.convert)
  669. def _setLabels(self, x):
  670. """Function to set the right labels"""
  671. if self.drawX != "":
  672. self.axes2d.set_xlabel(self.drawX)
  673. else:
  674. if self.temporalType == "absolute":
  675. self.axes2d.set_xlabel(_("Temporal resolution: %s" % x))
  676. else:
  677. self.axes2d.set_xlabel(_("Time [%s]") % self.unit)
  678. if self.drawY != "":
  679. self.axes2d.set_ylabel(self.drawY)
  680. else:
  681. self.axes2d.set_ylabel(", ".join(self.yticksNames))
  682. if self.drawTitle != "":
  683. self.axes2d.set_title(self.drawTitle)
  684. def _writeCSV(self, x, y):
  685. """Used to write CSV file of plotted data"""
  686. import csv
  687. if isinstance(y[0], list):
  688. zipped = list(zip(x, *y))
  689. else:
  690. zipped = list(zip(x, y))
  691. with open(self.csvpath, "w", newline="") as fi:
  692. writer = csv.writer(fi)
  693. if self.header:
  694. head = ["Time"]
  695. head.extend(self.yticksNames)
  696. writer.writerow(head)
  697. writer.writerows(zipped)
  698. def _calcSimpleLinReg(self, x, y, returnFormula=False):
  699. """Calculate simple linear regression model
  700. y = a + b*x (y is dependent variable, a is intercept, b is slope,
  701. x is explanatory variable)
  702. param numpy.array x: explanatory variable
  703. param numpy.array y: dependent variable
  704. param returnFormula bool: return calculated simple linear
  705. regression formula too
  706. return tuple or function:
  707. tuple: (simple linear regression function model for dependent
  708. variable, calculated simple linear regression formula model)
  709. function: simple linear regression model function for dependent
  710. variable
  711. """
  712. def predict(x1):
  713. return a + b * x1
  714. b = (len(x) * np.sum(x * y) - np.sum(x) * np.sum(y)) / (
  715. len(x) * np.sum(x * x) - np.sum(x) * np.sum(x)
  716. )
  717. a = (np.sum(y) - b * np.sum(x)) / len(x)
  718. if returnFormula:
  719. return predict, "y = {a:.5f} + {b:.5f}*x".format(a=a, b=b)
  720. return predict
  721. def _drawSimpleLinRegLine(self, xdata, ydata):
  722. """Draw simple regression line
  723. :param list xdata: x axis data
  724. :param list xdata: y axis data
  725. return None
  726. """
  727. predict, regFormula = self._calcSimpleLinReg(
  728. x=np.array(xdata), y=np.array(ydata), returnFormula=True
  729. )
  730. r2 = "r\u00B2 = {:.5f}".format(
  731. np.corrcoef(np.array(xdata), np.array(ydata))[0, 1] ** 2
  732. )
  733. self.plots.append(
  734. self.axes2d.plot(
  735. xdata,
  736. predict(x1=np.array(xdata)),
  737. color=LINEAR_REG_LINE_COLOR,
  738. label="{reg}, {r2}".format(reg=regFormula, r2=r2),
  739. )[0]
  740. )
  741. print(regFormula)
  742. import platform
  743. if platform.system() == "Windows":
  744. print(" =".join(["r2"] + r2.split("=")[1:]))
  745. else:
  746. print(r2)
  747. def drawR(self):
  748. ycsv = []
  749. xcsv = []
  750. for i, name in enumerate(self.datasetsR):
  751. name = name[0]
  752. # just name; with mapset it would be long
  753. self.yticksNames.append(name)
  754. self.yticksPos.append(1) # TODO
  755. xdata = []
  756. ydata = []
  757. for keys, values in six.iteritems(self.timeDataR[name]):
  758. if keys in [
  759. "temporalType",
  760. "granularity",
  761. "validTopology",
  762. "unit",
  763. "temporalDataType",
  764. ]:
  765. continue
  766. xdata.append(self.convert(values["start_datetime"]))
  767. ydata.append(values["value"])
  768. xcsv.append(values["start_datetime"])
  769. if len(ydata) == ydata.count(None):
  770. GError(
  771. parent=self,
  772. showTraceback=False,
  773. message=_(
  774. "Problem getting data from raster temporal"
  775. " dataset. Empty list of values."
  776. ),
  777. )
  778. return
  779. self.lookUp.AddDataset(yranges=ydata, xranges=xdata, datasetName=name)
  780. color = next(self.colors)
  781. self.plots.append(
  782. self.axes2d.plot(
  783. xdata, ydata, marker="o", color=color, label=self.plotNameListR[i]
  784. )[0]
  785. )
  786. if self.linRegRaster.IsChecked():
  787. self._drawSimpleLinRegLine(xdata=xdata, ydata=ydata)
  788. if self.csvpath:
  789. ycsv.append(ydata)
  790. if self.csvpath:
  791. self._writeCSV(xcsv, ycsv)
  792. self._setLabels(self.timeDataR[name]["granularity"])
  793. # legend
  794. handles, labels = self.axes2d.get_legend_handles_labels()
  795. self.axes2d.legend(loc=0)
  796. def drawVCats(self):
  797. ycsv = []
  798. for i, name in enumerate(self.plotNameListV):
  799. # just name; with mapset it would be long
  800. labelname = name.replace("+", " ")
  801. self.yticksNames.append(labelname)
  802. name_cat = name.split("+")
  803. name = name_cat[0]
  804. self.yticksPos.append(1) # TODO
  805. xdata = []
  806. ydata = []
  807. xcsv = []
  808. for keys, values in six.iteritems(self.timeDataV[name_cat[0]][name_cat[1]]):
  809. if keys in [
  810. "temporalType",
  811. "granularity",
  812. "validTopology",
  813. "unit",
  814. "temporalDataType",
  815. ]:
  816. continue
  817. xdata.append(self.convert(values["start_datetime"]))
  818. if values["value"] == "":
  819. ydata.append(None)
  820. else:
  821. ydata.append(values["value"])
  822. xcsv.append(values["start_datetime"])
  823. if len(ydata) == ydata.count(None):
  824. GError(
  825. parent=self,
  826. showTraceback=False,
  827. message=_(
  828. "Problem getting data from vector temporal"
  829. " dataset. Empty list of values for cat "
  830. "{ca}.".format(ca=name_cat[1].replace("cat", ""))
  831. ),
  832. )
  833. continue
  834. self.lookUp.AddDataset(yranges=ydata, xranges=xdata, datasetName=name)
  835. color = next(self.colors)
  836. self.plots.append(
  837. self.axes2d.plot(
  838. xdata, ydata, marker="o", color=color, label=labelname
  839. )[0]
  840. )
  841. if self.linRegVector.IsChecked():
  842. self._drawSimpleLinRegLine(xdata=xdata, ydata=ydata)
  843. if self.csvpath:
  844. ycsv.append(ydata)
  845. if self.csvpath:
  846. self._writeCSV(xcsv, ycsv)
  847. self._setLabels(self.timeDataV[name]["granularity"])
  848. # legend
  849. handles, labels = self.axes2d.get_legend_handles_labels()
  850. self.axes2d.legend(loc=0)
  851. self.listWhereConditions = []
  852. def drawV(self):
  853. ycsv = []
  854. for i, name in enumerate(self.plotNameListV):
  855. # just name; with mapset it would be long
  856. self.yticksNames.append(self.attribute.GetValue())
  857. self.yticksPos.append(0) # TODO
  858. xdata = []
  859. ydata = []
  860. xcsv = []
  861. for keys, values in six.iteritems(self.timeDataV[name]):
  862. if keys in [
  863. "temporalType",
  864. "granularity",
  865. "validTopology",
  866. "unit",
  867. "temporalDataType",
  868. ]:
  869. continue
  870. xdata.append(self.convert(values["start_datetime"]))
  871. ydata.append(values["value"])
  872. xcsv.append(values["start_datetime"])
  873. if len(ydata) == ydata.count(None):
  874. GError(
  875. parent=self,
  876. showTraceback=False,
  877. message=_(
  878. "Problem getting data from vector temporal"
  879. " dataset. Empty list of values."
  880. ),
  881. )
  882. return
  883. self.lookUp.AddDataset(yranges=ydata, xranges=xdata, datasetName=name)
  884. color = next(self.colors)
  885. self.plots.append(
  886. self.axes2d.plot(xdata, ydata, marker="o", color=color, label=name)[0]
  887. )
  888. if self.linRegVector.IsChecked():
  889. self._drawSimpleLinRegLine(xdata=xdata, ydata=ydata)
  890. if self.csvpath:
  891. ycsv.append(ydata)
  892. if self.csvpath:
  893. self._writeCSV(xcsv, ycsv)
  894. self._setLabels(self.timeDataV[name]["granularity"])
  895. # legend
  896. handles, labels = self.axes2d.get_legend_handles_labels()
  897. self.axes2d.legend(loc=0)
  898. self.listWhereConditions = []
  899. def OnRedraw(self, event=None):
  900. """Required redrawing."""
  901. self.init()
  902. self.csvpath = self.csvButton.GetValue()
  903. self.header = self.headerCheck.IsChecked()
  904. if os.path.exists(self.csvpath) and not self.overwrite:
  905. dlg = wx.MessageDialog(
  906. self,
  907. _(
  908. "{pa} already exists, do you want "
  909. "to overwrite?".format(pa=self.csvpath)
  910. ),
  911. _("File exists"),
  912. wx.OK | wx.CANCEL | wx.ICON_QUESTION,
  913. )
  914. if dlg.ShowModal() != wx.ID_OK:
  915. dlg.Destroy()
  916. GError(
  917. parent=self,
  918. showTraceback=False,
  919. message=_("Please change name of output CSV file or "),
  920. )
  921. return
  922. dlg.Destroy()
  923. datasetsR = self.datasetSelectR.GetValue().strip()
  924. datasetsV = self.datasetSelectV.GetValue().strip()
  925. if not datasetsR and not datasetsV:
  926. return
  927. try:
  928. getcoors = self.coorval.coordsField.GetValue()
  929. except:
  930. try:
  931. getcoors = self.coorval.GetValue()
  932. except:
  933. getcoors = None
  934. if getcoors and getcoors != "":
  935. try:
  936. coordx, coordy = getcoors.split(",")
  937. coordx, coordy = float(coordx), float(coordy)
  938. except (ValueError, AttributeError):
  939. try:
  940. coordx, coordy = self.coorval.GetValue().split(",")
  941. coordx, coordy = float(coordx), float(coordy)
  942. except (ValueError, AttributeError):
  943. GError(
  944. parent=self,
  945. message=_("Incorrect coordinates format, should be: x,y"),
  946. showTraceback=False,
  947. )
  948. return
  949. coors = [coordx, coordy]
  950. if coors:
  951. try:
  952. self.poi = Point(float(coors[0]), float(coors[1]))
  953. except GException:
  954. GError(
  955. parent=self,
  956. message=_("Invalid input coordinates"),
  957. showTraceback=False,
  958. )
  959. return
  960. if not self.poi:
  961. GError(
  962. parent=self,
  963. message=_("Invalid input coordinates"),
  964. showTraceback=False,
  965. )
  966. return
  967. bbox = self.region.get_bbox()
  968. if not bbox.contains(self.poi):
  969. GError(
  970. parent=self,
  971. message=_("Seed point outside the " "current region"),
  972. showTraceback=False,
  973. )
  974. return
  975. # check raster dataset
  976. if datasetsR:
  977. datasetsR = datasetsR.split(",")
  978. try:
  979. datasetsR = self._checkDatasets(datasetsR, "strds")
  980. if not datasetsR:
  981. return
  982. except GException:
  983. GError(
  984. parent=self,
  985. message=_("Invalid input raster dataset"),
  986. showTraceback=False,
  987. )
  988. return
  989. if not self.poi:
  990. GError(
  991. parent=self,
  992. message=_("Invalid input coordinates"),
  993. showTraceback=False,
  994. )
  995. return
  996. self.datasetsR = datasetsR
  997. # check vector dataset
  998. if datasetsV:
  999. datasetsV = datasetsV.split(",")
  1000. try:
  1001. datasetsV = self._checkDatasets(datasetsV, "stvds")
  1002. if not datasetsV:
  1003. return
  1004. except GException:
  1005. GError(
  1006. parent=self,
  1007. message=_("Invalid input vector dataset"),
  1008. showTraceback=False,
  1009. )
  1010. return
  1011. self.datasetsV = datasetsV
  1012. self._redraw()
  1013. def _redraw(self):
  1014. """Readraw data.
  1015. Decides if to draw also 3D and adjusts layout if needed.
  1016. """
  1017. if self.datasetsR:
  1018. self._getSTRDdata(self.datasetsR)
  1019. if self.datasetsV:
  1020. self._getSTVDData(self.datasetsV)
  1021. # axes3d are physically removed
  1022. if not self.axes2d:
  1023. self.axes2d = self.fig.add_subplot(1, 1, 1)
  1024. self._drawFigure()
  1025. def _checkDatasets(self, datasets, typ):
  1026. """Checks and validates datasets.
  1027. Reports also type of dataset (e.g. 'strds').
  1028. :param list datasets: list of temporal dataset's name
  1029. :return: (mapName, mapset, type)
  1030. """
  1031. validated = []
  1032. tDict = tgis.tlist_grouped(type=typ, group_type=True, dbif=self.dbif)
  1033. # nested list with '(map, mapset, etype)' items
  1034. allDatasets = [
  1035. [
  1036. [(map, mapset, etype) for map in maps]
  1037. for etype, maps in six.iteritems(etypesDict)
  1038. ]
  1039. for mapset, etypesDict in six.iteritems(tDict)
  1040. ]
  1041. # flatten this list
  1042. if allDatasets:
  1043. allDatasets = reduce(
  1044. lambda x, y: x + y, reduce(lambda x, y: x + y, allDatasets)
  1045. )
  1046. mapsets = tgis.get_tgis_c_library_interface().available_mapsets()
  1047. allDatasets = [
  1048. i for i in sorted(allDatasets, key=lambda l: mapsets.index(l[1]))
  1049. ]
  1050. for dataset in datasets:
  1051. errorMsg = _("Space time dataset <%s> not found.") % dataset
  1052. if dataset.find("@") >= 0:
  1053. nameShort, mapset = dataset.split("@", 1)
  1054. indices = [
  1055. n
  1056. for n, (mapName, mapsetName, etype) in enumerate(allDatasets)
  1057. if nameShort == mapName and mapsetName == mapset
  1058. ]
  1059. else:
  1060. indices = [
  1061. n
  1062. for n, (mapName, mapset, etype) in enumerate(allDatasets)
  1063. if dataset == mapName
  1064. ]
  1065. if len(indices) == 0:
  1066. raise GException(errorMsg)
  1067. elif len(indices) >= 2:
  1068. dlg = wx.SingleChoiceDialog(
  1069. self,
  1070. message=_(
  1071. "Please specify the " "space time dataset " "<%s>." % dataset
  1072. ),
  1073. caption=_("Ambiguous dataset name"),
  1074. choices=[
  1075. (
  1076. "%(map)s@%(mapset)s:"
  1077. " %(etype)s"
  1078. % {
  1079. "map": allDatasets[i][0],
  1080. "mapset": allDatasets[i][1],
  1081. "etype": allDatasets[i][2],
  1082. }
  1083. )
  1084. for i in indices
  1085. ],
  1086. style=wx.CHOICEDLG_STYLE | wx.OK,
  1087. )
  1088. if dlg.ShowModal() == wx.ID_OK:
  1089. index = dlg.GetSelection()
  1090. validated.append(allDatasets[indices[index]])
  1091. else:
  1092. continue
  1093. else:
  1094. validated.append(allDatasets[indices[0]])
  1095. return validated
  1096. def OnHelp(self, event):
  1097. """Function to show help"""
  1098. RunCommand(prog="g.manual", quiet=True, entry="g.gui.tplot")
  1099. def SetDatasets(
  1100. self,
  1101. rasters,
  1102. vectors,
  1103. coors,
  1104. cats,
  1105. attr,
  1106. title,
  1107. xlabel,
  1108. ylabel,
  1109. csvfile,
  1110. head,
  1111. overwrite,
  1112. ):
  1113. """Set the data
  1114. :param list rasters: a list of temporal raster dataset's name
  1115. :param list vectors: a list of temporal vector dataset's name
  1116. :param list coors: a list with x/y coordinates
  1117. :param list cats: a list with incld. categories of vector
  1118. :param str attr: name of attribute of vectror data
  1119. """
  1120. if not (rasters or vectors) or not (coors or cats):
  1121. return
  1122. try:
  1123. if rasters:
  1124. self.datasetsR = self._checkDatasets(rasters, "strds")
  1125. if vectors:
  1126. self.datasetsV = self._checkDatasets(vectors, "stvds")
  1127. if not (self.datasetsR or self.datasetsV):
  1128. return
  1129. except GException:
  1130. GError(
  1131. parent=self,
  1132. message=_("Invalid input temporal dataset"),
  1133. showTraceback=False,
  1134. )
  1135. return
  1136. if coors:
  1137. try:
  1138. self.poi = Point(float(coors[0]), float(coors[1]))
  1139. except GException:
  1140. GError(
  1141. parent=self,
  1142. message=_("Invalid input coordinates"),
  1143. showTraceback=False,
  1144. )
  1145. return
  1146. try:
  1147. self.coorval.coordsField.SetValue(",".join(coors))
  1148. except:
  1149. self.coorval.SetValue(",".join(coors))
  1150. if self.datasetsV:
  1151. vdatas = ",".join(map(lambda x: x[0] + "@" + x[1], self.datasetsV))
  1152. self.datasetSelectV.SetValue(vdatas)
  1153. if attr:
  1154. self.attribute.SetValue(attr)
  1155. if cats:
  1156. self.cats.SetValue(cats)
  1157. if self.datasetsR:
  1158. self.datasetSelectR.SetValue(
  1159. ",".join(map(lambda x: x[0] + "@" + x[1], self.datasetsR))
  1160. )
  1161. if title:
  1162. self.title.SetValue(title)
  1163. if xlabel:
  1164. self.x.SetValue(xlabel)
  1165. if ylabel:
  1166. self.y.SetValue(ylabel)
  1167. if csvfile:
  1168. self.csvpath = csvfile
  1169. self.header = head
  1170. self.overwrite = overwrite
  1171. self._redraw()
  1172. def OnVectorSelected(self, event):
  1173. """Update the controlbox related to stvds"""
  1174. dataset = self.datasetSelectV.GetValue().strip()
  1175. name = dataset.split("@")[0]
  1176. mapset = dataset.split("@")[1] if len(dataset.split("@")) > 1 else ""
  1177. found = False
  1178. for each in tgis.tlist(type="stvds", dbif=self.dbif):
  1179. each_name, each_mapset = each.split("@")
  1180. if name == each_name:
  1181. if mapset and mapset != each_mapset:
  1182. continue
  1183. dataset = name + "@" + each_mapset
  1184. found = True
  1185. break
  1186. if found:
  1187. try:
  1188. vect_list = grass.read_command(
  1189. "t.vect.list", flags="u", input=dataset, column="name"
  1190. )
  1191. except Exception:
  1192. self.attribute.Clear()
  1193. GError(
  1194. parent=self,
  1195. message=_("Invalid input temporal dataset"),
  1196. showTraceback=False,
  1197. )
  1198. return
  1199. vect_list = list(set(sorted(vect_list.split())))
  1200. for vec in vect_list:
  1201. self.attribute.InsertColumns(vec, 1)
  1202. else:
  1203. self.attribute.Clear()
  1204. class LookUp:
  1205. """Helper class for searching info by coordinates"""
  1206. def __init__(self, timeData, convert):
  1207. self.data = {}
  1208. self.timeData = timeData
  1209. self.convert = convert
  1210. def AddDataset(self, yranges, xranges, datasetName):
  1211. if len(yranges) != len(xranges):
  1212. GError(
  1213. parent=self,
  1214. showTraceback=False,
  1215. message=_("Datasets have different number of values"),
  1216. )
  1217. return
  1218. self.data[datasetName] = {}
  1219. for i in range(len(xranges)):
  1220. self.data[datasetName][xranges[i]] = yranges[i]
  1221. def GetInformation(self, x):
  1222. values = {}
  1223. for key, value in six.iteritems(self.data):
  1224. if value[x]:
  1225. values[key] = [self.convert(x), value[x]]
  1226. if len(values) == 0:
  1227. return None
  1228. return self.timeData, values
  1229. def InfoFormat(timeData, values):
  1230. """Formats information about dataset"""
  1231. text = []
  1232. for key, val in six.iteritems(values):
  1233. etype = timeData[key]["temporalDataType"]
  1234. if etype == "strds":
  1235. text.append(_("Space time raster dataset: %s") % key)
  1236. elif etype == "stvds":
  1237. text.append(_("Space time vector dataset: %s") % key)
  1238. elif etype == "str3ds":
  1239. text.append(_("Space time 3D raster dataset: %s") % key)
  1240. text.append(_("Value for {date} is {val}".format(date=val[0], val=val[1])))
  1241. text.append("\n")
  1242. text.append(_("Press Del to dismiss."))
  1243. return "\n".join(text)
  1244. class DataCursor(object):
  1245. """A simple data cursor widget that displays the x,y location of a
  1246. matplotlib artist when it is selected.
  1247. Source: http://stackoverflow.com/questions/4652439/
  1248. is-there-a-matplotlib-equivalent-of-matlabs-datacursormode/4674445
  1249. """
  1250. def __init__(
  1251. self,
  1252. artists,
  1253. lookUp,
  1254. formatFunction,
  1255. convert,
  1256. tolerance=5,
  1257. offsets=(-30, 20),
  1258. display_all=False,
  1259. ):
  1260. """Create the data cursor and connect it to the relevant figure.
  1261. "artists" is the matplotlib artist or sequence of artists that will be
  1262. selected.
  1263. "tolerance" is the radius (in points) that the mouse click must be
  1264. within to select the artist.
  1265. "offsets" is a tuple of (x,y) offsets in points from the selected
  1266. point to the displayed annotation box
  1267. "display_all" controls whether more than one annotation box will
  1268. be shown if there are multiple axes. Only one will be shown
  1269. per-axis, regardless.
  1270. """
  1271. self.lookUp = lookUp
  1272. self.formatFunction = formatFunction
  1273. self.offsets = offsets
  1274. self.display_all = display_all
  1275. if not np.iterable(artists):
  1276. artists = [artists]
  1277. self.artists = artists
  1278. self.convert = convert
  1279. self.axes = tuple(set(art.axes for art in self.artists))
  1280. self.figures = tuple(set(ax.figure for ax in self.axes))
  1281. self.annotations = {}
  1282. for ax in self.axes:
  1283. self.annotations[ax] = self.annotate(ax)
  1284. for artist in self.artists:
  1285. artist.set_pickradius(tolerance)
  1286. for fig in self.figures:
  1287. fig.canvas.mpl_connect("pick_event", self)
  1288. fig.canvas.mpl_connect("key_press_event", self.keyPressed)
  1289. def keyPressed(self, event):
  1290. """Key pressed - hide annotation if Delete was pressed"""
  1291. if event.key != "delete":
  1292. return
  1293. for ax in self.axes:
  1294. self.annotations[ax].set_visible(False)
  1295. event.canvas.draw()
  1296. def annotate(self, ax):
  1297. """Draws and hides the annotation box for the given axis "ax"."""
  1298. annotation = ax.annotate(
  1299. self.formatFunction,
  1300. xy=(0, 0),
  1301. ha="center",
  1302. xytext=self.offsets,
  1303. va="bottom",
  1304. textcoords="offset points",
  1305. bbox=dict(boxstyle="round,pad=0.5", fc="yellow", alpha=0.7),
  1306. arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=0"),
  1307. annotation_clip=False,
  1308. multialignment="left",
  1309. )
  1310. annotation.set_visible(False)
  1311. return annotation
  1312. def __call__(self, event):
  1313. """Intended to be called through "mpl_connect"."""
  1314. # Rather than trying to interpolate, just display the clicked coords
  1315. # This will only be called if it's within "tolerance", anyway.
  1316. x, y = event.mouseevent.xdata, event.mouseevent.ydata
  1317. annotation = self.annotations[event.artist.axes]
  1318. if x is not None:
  1319. if not self.display_all:
  1320. # Hide any other annotation boxes...
  1321. for ann in self.annotations.values():
  1322. ann.set_visible(False)
  1323. if "Line2D" in str(type(event.artist)):
  1324. xData = []
  1325. for a in event.artist.get_xdata():
  1326. try:
  1327. d = self.convert(a)
  1328. except:
  1329. d = a
  1330. xData.append(d)
  1331. x = xData[np.argmin(abs(xData - x))]
  1332. info = self.lookUp.GetInformation(x)
  1333. ys = list(zip(*info[1].values()))[1]
  1334. if not info:
  1335. return
  1336. # Update the annotation in the current axis..
  1337. annotation.xy = x, max(ys)
  1338. text = self.formatFunction(*info)
  1339. annotation.set_text(text)
  1340. annotation.set_visible(True)
  1341. event.canvas.draw()