Newer
Older
# -*- coding: utf-8 -*-
"""
/***************************************************************************

Benjamin Jakimow
committed
EO Time Series Viewer
-------------------
begin : 2017-08-04
git sha : $Format:%H$
copyright : (C) 2017 by HU-Berlin
email : benjamin.jakimow@geo.hu-berlin.de
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
"""
# noinspection PyPep8Naming
import os, sys, pickle, datetime, re, collections
from collections import OrderedDict
from qgis.gui import *
from qgis.core import *
from PyQt5.Qt import *
from PyQt5.QtCore import *
from PyQt5.QtXml import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import numpy as np
import pyqtgraph as pg
from pyqtgraph import functions as fn
from pyqtgraph import AxisItem
from osgeo import ogr, osr, gdal
from timeseriesviewer.timeseries import TimeSeries, TimeSeriesDatum, SensorInstrument
from timeseriesviewer.plotstyling import PlotStyle
from timeseriesviewer.pixelloader import PixelLoader, PixelLoaderTask
from timeseriesviewer.utils import *
from timeseriesviewer.models import OptionListModel, Option
LABEL_EXPRESSION_2D = 'DN or Index'
LABEL_TIME = 'Date'
DEBUG = False
OPENGL_AVAILABLE = False
try:
import OpenGL
def qgsFieldFromKeyValue(fieldName, value):
t = type(value)
if t in [int, float] or np.isreal(value):
fLen = 0
fPrec = 0
fComm = ''
fType = ''
f = QgsField(fieldName, QVariant.Double, 'double', 40, 5)
else:
f = QgsField(fieldName, QVariant.String, 'text', 40, 5)
return f
def sensorExampleQgsFeature(sensor, singleBandOnly=False):
# populate with exemplary band values (generally stored as floats)
if sensor is None:
singleBandOnly = True
fieldValues = collections.OrderedDict()
if singleBandOnly:
fieldValues['b'] = 1.0
else:
for b in range(sensor.nb):
fn = bandIndex2bandKey(b)
fieldValues[fn] = 1.0
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
date = datetime.date.today()
doy = dateDOY(date)
fieldValues['doy'] = doy
fieldValues['date'] = str(date)
fields = QgsFields()
for k, v in fieldValues.items():
fields.append(qgsFieldFromKeyValue(k,v))
f = QgsFeature(fields)
for k, v in fieldValues.items():
f.setAttribute(k, v)
return f
def dateDOY(date):
if isinstance(date, np.datetime64):
date = date.astype(datetime.date)
return date.timetuple().tm_yday
def daysPerYear(year):
if isinstance(year, np.datetime64):
year = year.astype(datetime.date)
if isinstance(year, datetime.date):
year = year.timetuple().tm_year
return dateDOY(datetime.date(year=year, month=12, day=31))
def date2num(d):
#kindly taken from https://stackoverflow.com/questions/6451655/python-how-to-convert-datetime-dates-to-decimal-years
if isinstance(d, np.datetime64):
d = d.astype(datetime.datetime)
if isinstance(d, QDate):
d = datetime.date(d.year(), d.month(), d.day())
assert isinstance(d, datetime.date)
yearDuration = daysPerYear(d)
yearElapsed = d.timetuple().tm_yday
fraction = float(yearElapsed) / float(yearDuration)
if fraction == 1.0:
fraction = 0.9999999
return float(d.year) + fraction
def num2date(n, dt64=True, qDate=False):
n = float(n)
if n < 1:
n += 1
year = int(n)
fraction = n - year
yearDuration = daysPerYear(year)
yearElapsed = fraction * yearDuration
import math
doy = round(yearElapsed)
if doy < 1:
doy = 1
try:
date = datetime.date(year, 1, 1) + datetime.timedelta(days=doy-1)
except:
s = ""
if qDate:
return QDate(date.year, date.month, date.day)
if dt64:
return np.datetime64(date)
else:
return date
#return np.datetime64('{:04}-01-01'.format(year), 'D') + np.timedelta64(int(yearElapsed), 'D')
def saveTemporalProfiles(profiles, path, mode='all', sep=',', loadMissingValues=False):
if path is None or len(path) == 0:
return
assert mode in ['coordinate','all']
nbMax = 0
for sensor in profiles[0].timeSeries().sensors():
assert isinstance(sensor, SensorInstrument)
nbMax = max(nbMax, sensor.nb)
ext = os.path.splitext(path)[1].lower()
assert isinstance(ext, str)
if ext.startswith('.'):
ext = ext[1:]
if loadMissingValues:
for p in profiles:
assert isinstance(p, TemporalProfile)
p.loadMissingData()
#write a flat list of profiles
lines = ['Temporal Profiles']
lines.append(sep.join(['pid', 'name', 'date', 'img', 'location', 'band values']))
for nP, p in enumerate(profiles):
assert isinstance(p, TemporalProfile)
lines.append('Profile {} "{}": {}'.format(p.mID, p.name(), p.mCoordinate))
assert isinstance(p, TemporalProfile)
c = p.coordinate()
for i, tsd in enumerate(p.mTimeSeries):
assert isinstance(tsd, TimeSeriesDatum)
data = p.data(tsd)
line = [p.id(), p.name(), data['date'], tsd.pathImg, c.asWkt()]
for b in range(tsd.sensor.nb):
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
key = 'b{}'.format(b+1)
if key in data.keys():
line.append(data[key])
else:
line.append('')
line = sep.join([str(l) for l in line])
lines.append(line)
file = open(path, 'w', encoding='utf8')
file.writelines('\n'.join(lines))
file.flush()
file.close()
else:
drv = None
for i in range(ogr.GetDriverCount()):
d = ogr.GetDriver(i)
driverExtensions = d.GetMetadataItem('DMD_EXTENSIONS')
if driverExtensions is not None and ext in driverExtensions:
drv = d
break
if not isinstance(drv, ogr.Driver):
raise Exception('Unable to find a OGR driver to write {}'.format(path))
drvMEM = ogr.GetDriverByName('Memory')
assert isinstance(drvMEM, ogr.Driver)
dsMEM = drvMEM.CreateDataSource('')
srs = osr.SpatialReference()
srs.ImportFromEPSG(4326)
crs = QgsCoordinateReferenceSystem('EPSG:4326')
assert isinstance(dsMEM, ogr.DataSource)
#lines.append(sep.join(['pid', 'name', 'date', 'img', 'location', 'band values']))
lyr = dsMEM.CreateLayer(os.path.basename(path), srs, ogr.wkbPoint)
lyr.CreateField(ogr.FieldDefn('pid', ogr.OFTInteger64))
lyr.CreateField(ogr.FieldDefn('name', ogr.OFTString))
lyr.CreateField(ogr.FieldDefn('date', ogr.OFTDateTime))
lyr.CreateField(ogr.FieldDefn('doy', ogr.OFTInteger))
lyr.CreateField(ogr.FieldDefn('img', ogr.OFTString))
lyr.CreateField(ogr.FieldDefn('lat', ogr.OFTReal))
lyr.CreateField(ogr.FieldDefn('lon', ogr.OFTReal))
lyr.CreateField(ogr.FieldDefn('nodata', ogr.OFTBinary))
for b in range(nbMax):
lyr.CreateField(ogr.FieldDefn('b{}'.format(b+1), ogr.OFTReal))
for iP, p in enumerate(profiles):
assert isinstance(p, TemporalProfile)
c = p.coordinate().toCrs(crs)
assert isinstance(c, SpatialPoint)
for iT, tsd in enumerate(p.timeSeries()):
feature = ogr.Feature(lyr.GetLayerDefn())
assert isinstance(feature, ogr.Feature)
data = p.data(tsd)
assert isinstance(tsd, TimeSeriesDatum)
feature.SetField('pid', p.id())
feature.SetField('name', p.name())
feature.SetField('date', str(tsd.date))
feature.SetField('doy', tsd.doy)
feature.SetField('img', tsd.pathImg)
feature.SetField('lon', c.x())
feature.SetField('lat', c.y())
point = ogr.CreateGeometryFromWkt(c.asWkt())
feature.SetGeometry(point)
if 'nodata' in data.keys():
feature.SetField('nodata', data['nodata'])
for b in range(tsd.sensor.nb):
key = 'b{}'.format(b+1)
if key in data.keys():
feature.SetField(key, data[key])
lyr.CreateFeature(feature)
drv.CopyDataSource(dsMEM, path)
regBandKey = re.compile(r"(?<!\w)b\d+(?!\w)", re.IGNORECASE)
regBandKeyExact = re.compile(r'^' + regBandKey.pattern + '$', re.IGNORECASE)
def bandIndex2bandKey(i):
assert isinstance(i, int)
assert i >= 0
return 'b{}'.format(i + 1)
def bandKey2bandIndex(key):
match = regBandKeyExact.search(key)
assert match
idx = int(match.group()[1:]) - 1
return idx
class DateTimePlotWidget(pg.PlotWidget):
"""
Subclass of PlotWidget
"""
def __init__(self, parent=None):
"""
Constructor of the widget
"""
super(DateTimePlotWidget, self).__init__(parent)
self.plotItem = pg.PlotItem(
axisItems={'bottom':DateTimeAxis(orientation='bottom')}
,viewBox=DateTimeViewBox()
)
self.setCentralItem(self.plotItem)
#self.xAxisInitialized = False
pi = self.getPlotItem()
pi.getAxis('bottom').setLabel(LABEL_TIME)
pi.getAxis('left').setLabel(LABEL_EXPRESSION_2D)
self.mInfoColor = QColor('yellow')
self.mCrosshairLineV = pg.InfiniteLine(angle=90, movable=False)
self.mCrosshairLineH = pg.InfiniteLine(angle=0, movable=False)
self.mInfoLabelCursor = pg.TextItem(text='<cursor position>', anchor=(1.0, 0.0))
self.mInfoLabelCursor.setColor(QColor('yellow'))
self.scene().addItem(self.mInfoLabelCursor)
self.mInfoLabelCursor.setParentItem(self.getPlotItem())
#self.plot2DLabel.setAnchor()
#self.plot2DLabel.anchor(itemPos=(0, 0), parentPos=(0, 0), offset=(0, 0))
pi.addItem(self.mCrosshairLineV, ignoreBounds=True)
pi.addItem(self.mCrosshairLineH, ignoreBounds=True)
self.proxy2D = pg.SignalProxy(self.scene().sigMouseMoved, rateLimit=60, slot=self.onMouseMoved2D)
def onMouseMoved2D(self, evt):
pos = evt[0] ## using signal proxy turns original arguments into a tuple
plotItem = self.getPlotItem()
if plotItem.sceneBoundingRect().contains(pos):

benjamin.jakimow@geo.hu-berlin.de
committed
vb = plotItem.vb
assert isinstance(vb, DateTimeViewBox)
mousePoint = vb.mapSceneToView(pos)
Loading
Loading full blame...