123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963 |
- #!/usr/bin/env python
- """
- @package frame
- @brief Temporal Plot Tool
- Classes:
- - frame::DataCursor
- - frame::TplotFrame
- - frame::LookUp
- (C) 2012-2014 by the GRASS Development Team
- This program is free software under the GNU General Public License
- (>=v2). Read the file COPYING that comes with GRASS for details.
- @author Luca Delucchi
- @author start stvds support Matej Krejci
- """
- from itertools import cycle
- import numpy as np
- import wx
- from grass.pygrass.modules import Module
- import grass.script as grass
- from core.utils import _
- try:
- import matplotlib
- # The recommended way to use wx with mpl is with the WXAgg
- # backend.
- matplotlib.use('WXAgg')
- from matplotlib.figure import Figure
- from matplotlib.backends.backend_wxagg import \
- FigureCanvasWxAgg as FigCanvas, \
- NavigationToolbar2WxAgg as NavigationToolbar
- import matplotlib.dates as mdates
- from matplotlib import cbook
- except ImportError:
- raise ImportError(_('The Temporal Plot Tool needs the "matplotlib" '
- '(python-matplotlib) package to be installed.'))
- from core.utils import _
- import grass.temporal as tgis
- from core.gcmd import GMessage, GError, GException, RunCommand
- from gui_core.widgets import CoordinatesValidator
- from gui_core import gselect
- from core import globalvar
- from grass.pygrass.vector.geometry import Point
- from grass.pygrass.raster import RasterRow
- from collections import OrderedDict
- from subprocess import PIPE
- try:
- import wx.lib.agw.flatnotebook as FN
- except ImportError:
- import wx.lib.flatnotebook as FN
- from gui_core.widgets import GNotebook
- ALPHA = 0.5
- COLORS = ['b', 'g', 'r', 'c', 'm', 'y', 'k']
- def check_version(*version):
- """Checks if given version or newer is installed"""
- versionInstalled = []
- for i in matplotlib.__version__.split('.'):
- try:
- v = int(i)
- versionInstalled.append(v)
- except ValueError:
- versionInstalled.append(0)
- if versionInstalled < list(version):
- return False
- else:
- return True
- def findBetween(s, first, last):
- try:
- start = s.rindex(first) + len(first)
- end = s.rindex(last, start)
- return s[start:end]
- except ValueError:
- return ""
- class TplotFrame(wx.Frame):
- """The main frame of the application"""
- def __init__(self, parent, giface):
- wx.Frame.__init__(self, parent, id=wx.ID_ANY,
- title=_("GRASS GIS Temporal Plot Tool"))
- tgis.init(True)
- self._giface = giface
- self.datasetsV = None
- self.datasetsR = None
- # self.vectorDraw=False
- # self.rasterDraw=False
- self.init()
- self._layout()
- # We create a database interface here to speedup the GUI
- self.dbif = tgis.SQLDatabaseInterfaceConnection()
- self.dbif.connect()
- def init(self):
- self.timeDataR = OrderedDict()
- self.timeDataV = OrderedDict()
- self.temporalType = None
- self.unit = None
- self.listWhereConditions = []
- self.plotNameListR = []
- self.plotNameListV = []
- self.poi = None
- def __del__(self):
- """Close the database interface and stop the messenger and C-interface
- subprocesses.
- """
- if self.dbif.connected is True:
- self.dbif.close()
- tgis.stop_subprocesses()
- def _layout(self):
- """Creates the main panel with all the controls on it:
- * mpl canvas
- * mpl navigation toolbar
- * Control panel for interaction
- """
- self.mainPanel = wx.Panel(self)
- # Create the mpl Figure and FigCanvas objects.
- # 5x4 inches, 100 dots-per-inch
- #
- # color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
- # ------------CANVAS AND TOOLBAR------------
- self.fig = Figure((5.0, 4.0), facecolor=(1, 1, 1))
- self.canvas = FigCanvas(self.mainPanel, wx.ID_ANY, self.fig)
- # axes are initialized later
- self.axes2d = None
- self.axes3d = None
- # Create the navigation toolbar, tied to the canvas
- #
- self.toolbar = NavigationToolbar(self.canvas)
- #
- # Layout
- #
- # ------------MAIN VERTICAL SIZER------------
- self.vbox = wx.BoxSizer(wx.VERTICAL)
- self.vbox.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.EXPAND)
- self.vbox.Add(self.toolbar, 0, wx.EXPAND)
- # self.vbox.AddSpacer(10)
- # ------------ADD NOTEBOOK------------
- self.ntb = GNotebook(parent=self.mainPanel, style=FN.FNB_FANCY_TABS)
- # ------------ITEMS IN NOTEBOOK PAGE (RASTER)------------------------
- self.controlPanelRaster = wx.Panel(parent=self.ntb, id=wx.ID_ANY)
- self.datasetSelectLabelR = wx.StaticText(parent=self.controlPanelRaster,
- id=wx.ID_ANY,
- label=_('Raster temporal '
- 'dataset (strds)'))
- self.datasetSelectR = gselect.Select(parent=self.controlPanelRaster,
- id=wx.ID_ANY,
- size=globalvar.DIALOG_GSELECT_SIZE,
- type='strds', multiple=True)
- self.coor = wx.StaticText(parent=self.controlPanelRaster, id=wx.ID_ANY,
- label=_('X and Y coordinates separated by '
- 'comma:'))
- try:
- self._giface.GetMapWindow()
- self.coorval = gselect.CoordinatesSelect(parent=self.controlPanelRaster,
- giface=self._giface)
- except:
- self.coorval = wx.TextCtrl(parent=self.controlPanelRaster,
- id=wx.ID_ANY,
- size=globalvar.DIALOG_TEXTCTRL_SIZE,
- validator=CoordinatesValidator())
- self.coorval.SetToolTipString(_("Coordinates can be obtained for example"
- " by right-clicking on Map Display."))
- self.controlPanelSizerRaster = wx.BoxSizer(wx.VERTICAL)
- # self.controlPanelSizer.Add(wx.StaticText(self.panel, id=wx.ID_ANY,
- # label=_("Select space time raster dataset(s):")),
- # pos=(0, 0), flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
- self.controlPanelSizerRaster.Add(self.datasetSelectLabelR,
- flag=wx.EXPAND)
- self.controlPanelSizerRaster.Add(self.datasetSelectR, flag=wx.EXPAND)
- self.controlPanelSizerRaster.Add(self.coor, flag=wx.EXPAND)
- self.controlPanelSizerRaster.Add(self.coorval, flag=wx.EXPAND)
- self.controlPanelRaster.SetSizer(self.controlPanelSizerRaster)
- self.controlPanelSizerRaster.Fit(self)
- self.ntb.AddPage(page=self.controlPanelRaster, text=_('STRDS'),
- name='STRDS')
- # ------------ITEMS IN NOTEBOOK PAGE (VECTOR)------------------------
- self.controlPanelVector = wx.Panel(parent=self.ntb, id=wx.ID_ANY)
- self.datasetSelectLabelV = wx.StaticText(parent=self.controlPanelVector,
- id=wx.ID_ANY,
- label=_('Vector temporal '
- 'dataset (strds)'))
- self.datasetSelectV = gselect.Select(parent=self.controlPanelVector,
- id=wx.ID_ANY,
- size=globalvar.DIALOG_GSELECT_SIZE,
- type='stvds', multiple=True)
- self.datasetSelectV.Bind(wx.EVT_TEXT, self.OnVectorSelected)
- self.attribute = gselect.ColumnSelect(parent=self.controlPanelVector)
- self.attributeLabel = wx.StaticText(parent=self.controlPanelVector,
- id=wx.ID_ANY,
- label=_('Select attribute column'))
- # TODO fix the category selection as done for coordinates
- try:
- self._giface.GetMapWindow()
- self.cats = gselect.VectorCategorySelect(parent=self.controlPanelVector,
- giface=self._giface)
- except:
- self.cats = wx.TextCtrl(parent=self.controlPanelVector, id=wx.ID_ANY,
- size=globalvar.DIALOG_TEXTCTRL_SIZE)
- self.catsLabel = wx.StaticText(parent=self.controlPanelVector,
- id=wx.ID_ANY,
- label=_('Select category of vector(s)'))
- self.controlPanelSizerVector = wx.BoxSizer(wx.VERTICAL)
- #self.controlPanelSizer.Add(wx.StaticText(self.panel, id=wx.ID_ANY,
- #label=_("Select space time raster dataset(s):")),
- #pos=(0, 0), flag=wx.EXPAND | wx.ALIGN_CENTER_VERTICAL)
- self.controlPanelSizerVector.Add(self.datasetSelectLabelV,
- flag=wx.EXPAND)
- self.controlPanelSizerVector.Add(self.datasetSelectV, flag=wx.EXPAND)
- self.controlPanelSizerVector.Add(self.attributeLabel, flag=wx.EXPAND)
- self.controlPanelSizerVector.Add(self.attribute, flag=wx.EXPAND)
- self.controlPanelSizerVector.Add(self.catsLabel, flag=wx.EXPAND)
- self.controlPanelSizerVector.Add(self.cats, flag=wx.EXPAND)
- self.controlPanelVector.SetSizer(self.controlPanelSizerVector)
- self.controlPanelSizerVector.Fit(self)
- self.ntb.AddPage(page=self.controlPanelVector, text=_('STVDS'),
- name='STVDS')
- # ------------Buttons on the bottom(draw,help)------------
- self.vButtPanel = wx.Panel(self.mainPanel, id=wx.ID_ANY)
- self.vButtSizer = wx.BoxSizer(wx.HORIZONTAL)
- self.drawButton = wx.Button(self.vButtPanel, id=wx.ID_ANY,
- label=_("Draw"))
- self.drawButton.Bind(wx.EVT_BUTTON, self.OnRedraw)
- self.helpButton = wx.Button(self.vButtPanel, id=wx.ID_ANY,
- label=_("Help"))
- self.helpButton.Bind(wx.EVT_BUTTON, self.OnHelp)
- self.vButtSizer.Add(self.drawButton)
- self.vButtSizer.Add(self.helpButton)
- self.vButtPanel.SetSizer(self.vButtSizer)
- self.mainPanel.SetSizer(self.vbox)
- self.vbox.Add(self.ntb, flag=wx.EXPAND)
- self.vbox.Add(self.vButtPanel, flag=wx.EXPAND)
- self.vbox.Fit(self)
- self.mainPanel.Fit()
- def _getSTRDdata(self, timeseries):
- """Load data and read properties
- :param list timeseries: a list of timeseries
- """
- mode = None
- unit = None
- columns = ','.join(['name', 'start_time', 'end_time'])
- for series in timeseries:
- name = series[0]
- fullname = name + '@' + series[1]
- etype = series[2]
- sp = tgis.dataset_factory(etype, fullname)
- if not sp.is_in_db(dbif=self.dbif):
- GError(message=_("Dataset <%s> not found in temporal "
- "database") % (fullname), parent=self)
- return
- sp.select(dbif=self.dbif)
- minmin = sp.metadata.get_min_min()
- self.plotNameListR.append(name)
- self.timeDataR[name] = OrderedDict()
- self.timeDataR[name]['temporalDataType'] = etype
- self.timeDataR[name]['temporalType'] = sp.get_temporal_type()
- self.timeDataR[name]['granularity'] = sp.get_granularity()
- if mode is None:
- mode = self.timeDataR[name]['temporalType']
- elif self.timeDataR[name]['temporalType'] != mode:
- GError(parent=self, message=_("Datasets have different temporal"
- " type (absolute x relative), "
- "which is not allowed."))
- return
- # check topology
- maps = sp.get_registered_maps_as_objects(dbif=self.dbif)
- self.timeDataR[name]['validTopology'] = sp.check_temporal_topology(maps=maps, dbif=self.dbif)
- self.timeDataR[name]['unit'] = None # only with relative
- if self.timeDataR[name]['temporalType'] == 'relative':
- start, end, self.timeDataR[name]['unit'] = sp.get_relative_time()
- if unit is None:
- unit = self.timeDataR[name]['unit']
- elif self.timeDataR[name]['unit'] != unit:
- GError(parent=self, message=_("Datasets have different "
- "time unit which is not "
- "allowed."))
- return
- rows = sp.get_registered_maps(columns=columns, where=None,
- order='start_time', dbif=self.dbif)
- for row in rows:
- self.timeDataR[name][row[0]] = {}
- self.timeDataR[name][row[0]]['start_datetime'] = row[1]
- self.timeDataR[name][row[0]]['end_datetime'] = row[2]
- r = RasterRow(row[0])
- r.open()
- val = r.get_value(self.poi)
- r.close()
- if val == -2147483648 and val < minmin:
- self.timeDataR[name][row[0]]['value'] = None
- else:
- self.timeDataR[name][row[0]]['value'] = val
- self.unit = unit
- self.temporalType = mode
- return
- def _parseVDbConn(self, mapp, layerInp):
- '''find attribute key according to layer of input map'''
- vdb = Module('v.db.connect', map=mapp, flags='g', stdout_=PIPE)
- vdb = vdb.outputs.stdout
- for line in vdb.splitlines():
- lsplit = line.split('|')
- layer = lsplit[0].split('/')[0]
- if str(layer) == str(layerInp):
- return lsplit[2]
- return None
- def _getExistingCategories(self, mapp, cats):
- """Get a list of categories for a vector map"""
- vdb = grass.read_command('v.category', input=mapp, option='print')
- categories = vdb.splitlines()
- for cat in cats:
- if str(cat) not in categories:
- GMessage(message=_("Category {ca} is not on vector map"
- " {ma} and it will be used").format(ma=mapp,
- ca=cat),
- parent=self)
- cats.remove(cat)
- return cats
- def _getSTVDData(self, timeseries):
- """Load data and read properties
- :param list timeseries: a list of timeseries
- """
- mode = None
- unit = None
- cats = None
- attribute = self.attribute.GetValue()
- if self.cats.GetValue() != '':
- cats = self.cats.GetValue().split(',')
- if cats and self.poi:
- GMessage(message=_("Both coordinates and categories are set, "
- "coordinates will be used. The use categories "
- "remove text from coordinate form"))
- if not attribute or attribute == '':
- GError(parent=self, showTraceback=False,
- message=_("With Vector temporal dataset you have to select"
- " an attribute column"))
- return
- columns = ','.join(['name', 'start_time', 'end_time', 'id', 'layer'])
- for series in timeseries:
- name = series[0]
- fullname = name + '@' + series[1]
- etype = series[2]
- sp = tgis.dataset_factory(etype, fullname)
- if not sp.is_in_db(dbif=self.dbif):
- GError(message=_("Dataset <%s> not found in temporal "
- "database") % (fullname), parent=self,
- showTraceback=False)
- return
- sp.select(dbif=self.dbif)
- rows = sp.get_registered_maps(dbif=self.dbif, order="start_time",
- columns=columns, where=None)
- self.timeDataV[name] = OrderedDict()
- self.timeDataV[name]['temporalDataType'] = etype
- self.timeDataV[name]['temporalType'] = sp.get_temporal_type()
- self.timeDataV[name]['granularity'] = sp.get_granularity()
- if mode is None:
- mode = self.timeDataV[name]['temporalType']
- elif self.timeDataV[name]['temporalType'] != mode:
- GError(parent=self, showTraceback=False,
- message=_("Datasets have different temporal type ("
- "absolute x relative), which is not allowed."))
- return
- self.timeDataV[name]['unit'] = None # only with relative
- if self.timeDataV[name]['temporalType'] == 'relative':
- start, end, self.timeDataV[name]['unit'] = sp.get_relative_time()
- if unit is None:
- unit = self.timeDataV[name]['unit']
- elif self.timeDataV[name]['unit'] != unit:
- GError(message=_("Datasets have different time unit which"
- " is not allowed."), parent=self,
- showTraceback=False)
- return
- if self.poi:
- self.plotNameListV.append(name)
- # TODO set an appropriate distance, right now a big one is set
- # to return the closer point to the selected one
- out = grass.vector_what(map='pois_srvds',
- coord=self.poi.coords(),
- distance=10000000000000000)
- if len(out) != len(rows):
- GError(parent=self, showTraceback=False,
- message=_("Difference number of vector layers and "
- "maps in the vector temporal dataset"))
- return
- for i in range(len(rows)):
- row = rows[i]
- values = out[i]
- if str(row['layer']) == str(values['Layer']):
- lay = "{map}_{layer}".format(map=row['name'],
- layer=values['Layer'])
- self.timeDataV[name][lay] = {}
- self.timeDataV[name][lay]['start_datetime'] = row['start_time']
- self.timeDataV[name][lay]['end_datetime'] = row['start_time']
- self.timeDataV[name][lay]['value'] = values['Attributes'][attribute]
- else:
- wherequery = ''
- cats = self._getExistingCategories(rows[0]['name'], cats)
- totcat = len(cats)
- ncat = 1
- for cat in cats:
- if ncat == 1 and totcat != 1:
- wherequery += '{k}={c} or'.format(c=cat, k="{key}")
- elif ncat == 1 and totcat == 1:
- wherequery += '{k}={c}'.format(c=cat, k="{key}")
- elif ncat == totcat:
- wherequery += ' {k}={c}'.format(c=cat, k="{key}")
- else:
- wherequery += ' {k}={c} or'.format(c=cat, k="{key}")
- catn = "cat{num}".format(num=cat)
- self.plotNameListV.append("{na}+{cat}".format(na=name,
- cat=catn))
- self.timeDataV[name][catn] = OrderedDict()
- ncat += 1
- for row in rows:
- lay = int(row['layer'])
- catkey = self._parseVDbConn(row['name'], lay)
- if not catkey:
- GError(parent=self, showTraceback=False,
- message=_("No connection between vector map {vmap} "
- "and layer {la}".format(vmap=row['name'],
- la=lay)))
- return
- vals = grass.vector_db_select(map=row['name'], layer=lay,
- where=wherequery.format(key=catkey),
- columns=attribute)
- layn = "lay{num}".format(num=lay)
- for cat in cats:
- catn = "cat{num}".format(num=cat)
- if layn not in self.timeDataV[name][catn].keys():
- self.timeDataV[name][catn][layn] = {}
- self.timeDataV[name][catn][layn]['start_datetime'] = row['start_time']
- self.timeDataV[name][catn][layn]['end_datetime'] = row['end_time']
- self.timeDataV[name][catn][layn]['value'] = vals['values'][int(cat)][0]
- self.unit = unit
- self.temporalType = mode
- return
- def _drawFigure(self):
- """Draws or print 2D plot (temporal extents)"""
- self.axes2d.clear()
- self.axes2d.grid(False)
- if self.temporalType == 'absolute':
- self.axes2d.xaxis_date()
- self.fig.autofmt_xdate()
- self.convert = mdates.date2num
- self.invconvert = mdates.num2date
- else:
- self.convert = lambda x: x
- self.invconvert = self.convert
- self.colors = cycle(COLORS)
- self.yticksNames = []
- self.yticksPos = []
- self.plots = []
- if self.datasetsR:
- self.lookUp = LookUp(self.timeDataR, self.invconvert)
- else:
- self.lookUp = LookUp(self.timeDataV, self.invconvert)
- if self.datasetsR:
- self.drawR()
- if self.datasetsV:
- if self.poi:
- self.drawV()
- elif self.cats:
- self.drawVCats()
- self.canvas.draw()
- DataCursor(self.plots, self.lookUp, InfoFormat, self.convert)
- def drawR(self):
- for i, name in enumerate(self.datasetsR):
- name = name[0]
- # just name; with mapset it would be long
- self.yticksNames.append(name)
- self.yticksPos.append(1) # TODO
- xdata = []
- ydata = []
- for keys, values in self.timeDataR[name].iteritems():
- if keys in ['temporalType', 'granularity', 'validTopology',
- 'unit', 'temporalDataType']:
- continue
- xdata.append(self.convert(values['start_datetime']))
- ydata.append(values['value'])
- self.lookUp.AddDataset(yranges=ydata, xranges=xdata,
- datasetName=name)
- color = self.colors.next()
- self.plots.append(self.axes2d.plot(xdata, ydata, marker='o',
- color=color,
- label=self.plotNameListR[i])[0])
- if self.temporalType == 'absolute':
- self.axes2d.set_xlabel(_("Temporal resolution: %s" % self.timeDataR[name]['granularity']))
- else:
- self.axes2d.set_xlabel(_("Time [%s]") % self.unit)
- self.axes2d.set_ylabel(', '.join(self.yticksNames))
- # legend
- handles, labels = self.axes2d.get_legend_handles_labels()
- self.axes2d.legend(loc=0)
- def drawVCats(self):
- for i, name in enumerate(self.plotNameListV):
- # just name; with mapset it would be long
- labelname = name.replace('+', ' ')
- self.yticksNames.append(labelname)
- name_cat = name.split('+')
- name = name_cat[0]
- self.yticksPos.append(1) # TODO
- xdata = []
- ydata = []
- for keys, values in self.timeDataV[name_cat[0]][name_cat[1]].iteritems():
- if keys in ['temporalType', 'granularity', 'validTopology',
- 'unit', 'temporalDataType']:
- continue
- xdata.append(self.convert(values['start_datetime']))
- ydata.append(values['value'])
- self.lookUp.AddDataset(yranges=ydata, xranges=xdata,
- datasetName=name)
- color = self.colors.next()
- self.plots.append(self.axes2d.plot(xdata, ydata, marker='o',
- color=color, label=labelname)[0])
- # ============================
- if self.temporalType == 'absolute':
- self.axes2d.set_xlabel(_("Temporal resolution: %s" % self.timeDataV[name]['granularity']))
- else:
- self.axes2d.set_xlabel(_("Time [%s]") % self.unit)
- self.axes2d.set_ylabel(', '.join(self.yticksNames))
- # legend
- handles, labels = self.axes2d.get_legend_handles_labels()
- self.axes2d.legend(loc=0)
- self.listWhereConditions = []
- def drawV(self):
- for i, name in enumerate(self.plotNameListV):
- # just name; with mapset it would be long
- self.yticksNames.append(self.attribute.GetValue())
- self.yticksPos.append(0) # TODO
- xdata = []
- ydata = []
- for keys, values in self.timeDataV[name].iteritems():
- if keys in ['temporalType', 'granularity', 'validTopology',
- 'unit', 'temporalDataType']:
- continue
- xdata.append(self.convert(values['start_datetime']))
- ydata.append(values['value'])
- self.lookUp.AddDataset(yranges=ydata, xranges=xdata,
- datasetName=name)
- color = self.colors.next()
- self.plots.append(self.axes2d.plot(xdata, ydata, marker='o',
- color=color, label=name)[0])
- # ============================
- if self.temporalType == 'absolute':
- self.axes2d.set_xlabel(_("Temporal resolution: %s" % self.timeDataV[name]['granularity']))
- else:
- self.axes2d.set_xlabel(_("Time [%s]") % self.unit)
- self.axes2d.set_ylabel(', '.join(self.yticksNames))
- # legend
- handles, labels = self.axes2d.get_legend_handles_labels()
- self.axes2d.legend(loc=0)
- self.listWhereConditions = []
- def OnRedraw(self, event=None):
- """Required redrawing."""
- self.init()
- datasetsR = self.datasetSelectR.GetValue().strip()
- datasetsV = self.datasetSelectV.GetValue().strip()
- if not datasetsR and not datasetsV:
- return
- try:
- getcoors = self.coorval.coordsField.GetValue()
- except:
- try:
- getcoors = self.coorval.GetValue()
- except:
- getcoors = None
- if getcoors and getcoors != '':
- try:
- coordx, coordy = getcoors.split(',')
- coordx, coordy = float(coordx), float(coordy)
- except (ValueError, AttributeError):
- try:
- coordx, coordy = self.coorval.GetValue().split(',')
- coordx, coordy = float(coordx), float(coordy)
- except (ValueError, AttributeError):
- GMessage(message=_("Incorrect coordinates format, should "
- "be: x,y"), parent=self)
- coors = [coordx, coordy]
- if coors:
- try:
- self.poi = Point(float(coors[0]), float(coors[1]))
- except GException:
- GError(parent=self, message=_("Invalid input coordinates"),
- showTraceback=False)
- return
- # check raster dataset
- if datasetsR:
- datasetsR = datasetsR.split(',')
- try:
- datasetsR = self._checkDatasets(datasetsR, 'strds')
- if not datasetsR:
- return
- except GException:
- GError(parent=self, message=_("Invalid input raster dataset"),
- showTraceback=False)
- return
- self.datasetsR = datasetsR
- # check vector dataset
- if datasetsV:
- datasetsV = datasetsV.split(',')
- try:
- datasetsV = self._checkDatasets(datasetsV, 'stvds')
- if not datasetsV:
- return
- except GException:
- GError(parent=self, message=_("Invalid input vector dataset"),
- showTraceback=False)
- return
- self.datasetsV = datasetsV
- self._redraw()
- def _redraw(self):
- """Readraw data.
- Decides if to draw also 3D and adjusts layout if needed.
- """
- if self.datasetsR:
- self._getSTRDdata(self.datasetsR)
- if self.datasetsV:
- self._getSTVDData(self.datasetsV)
- # axes3d are physically removed
- if not self.axes2d:
- self.axes2d = self.fig.add_subplot(1, 1, 1)
- self._drawFigure()
- def _checkDatasets(self, datasets, typ):
- """Checks and validates datasets.
- Reports also type of dataset (e.g. 'strds').
- :param list datasets: list of temporal dataset's name
- :return: (mapName, mapset, type)
- """
- validated = []
- tDict = tgis.tlist_grouped(type=typ, group_type=True, dbif=self.dbif)
- # nested list with '(map, mapset, etype)' items
- allDatasets = [[[(map, mapset, etype) for map in maps]
- for etype, maps in etypesDict.iteritems()]
- for mapset, etypesDict in tDict.iteritems()]
- # flatten this list
- if allDatasets:
- allDatasets = reduce(lambda x, y: x + y, reduce(lambda x, y: x + y,
- allDatasets))
- mapsets = tgis.get_tgis_c_library_interface().available_mapsets()
- allDatasets = [i for i in sorted(allDatasets,
- key=lambda l: mapsets.index(l[1]))]
- for dataset in datasets:
- errorMsg = _("Space time dataset <%s> not found.") % dataset
- if dataset.find("@") >= 0:
- nameShort, mapset = dataset.split('@', 1)
- indices = [n for n, (mapName, mapsetName, etype) in enumerate(allDatasets)
- if nameShort == mapName and mapsetName == mapset]
- else:
- indices = [n for n, (mapName, mapset, etype) in enumerate(allDatasets)
- if dataset == mapName]
- if len(indices) == 0:
- raise GException(errorMsg)
- elif len(indices) >= 2:
- dlg = wx.SingleChoiceDialog(self,
- message=_("Please specify the "
- "space time dataset "
- "<%s>." % dataset),
- caption=_("Ambiguous dataset name"),
- choices=[("%(map)s@%(mapset)s:"
- " %(etype)s" % {'map': allDatasets[i][0],
- 'mapset': allDatasets[i][1],
- 'etype': allDatasets[i][2]})
- for i in indices],
- style=wx.CHOICEDLG_STYLE | wx.OK)
- if dlg.ShowModal() == wx.ID_OK:
- index = dlg.GetSelection()
- validated.append(allDatasets[indices[index]])
- else:
- continue
- else:
- validated.append(allDatasets[indices[0]])
- return validated
- def OnHelp(self, event):
- """Function to show help"""
- RunCommand(prog='g.manual', quiet=True, entry='g.gui.tplot')
- def SetDatasets(self, rasters, vectors, coors, cats, attr):
- """Set the data
- #TODO
- :param list rasters: a list of temporal raster dataset's name
- :param list vectors: a list of temporal vector dataset's name
- :param list coors: a list with x/y coordinates
- :param list cats: a list with incld. categories of vector
- :param str attr: name of atribute of vectror data
- """
- if not (rasters or vectors) or not (coors or cats):
- return
- try:
- if rasters:
- self.datasetsR = self._checkDatasets(rasters, 'strds')
- if vectors:
- self.datasetsV = self._checkDatasets(vectors, 'stvds')
- if not (self.datasetsR or self.datasetsV):
- return
- except GException:
- GError(parent=self, message=_("Invalid input temporal dataset"),
- showTraceback=False)
- return
- if coors:
- try:
- self.poi = Point(float(coors[0]), float(coors[1]))
- except GException:
- GError(parent=self, message=_("Invalid input coordinates"),
- showTraceback=False)
- return
- try:
- self.coorval.coordsField.SetValue(','.join(coors))
- except:
- self.coorval.SetValue(','.join(coors))
- if self.datasetsV:
- vdatas = ','.join(map(lambda x: x[0] + '@' + x[1], self.datasetsV))
- self.datasetSelectV.SetValue(vdatas)
- if attr:
- self.attribute.SetValue(attr)
- if cats:
- self.cats.SetValue(cats)
- if self.datasetsR:
- self.datasetSelectR.SetValue(','.join(map(lambda x: x[0] + '@' + x[1],
- self.datasetsR)))
- self._redraw()
- def OnVectorSelected(self, event):
- """Update the controlbox related to stvds"""
- dataset = self.datasetSelectV.GetValue().strip()
- vect_list = grass.read_command('t.vect.list', flags='s', input=dataset,
- col='name')
- vect_list = list(set(sorted(vect_list.split())))
- for vec in vect_list:
- self.attribute.InsertColumns(vec, 1)
- class LookUp:
- """Helper class for searching info by coordinates"""
- def __init__(self, timeData, convert):
- self.data = {}
- self.timeData = timeData
- self.convert = convert
- def AddDataset(self, yranges, xranges, datasetName):
- if len(yranges) != len(xranges):
- GError(parent=self, showTraceback=False,
- message=_("Datasets have different number of values"))
- return
- self.data[datasetName] = {}
- for i in range(len(xranges)):
- self.data[datasetName][xranges[i]] = yranges[i]
- def GetInformation(self, x):
- values = {}
- for key, value in self.data.iteritems():
- if value[x]:
- values[key] = [self.convert(x), value[x]]
- if len(values) == 0:
- return None
- return self.timeData, values
- def InfoFormat(timeData, values):
- """Formats information about dataset"""
- text = []
- for key, val in values.iteritems():
- etype = timeData[key]['temporalDataType']
- if etype == 'strds':
- text.append(_("Space time raster dataset: %s") % key)
- elif etype == 'stvds':
- text.append(_("Space time vector dataset: %s") % key)
- elif etype == 'str3ds':
- text.append(_("Space time 3D raster dataset: %s") % key)
- text.append(_("Value for {date} is {val}".format(date=val[0],
- val=val[1])))
- text.append('\n')
- text.append(_("Press Del to dismiss."))
- return '\n'.join(text)
- class DataCursor(object):
- """A simple data cursor widget that displays the x,y location of a
- matplotlib artist when it is selected.
- Source: http://stackoverflow.com/questions/4652439/
- is-there-a-matplotlib-equivalent-of-matlabs-datacursormode/4674445
- """
- def __init__(self, artists, lookUp, formatFunction, convert,
- tolerance=5, offsets=(-30, 20), display_all=False):
- """Create the data cursor and connect it to the relevant figure.
- "artists" is the matplotlib artist or sequence of artists that will be
- selected.
- "tolerance" is the radius (in points) that the mouse click must be
- within to select the artist.
- "offsets" is a tuple of (x,y) offsets in points from the selected
- point to the displayed annotation box
- "display_all" controls whether more than one annotation box will
- be shown if there are multiple axes. Only one will be shown
- per-axis, regardless.
- """
- self.lookUp = lookUp
- self.formatFunction = formatFunction
- self.offsets = offsets
- self.display_all = display_all
- if not cbook.iterable(artists):
- artists = [artists]
- self.artists = artists
- self.convert = convert
- self.axes = tuple(set(art.axes for art in self.artists))
- self.figures = tuple(set(ax.figure for ax in self.axes))
- self.annotations = {}
- for ax in self.axes:
- self.annotations[ax] = self.annotate(ax)
- for artist in self.artists:
- artist.set_picker(tolerance)
- for fig in self.figures:
- fig.canvas.mpl_connect('pick_event', self)
- fig.canvas.mpl_connect('key_press_event', self.keyPressed)
- def keyPressed(self, event):
- """Key pressed - hide annotation if Delete was pressed"""
- if event.key != 'delete':
- return
- for ax in self.axes:
- self.annotations[ax].set_visible(False)
- event.canvas.draw()
- def annotate(self, ax):
- """Draws and hides the annotation box for the given axis "ax"."""
- annotation = ax.annotate(self.formatFunction, xy=(0, 0), ha='center',
- xytext=self.offsets, va='bottom',
- textcoords='offset points',
- bbox=dict(boxstyle='round,pad=0.5',
- fc='yellow', alpha=0.7),
- arrowprops=dict(arrowstyle='->',
- connectionstyle='arc3,rad=0'),
- annotation_clip=False, multialignment='left')
- annotation.set_visible(False)
- return annotation
- def __call__(self, event):
- """Intended to be called through "mpl_connect"."""
- # Rather than trying to interpolate, just display the clicked coords
- # This will only be called if it's within "tolerance", anyway.
- x, y = event.mouseevent.xdata, event.mouseevent.ydata
- annotation = self.annotations[event.artist.axes]
- if x is not None:
- if not self.display_all:
- # Hide any other annotation boxes...
- for ann in self.annotations.values():
- ann.set_visible(False)
- if 'Line2D' in str(type(event.artist)):
- xData = []
- for a in event.artist.get_xdata():
- try:
- d = self.convert(a)
- except:
- d = a
- xData.append(d)
- x = xData[np.argmin(abs(xData - x))]
- info = self.lookUp.GetInformation(x)
- ys = zip(*info[1].values())[1]
- if not info:
- return
- # Update the annotation in the current axis..
- annotation.xy = x, max(ys)
- text = self.formatFunction(*info)
- annotation.set_text(text)
- annotation.set_visible(True)
- event.canvas.draw()
|