Newer
Older
# -*- coding: utf-8 -*-
# noinspection PyPep8Naming
"""
***************************************************************************
spectrallibraries.py
Spectral Profiles and Libraries for a GUI context.
---------------------
Date : Juli 2017
Copyright : (C) 2017 by Benjamin Jakimow
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. *
* *
***************************************************************************
"""
#see http://python-future.org/str_literals.html for str issue discussion

Benjamin Jakimow
committed
import os, re, tempfile, pickle, copy, shutil, locale
from collections import OrderedDict
from qgis.core import *
from qgis.gui import *

Benjamin Jakimow
committed
from qgis.utils import qgsfunction
from qgis.PyQt.QtCore import *
from qgis.PyQt.QtGui import *
from qgis.PyQt.QtWidgets import *

Benjamin Jakimow
committed
from qgis.core import QgsField, QgsFields, QgsFeature, QgsMapLayer, QgsVectorLayer, QgsConditionalStyle
from qgis.gui import QgsMapCanvas, QgsDockWidget
from pyqtgraph.widgets.PlotWidget import PlotWidget
from pyqtgraph.graphicsItems.PlotDataItem import PlotDataItem

Benjamin Jakimow
committed
from pyqtgraph.graphicsItems.PlotItem import PlotItem

Benjamin Jakimow
committed
import pyqtgraph.functions as fn
import numpy as np
from osgeo import gdal, gdal_array
from timeseriesviewer.utils import *
from timeseriesviewer.virtualrasters import *
from timeseriesviewer.models import *

Benjamin Jakimow
committed
from timeseriesviewer.plotstyling import PlotStyle, PlotStyleDialog, MARKERSYMBOLS2QGIS_SYMBOLS

benjamin.jakimow@geo.hu-berlin.de
committed
FILTERS = 'ENVI Spectral Library (*.esl *.sli);;CSV Table (*.csv)'
PICKLE_PROTOCOL = pickle.HIGHEST_PROTOCOL
HIDDEN_ATTRIBUTE_PREFIX = '__serialized__'
CURRENT_SPECTRUM_STYLE = PlotStyle()
CURRENT_SPECTRUM_STYLE.linePen.setStyle(Qt.SolidLine)
CURRENT_SPECTRUM_STYLE.linePen.setColor(Qt.green)

Benjamin Jakimow
committed
DEFAULT_SPECTRUM_STYLE = PlotStyle()
DEFAULT_SPECTRUM_STYLE.linePen.setStyle(Qt.SolidLine)
DEFAULT_SPECTRUM_STYLE.linePen.setColor(Qt.white)
#CURRENT_SPECTRUM_STYLE.linePen
#pdi.setPen(fn.mkPen(QColor('green'), width=3))
def gdalDataset(pathOrDataset, eAccess=gdal.GA_ReadOnly):
"""
:param pathOrDataset: path or gdal.Dataset
:return: gdal.Dataset
"""
if isinstance(pathOrDataset, QgsRasterLayer):
return gdalDataset(pathOrDataset.source())
if not isinstance(pathOrDataset, gdal.Dataset):
pathOrDataset = gdal.Open(pathOrDataset, eAccess)
assert isinstance(pathOrDataset, gdal.Dataset)
return pathOrDataset

Benjamin Jakimow
committed
80
81
82
83
84
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
119
120
121
122
123
@qgsfunction(0, "Spectral Libraries")
def plotStyleSymbolFillColor(values, feature, parent):
if isinstance(feature, QgsFeature):
i = feature.fieldNameIndex(HIDDEN_ATTRIBUTE_PREFIX+'style')
if i >= 0:
style = pickle.loads(feature.attribute(i))
if isinstance(style, PlotStyle):
r,g,b,a = style.markerBrush.color().getRgb()
return '{},{},{},{}'.format(r,g,b, a)
return None
@qgsfunction(0, "Spectral Libraries")
def plotStyleSymbol(values, feature, parent):
if isinstance(feature, QgsFeature):
i = feature.fieldNameIndex(HIDDEN_ATTRIBUTE_PREFIX+'style')
if i >= 0:
style = pickle.loads(feature.attribute(i))
if isinstance(style, PlotStyle):
symbol = style.markerSymbol
qgisSymbolString = MARKERSYMBOLS2QGIS_SYMBOLS.get(symbol)
if isinstance(qgisSymbolString, str):
return qgisSymbolString
return None
@qgsfunction(0, "Spectral Libraries")
def plotStyleSymbolSize(values, feature, parent):
if isinstance(feature, QgsFeature):
i = feature.fieldNameIndex(HIDDEN_ATTRIBUTE_PREFIX+'style')
if i >= 0:
style = pickle.loads(feature.attribute(i))
if isinstance(style, PlotStyle):
return style.markerSize
return None
QgsExpression.registerFunction(plotStyleSymbolFillColor)
QgsExpression.registerFunction(plotStyleSymbol)
QgsExpression.registerFunction(plotStyleSymbolSize)
#Lookup table for ENVI IDL DataTypes to GDAL Data Types
LUT_IDL2GDAL = {1:gdal.GDT_Byte,
12:gdal.GDT_UInt16,
2:gdal.GDT_Int16,
13:gdal.GDT_UInt32,
3:gdal.GDT_Int32,
4:gdal.GDT_Float32,
5:gdal.GDT_Float64,
#:gdal.GDT_CInt16,
#8:gdal.GDT_CInt32,
6:gdal.GDT_CFloat32,
9:gdal.GDT_CFloat64}

Benjamin Jakimow
committed
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
def createStandardFields():
fields = QgsFields()
"""
Parameters
name Field name type Field variant type, currently supported: String / Int / Double
typeName Field type (e.g., char, varchar, text, int, serial, double). Field types are usually unique to the source and are stored exactly as returned from the data store.
len Field length
prec Field precision. Usually decimal places but may also be used in conjunction with other fields types (e.g., variable character fields)
comment Comment for the field
subType If the field is a collection, its element's type. When all the elements don't need to have the same type, leave this to QVariant::Invalid.
"""
fields.append(createQgsField('name', ''))
fields.append(createQgsField('px_x', 0))
fields.append(createQgsField('px_y', 0))
fields.append(createQgsField('x_unit', ''))
fields.append(createQgsField('y_unit', ''))
fields.append(createQgsField('source', ''))
fields.append(createQgsField(HIDDEN_ATTRIBUTE_PREFIX + 'xvalues', ''))
fields.append(createQgsField(HIDDEN_ATTRIBUTE_PREFIX + 'yvalues', ''))
fields.append(createQgsField(HIDDEN_ATTRIBUTE_PREFIX + 'style', ''))
"""
fields.append(QgsField('name', QVariant.String,'varchar', 25))
fields.append(QgsField('px_x', QVariant.Int, 'int'))
fields.append(QgsField('px_y', QVariant.Int, 'int'))
fields.append(QgsField('x_unit', QVariant.String, 'varchar', 5))
fields.append(QgsField('y_unit', QVariant.String, 'varchar', 5))
fields.append(QgsField('source', QVariant.String, 'varchar', 5))
"""
return fields
def value2str(value, sep=' '):
if isinstance(value, list):
value = sep.join([str(v) for v in value])
elif isinstance(value, np.array):
value = value2str(value.astype(list), sep=sep)
elif value is None:
return value

Benjamin Jakimow
committed
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
class AddAttributeDialog(QDialog):
def __init__(self, layer, parent=None):
assert isinstance(layer, QgsVectorLayer)
super(AddAttributeDialog, self).__init__(parent)
assert isinstance(layer, QgsVectorLayer)
self.mLayer = layer
self.setWindowTitle('Add Field')
l = QGridLayout()
self.tbName = QLineEdit('Name')
self.tbName.setPlaceholderText('Name')
self.tbName.textChanged.connect(self.validate)
l.addWidget(QLabel('Name'), 0,0)
l.addWidget(self.tbName, 0, 1)
self.tbComment = QLineEdit()
self.tbComment.setPlaceholderText('Comment')
l.addWidget(QLabel('Comment'), 1, 0)
l.addWidget(self.tbComment, 1, 1)
self.cbType = QComboBox()
self.typeModel = OptionListModel()
for ntype in self.mLayer.dataProvider().nativeTypes():
assert isinstance(ntype, QgsVectorDataProvider.NativeType)
o = Option(ntype,name=ntype.mTypeName, tooltip=ntype.mTypeDesc)
self.typeModel.addOption(o)
self.cbType.setModel(self.typeModel)
self.cbType.currentIndexChanged.connect(self.onTypeChanged)
l.addWidget(QLabel('Type'), 2, 0)
l.addWidget(self.cbType, 2, 1)
self.sbLength = QSpinBox()
self.sbLength.setRange(0, 99)
self.sbLength.valueChanged.connect(lambda : self.setPrecisionMinMax())
self.lengthLabel = QLabel('Length')
l.addWidget(self.lengthLabel, 3, 0)
l.addWidget(self.sbLength, 3, 1)
self.sbPrecision = QSpinBox()
self.sbPrecision.setRange(0, 99)
self.precisionLabel = QLabel('Precision')
l.addWidget(self.precisionLabel, 4, 0)
l.addWidget(self.sbPrecision, 4, 1)
self.tbValidationInfo = QLabel()
self.tbValidationInfo.setStyleSheet("QLabel { color : red}")
l.addWidget(self.tbValidationInfo, 5, 0, 1, 2)
self.buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttons.button(QDialogButtonBox.Ok).clicked.connect(self.accept)
self.buttons.button(QDialogButtonBox.Cancel).clicked.connect(self.reject)
l.addWidget(self.buttons, 6, 1)
self.setLayout(l)
self.mLayer = layer
self.onTypeChanged()
def accept(self):
msg = self.validate()
if len(msg) > 0:
QMessageBox.warning(self, "Add Field", msg)
else:
super(AddAttributeDialog, self).accept()
def field(self):
"""
Returns the new QgsField
:return:
"""
ntype = self.currentNativeType()
return QgsField(name=self.tbName.text(),
type=QVariant(ntype.mType).type(),
typeName=ntype.mTypeName,
len=self.sbLength.value(),
prec=self.sbPrecision.value(),
comment=self.tbComment.text())
def currentNativeType(self):
return self.cbType.currentData().value()
def onTypeChanged(self, *args):
ntype = self.currentNativeType()
vMin , vMax = ntype.mMinLen, ntype.mMaxLen
assert isinstance(ntype, QgsVectorDataProvider.NativeType)
isVisible = vMin < vMax
self.sbLength.setVisible(isVisible)
self.lengthLabel.setVisible(isVisible)
self.setSpinBoxMinMax(self.sbLength, vMin , vMax)
self.setPrecisionMinMax()
def setPrecisionMinMax(self):
ntype = self.currentNativeType()
vMin, vMax = ntype.mMinPrec, ntype.mMaxPrec
isVisible = vMin < vMax
self.sbPrecision.setVisible(isVisible)
self.precisionLabel.setVisible(isVisible)
vMax = max(ntype.mMinPrec, min(ntype.mMaxPrec, self.sbLength.value()))
self.setSpinBoxMinMax(self.sbPrecision, vMin, vMax)
def setSpinBoxMinMax(self, sb, vMin, vMax):
assert isinstance(sb, QSpinBox)
value = sb.value()
sb.setRange(vMin, vMax)
if value > vMax:
sb.setValue(vMax)
elif value < vMin:
sb.setValue(vMin)
def validate(self):
msg = []
name = self.tbName.text()
if name in self.mLayer.fields().names():
msg.append('Field name "{}" already exists.'.format(name))
elif name == '':
msg.append('Missing field name')
elif name == 'shape':
msg.append('Field name "{}" already reserved.'.format(name))
msg = '\n'.join(msg)
self.buttons.button(QDialogButtonBox.Ok).setEnabled(len(msg) == 0)
self.tbValidationInfo.setText(msg)
return msg
class SpectralLibraryTableFilterModel(QgsAttributeTableFilterModel):
def __init__(self, sourceModel, parent=None):
dummyCanvas = QgsMapCanvas()
dummyCanvas.setDestinationCrs(SpectralProfile.crs)
dummyCanvas.setExtent(QgsRectangle(-180,-90,180,90))
super(SpectralLibraryTableFilterModel, self).__init__(dummyCanvas, sourceModel, parent=parent)
self.mDummyCanvas = dummyCanvas
#self.setSelectedOnTop(True)
class SpectralLibraryTableView(QgsAttributeTableView):
def __init__(self, parent=None):
super(SpectralLibraryTableView, self).__init__(parent)

Benjamin Jakimow
committed
#self.setSelectionBehavior(QAbstractItemView.SelectRows)
#self.setSelectionMode(QAbstractItemView.SingleSelection)

Benjamin Jakimow
committed
self.horizontalHeader().setSectionsMovable(True)
self.willShowContextMenu.connect(self.onWillShowContextMenu)
self.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)

Benjamin Jakimow
committed
super(SpectralLibraryTableView, self).setModel(filterModel)
self.mSelectionManager = SpectralLibraryFeatureSelectionManager(self.model().layer())
self.setFeatureSelectionManager(self.mSelectionManager)

Benjamin Jakimow
committed
#self.selectionModel().selectionChanged.connect(self.onSelectionChanged)
#def contextMenuEvent(self, event):
def onWillShowContextMenu(self, menu, index):
assert isinstance(menu, QMenu)
assert isinstance(index, QModelIndex)

Benjamin Jakimow
committed
featureIDs = self.spectralLibrary().selectedFeatureIds()
if len(featureIDs) == 0 and index.isValid():
if isinstance(self.model(), QgsAttributeTableFilterModel):
index = self.model().mapToSource(index)
if index.isValid():
featureIDs.append(self.model().sourceModel().feature(index).id())
elif isinstance(self.model(), QgsAttributeTableFilterModel):
featureIDs.append(self.model().feature(index).id())

Benjamin Jakimow
committed

Benjamin Jakimow
committed
m = menu.addMenu('Copy...')
a = m.addAction("Values")
a.triggered.connect(lambda b, ids=featureIDs, mode=ClipboardIO.WritingModes.VALUES: self.onCopy2Clipboard(ids, mode))
a = m.addAction("Attributes")
a.triggered.connect(lambda b, ids=featureIDs, mode=ClipboardIO.WritingModes.ATTRIBUTES: self.onCopy2Clipboard(ids, mode))
a = m.addAction("Values + Attributes")
a.triggered.connect(lambda b, ids=featureIDs, mode=ClipboardIO.WritingModes.ALL: self.onCopy2Clipboard(ids, mode))

Benjamin Jakimow
committed
a = menu.addAction('Save as...')
a.triggered.connect(lambda b, ids=featureIDs : self.onSaveToFile(ids))
menu.addSeparator()

Benjamin Jakimow
committed
a = menu.addAction('Set Style')
a.triggered.connect(lambda b, ids=featureIDs : self.onSetStyle(ids))

Benjamin Jakimow
committed
a = menu.addAction('Check')
a.triggered.connect(lambda : self.setCheckState(featureIDs, Qt.Checked))
a = menu.addAction('Uncheck')
a.triggered.connect(lambda: self.setCheckState(featureIDs, Qt.Unchecked))
menu.addSeparator()
for a in self.actions():
menu.addAction(a)
def spectralLibrary(self):
return self.model().layer()

Benjamin Jakimow
committed
assert isinstance(fids, list)
speclib = self.spectralLibrary()
assert isinstance(speclib, SpectralLibrary)
speclib = speclib.speclibFromFeatureIDs(fids)
ClipboardIO.write(speclib, mode=mode)
s = ""
def onSaveToFile(self, fids):
speclib = self.spectralLibrary()
assert isinstance(speclib, SpectralLibrary)
speclib.getFeatures(fids)
speclib.exportProfiles()

Benjamin Jakimow
committed
def fidsToIndices(self, fids):

Benjamin Jakimow
committed
Converts feature ids into FilterModel QModelIndices
:param fids: [list-of-int]
:return:

Benjamin Jakimow
committed
if isinstance(fids, int):
fids = [fids]
assert isinstance(fids, list)

Benjamin Jakimow
committed
indices = [fmodel.fidToIndex(id) for id in fids]

Benjamin Jakimow
committed

Benjamin Jakimow
committed
def onRemoveFIDs(self, fids):
speclib = self.spectralLibrary()
assert isinstance(speclib, SpectralLibrary)

Benjamin Jakimow
committed
b = speclib.isEditable()

Benjamin Jakimow
committed
speclib.startEditing()

Benjamin Jakimow
committed
speclib.deleteFeatures(fids)

Benjamin Jakimow
committed
saveEdits(speclib, leaveEditable=b)

Benjamin Jakimow
committed
def onSetStyle(self, ids):
if len(ids) == 0:
return
speclib = self.spectralLibrary()
assert isinstance(speclib, SpectralLibrary)
profiles = speclib.profiles(ids)
refProfile = profiles[0]
styleDefault = refProfile.style()
refStyle = PlotStyleDialog.getPlotStyle(plotStyle=styleDefault)

Benjamin Jakimow
committed
if isinstance(refStyle, PlotStyle):
refProfile.setStyle(refStyle)

Benjamin Jakimow
committed
iStyle = speclib.fields().indexFromName(HIDDEN_ATTRIBUTE_PREFIX+'style')
assert iStyle >= 0
if isinstance(refStyle, PlotStyle):

Benjamin Jakimow
committed
b = speclib.isEditable()

Benjamin Jakimow
committed
speclib.startEditing()
for f in profiles:
assert isinstance(f, SpectralProfile)
oldStyle = f.style()
refStyle.setVisibility(oldStyle.isVisible())
speclib.changeAttributeValue(f.id(), iStyle, pickle.dumps(refStyle), f.attributes()[iStyle])

Benjamin Jakimow
committed
saveEdits(speclib, leaveEditable=b)

Benjamin Jakimow
committed

Benjamin Jakimow
committed

Benjamin Jakimow
committed
def setCheckState(self, fids, checkState):

Benjamin Jakimow
committed
speclib = self.spectralLibrary()
speclib.startEditing()

Benjamin Jakimow
committed
profiles = speclib.profiles(fids)

Benjamin Jakimow
committed
iStyle = speclib.fields().indexFromName(HIDDEN_ATTRIBUTE_PREFIX + 'style')
setVisible = checkState == Qt.Checked

Benjamin Jakimow
committed
b = speclib.isEditable()
speclib.startEditing()

Benjamin Jakimow
committed
for p in profiles:
assert isinstance(p, SpectralProfile)
oldStyle = p.style()
assert isinstance(oldStyle, PlotStyle)
if oldStyle.isVisible() != setVisible:
newStyle = p.style()
newStyle.setVisibility(setVisible)
p.setStyle(newStyle)
speclib.changeAttributeValue(p.id(), iStyle, p.attributes()[iStyle], oldStyle)

Benjamin Jakimow
committed
saveEdits(speclib, leaveEditable=b)

Benjamin Jakimow
committed
def dropEvent(self, event):
assert isinstance(event, QDropEvent)
mimeData = event.mimeData()
if self.model().rowCount() == 0:
index = self.model().createIndex(0,0)
else:
index = self.indexAt(event.pos())
if mimeData.hasFormat(mimedata.MDF_SPECTRALLIBRARY):
self.model().dropMimeData(mimeData, event.dropAction(), index.row(), index.column(), index.parent())
event.accept()
def dragEnterEvent(self, event):
assert isinstance(event, QDragEnterEvent)
if event.mimeData().hasFormat(mimedata.MDF_SPECTRALLIBRARY):
event.accept()
def dragMoveEvent(self, event):
assert isinstance(event, QDragMoveEvent)
if event.mimeData().hasFormat(mimedata.MDF_SPECTRALLIBRARY):
event.accept()
s = ""
def mimeTypes(self):
pass
"""
class SpectralProfileMapTool(QgsMapToolEmitPoint):
sigProfileRequest = pyqtSignal(SpatialPoint, QgsMapCanvas)
def __init__(self, canvas, showCrosshair=True):
self.mShowCrosshair = showCrosshair
self.mCanvas = canvas
QgsMapToolEmitPoint.__init__(self, self.mCanvas)
self.marker = QgsVertexMarker(self.mCanvas)
self.rubberband = QgsRubberBand(self.mCanvas, QgsWkbTypes.PolygonGeometry)
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
color = QColor('red')
self.rubberband.setLineStyle(Qt.SolidLine)
self.rubberband.setColor(color)
self.rubberband.setWidth(2)
self.marker.setColor(color)
self.marker.setPenWidth(3)
self.marker.setIconSize(5)
self.marker.setIconType(QgsVertexMarker.ICON_CROSS) # or ICON_CROSS, ICON_X
def canvasPressEvent(self, e):
geoPoint = self.toMapCoordinates(e.pos())
self.marker.setCenter(geoPoint)
#self.marker.show()
def setStyle(self, color=None, brushStyle=None, fillColor=None, lineStyle=None):
if color:
self.rubberband.setColor(color)
if brushStyle:
self.rubberband.setBrushStyle(brushStyle)
if fillColor:
self.rubberband.setFillColor(fillColor)
if lineStyle:
self.rubberband.setLineStyle(lineStyle)
def canvasReleaseEvent(self, e):
pixelPoint = e.pixelPoint()
crs = self.mCanvas.mapSettings().destinationCrs()
self.marker.hide()
geoPoint = self.toMapCoordinates(pixelPoint)
if self.mShowCrosshair:
#show a temporary crosshair
ext = SpatialExtent.fromMapCanvas(self.mCanvas)
cen = geoPoint
geom = QgsGeometry()
geom.addPart([QgsPointXY(ext.upperLeftPt().x(),cen.y()), QgsPointXY(ext.lowerRightPt().x(), cen.y())],
geom.addPart([QgsPointXY(cen.x(), ext.upperLeftPt().y()), QgsPointXY(cen.x(), ext.lowerRightPt().y())],
self.rubberband.addGeometry(geom, None)
self.rubberband.show()
#remove crosshair after 0.1 sec
QTimer.singleShot(100, self.hideRubberband)
self.sigProfileRequest.emit(SpatialPoint(crs, geoPoint), self.mCanvas)
def hideRubberband(self):
self.rubberband.reset()
"""

Benjamin Jakimow
committed
class SpectralProfilePlotDataItem(PlotDataItem):
def __init__(self, spectralProfile):
assert isinstance(spectralProfile, SpectralProfile)
super(SpectralProfilePlotDataItem, self).__init__()
self.mProfile = spectralProfile

Benjamin Jakimow
committed
self.setData(x=spectralProfile.xValues(), y=spectralProfile.yValues())
self.setStyle(self.mProfile.style())
def setClickable(self, b, width=None):
assert isinstance(b, bool)
self.curve.setClickable(b, width=width)

Benjamin Jakimow
committed
def setStyle(self, style):
assert isinstance(style, PlotStyle)
self.setVisible(style.isVisible())
self.setSymbol(style.markerSymbol)
self.setSymbolBrush(style.markerBrush)
self.setSymbolSize(style.markerSize)
self.setSymbolPen(style.markerPen)
self.setPen(style.linePen)
def setColor(self, color):
if not isinstance(color, QColor):
color = QColor(color)
self.setPen(color)
def pen(self):
return fn.mkPen(self.opts['pen'])
def color(self):
return self.pen().color()
def setLineWidth(self, width):
from pyqtgraph.functions import mkPen
pen = mkPen(self.opts['pen'])
assert isinstance(pen, QPen)
pen.setWidth(width)
self.setPen(pen)
crs = QgsCoordinateReferenceSystem('EPSG:4326')
@staticmethod
def fromMapCanvas(mapCanvas, position):
"""
Returns a list of Spectral Profiles the raster layers in QgsMapCanvas mapCanvas.
:param mapCanvas:
:param position:
"""
assert isinstance(mapCanvas, QgsMapCanvas)
from timeseriesviewer.mapcanvas import MapCanvas
if isinstance(mapCanvas, MapCanvas):
sources = mapCanvas.layerModel().rasterLayerInfos()
sources = [s.mSrc for s in sources]
else:
layers = [l for l in mapCanvas.layers() if isinstance(l, QgsRasterLayer)]
sources = [l.source() for l in layers]
return SpectralProfile.fromRasterSources(sources, position)
@staticmethod
def fromRasterSources(sources, position):
"""
Returns a list of Spectral Profiles
:param sources: list-of-raster-sources, e.g. file paths, gdal.Datasets, QgsRasterLayers
:param position:
:return:
"""
profiles = [SpectralProfile.fromRasterSource(s, position) for s in sources]
return [p for p in profiles if isinstance(p, SpectralProfile)]
@staticmethod
def fromRasterSource(source, position):
"""
Returns the Spectral Profiles from source at position `position`
:param source: path or gdal.Dataset
:param position:
:return: SpectralProfile
"""
ds = gdalDataset(source)
files = ds.GetFileList()
if len(files) > 0:
baseName = os.path.basename(files[0])
else:
baseName = 'Spectrum'
crs = QgsCoordinateReferenceSystem(ds.GetProjection())
gt = ds.GetGeoTransform()
if isinstance(position, QPoint):
px = position
elif isinstance(position, SpatialPoint):
px = geo2px(position.toCrs(crs), gt)
elif isinstance(position, QgsPointXY):
px = geo2px(position, ds.GetGeoTransform())
else:
raise Exception('Unsupported type of argument "position" {}'.format('{}'.format(position)))
#check out-of-raster
if px.x() < 0 or px.y() < 0: return None
if px.x() > ds.RasterXSize - 1 or px.y() > ds.RasterYSize - 1: return None
values = ds.ReadAsArray(px.x(), px.y(), 1, 1)
values = values.flatten()
for b in range(ds.RasterCount):
band = ds.GetRasterBand(b+1)
nodata = band.GetNoDataValue()
if nodata and values[b] == nodata:
return None
wl = ds.GetMetadataItem(str('wavelength'), str('ENVI'))
wlu = ds.GetMetadataItem(str('wavelength_units'), str('ENVI'))
if wl is not None and len(wl) > 0:
wl = re.sub(r'[ {}]','', wl).split(',')
wl = [float(w) for w in wl]
profile = SpectralProfile()
profile.setName('{} x{} y{}'.format(baseName, px.x(), px.y()))
#profile.setValues(values, valuePositions=wl, valuePositionUnit=wlu)
profile.setYValues(values)
if wl is not None:
profile.setXValues(wl, unit=wlu)
profile.setCoordinates(px=px, pt=SpatialPoint(crs, px2geo(px, gt)))
profile.setSource('{}'.format(ds.GetFileList()[0]))
return profile
@staticmethod
def fromSpecLibFeature(feature):
sp = SpectralProfile(fields=feature.fields())
sp.setAttributes(feature.attributes())
return sp
XVALUES_FIELD = HIDDEN_ATTRIBUTE_PREFIX+'xvalues'
YVALUES_FIELD = HIDDEN_ATTRIBUTE_PREFIX + 'yvalues'
STYLE_FIELD = HIDDEN_ATTRIBUTE_PREFIX + 'style'
def __init__(self, parent=None, fields=None, xUnit='index', yUnit=None):

Benjamin Jakimow
committed
fields = createStandardFields()
#QgsFeature.__init__(self, fields)
#QObject.__init__(self)
#QObject.__init__(self)
fields = self.fields()
assert isinstance(fields, QgsFields)
self.setXUnit(xUnit)
self.setYUnit(yUnit)

Benjamin Jakimow
committed
self.setStyle(DEFAULT_SPECTRUM_STYLE)
def fieldNames(self):
return self.fields().names()
def setName(self, name:str):
if name != self.name():
self.setAttribute('name', name)
#self.sigNameChanged.emit(name)
def name(self):
return self.metadata('name')
def setSource(self, uri: str):
self.setAttribute('source', uri)
def source(self):
return self.metadata('source')
if isinstance(px, QPoint):
self.setAttribute('px_x', px.x())
self.setAttribute('px_y', px.y())
if isinstance(pt, SpatialPoint):
sp = pt.toCrs(SpectralProfile.crs)
self.setGeometry(QgsGeometry.fromPointXY(sp))
def pxCoordinate(self):
x = self.attribute('px_x')
y = self.attribute('px_y')
if x == None or y == None:
return None
return QPoint(x, y)
def geoCoordinate(self):
def isValid(self):
return len(self.mValues) > 0 and self.mValueUnit is not None
def setXValues(self, values, unit=None):
if isinstance(values, np.ndarray):
values = values.tolist()
assert isinstance(values, list)
self.setMetadata(SpectralProfile.XVALUES_FIELD, values)
if isinstance(unit, str):
self.setMetadata('x_unit', unit)
def setYValues(self, values, unit=None):
if isinstance(values, np.ndarray):
values = values.tolist()
assert isinstance(values, list)
self.setMetadata(SpectralProfile.YVALUES_FIELD, values)
if isinstance(unit, str):
self.setMetadata('y_unit', unit)
if self.xValues() is None:
self.setXValues(list(range(len(values))), unit='index')
def style(self):
return self.metadata(SpectralProfile.STYLE_FIELD)
def setStyle(self, style):
assert isinstance(style, PlotStyle)
self.setMetadata(SpectralProfile.STYLE_FIELD, style)
def updateMetadata(self, metaData):
if isinstance(metaData, dict):
for key, value in metaData.items():
self.setMetadata(key, value)
def removeField(self, name):
fields = self.fields()
values = self.attributes()
i = self.fieldNameIndex(name)
if i >= 0:
fields.remove(i)
values.pop(i)
self.setFields(fields)
self.setAttributes(values)
def setMetadata(self, key: str, value, addMissingFields=False):
"""
:param key: Name of metadata field
:param value: value to add. Need to be of type None, str, int or float.
:param addMissingFields: Set on True to add missing fields (in case value is not None)
:return:
"""
i = self.fieldNameIndex(key)
if key.startswith('__serialized__'):
if value is not None:
value = pickle.dumps(value)
if value is not None and addMissingFields:
fields = self.fields()
values = self.attributes()
if key.startswith('__serialized__'):
else:
values.append(value)
self.setFields(fields)
self.setAttributes(values)
return False
else:
return self.setAttribute(key, value)
def metadata(self, key: str, default=None):
i = self.fieldNameIndex(key)
if i < 0:
return None
v = self.attribute(i)
if v == QVariant(None):
v = None
if key.startswith('__serialized__') and v != None:
v = pickle.loads(v)
return default if v is None else v
def xValues(self):
return self.metadata(SpectralProfile.XVALUES_FIELD)
return self.metadata(SpectralProfile.YVALUES_FIELD)
def setXUnit(self, unit : str='index'):
self.setMetadata('x_unit', unit)
def xUnit(self):
def setYUnit(self, unit:str=None):
self.setMetadata('y_unit', unit)
def yUnit(self):
return self.metadata('y_unit', None)

Benjamin Jakimow
committed
def copyFieldSubset(self, fields):
sp = SpectralProfile(fields=fields)
fieldsInCommon = [field for field in sp.fields() if field in self.fields()]
sp.setGeometry(self.geometry())
sp.setId(self.id())
for field in fieldsInCommon:
assert isinstance(field, QgsField)
i = sp.fieldNameIndex(field.name())
sp.setAttribute(i, self.attribute(field.name()))
return sp
def clone(self):
sp = SpectralProfile(fields=self.fields())
sp.setAttributes(self.attributes())
return sp
def plot(self):
"""
Plots this profile to an new PyQtGraph window
:return:
"""
import pyqtgraph as pg
pi = SpectralProfilePlotDataItem(self)
pi.setClickable(True)
pw = pg.plot( title=self.name())
pi.setColor('green')
pg.QAPP.exec_()
def __reduce_ex__(self, protocol):
return self.__class__, (), self.__getstate__()
def __getstate__(self):
r = QVariant(None)
attributes = [None if v == r else v for v in self.attributes()]
state = (self.__dict__, attributes)
return pickle.dumps(state)
def __setstate__(self, state):
state = pickle.loads(state)
d, a = state
self.__dict__.update(d)
self.setAttributes(a)
def __copy__(self):
sp = SpectralProfile(fields=self.fields())
sp.setAttributes(self.attributes())
return sp
def __eq__(self, other):
if not isinstance(other, SpectralProfile):
return False
return np.array_equal(self.attributes(), other.attributes())
def __hash__(self):
return hash(id(self))
"""
def __eq__(self, other):