Skip to content
Snippets Groups Projects
temporalprofiles2d.py 59.1 KiB
Newer Older
  • Learn to ignore specific revisions
  •     #sigSensorAdded = pyqtSignal(SensorInstrument)
        #sigSensorRemoved = pyqtSignal(SensorInstrument)
        #sigPixelAdded = pyqtSignal()
        #sigPixelRemoved = pyqtSignal()
    
        sigTemporalProfilesAdded = pyqtSignal(list)
        sigTemporalProfilesRemoved = pyqtSignal(list)
        sigMaxProfilesChanged = pyqtSignal(int)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def __init__(self, timeSeries:TimeSeries, uri=None, name='Temporal Profiles'):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            lyrOptions = QgsVectorLayer.LayerOptions(loadDefaultStyle=False, readExtentFromXml=False)
    
            if uri is None:
                # create a new, empty backend
                # existing_vsi_files = vsiSpeclibs()
                existing_vsi_files = []
                # todo:
                assert isinstance(existing_vsi_files, list)
                i = 0
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                _name = name.replace(' ', '_')
                uri = (pathlib.Path(VSI_DIR) / '{}.gpkg'.format(_name)).as_posix()
                while not ogr.Open(uri) is None:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    i += 1
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    uri = (pathlib.Path(VSI_DIR) / '{}{:03}.gpkg'.format(_name, i)).as_posix()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
                drv = ogr.GetDriverByName('GPKG')
                assert isinstance(drv, ogr.Driver)
                co = ['VERSION=AUTO']
                dsSrc = drv.CreateDataSource(uri, options=co)
                assert isinstance(dsSrc, ogr.DataSource)
                srs = osr.SpatialReference()
                srs.ImportFromEPSG(4326)
                co = ['GEOMETRY_NAME=geom',
                      'GEOMETRY_NULLABLE=YES',
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                      ]
    
                lyr = dsSrc.CreateLayer(name, srs=srs, geom_type=ogr.wkbPoint, options=co)
    
                assert isinstance(lyr, ogr.Layer)
                ldefn = lyr.GetLayerDefn()
                assert isinstance(ldefn, ogr.FeatureDefn)
                dsSrc.FlushCache()
            else:
                dsSrc = ogr.Open(uri)
                assert isinstance(dsSrc, ogr.DataSource)
                names = [dsSrc.GetLayerByIndex(i).GetName() for i in range(dsSrc.GetLayerCount())]
                i = names.index(name)
                lyr = dsSrc.GetLayer(i)
    
    
            # consistency check
            uri2 = '{}|{}'.format(dsSrc.GetName(), lyr.GetName())
            assert QgsVectorLayer(uri2).isValid()
            super(TemporalProfileLayer, self).__init__(uri2, name, 'ogr', lyrOptions)
    
    
            """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            assert isinstance(timeSeries, TimeSeries)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            crs = QgsCoordinateReferenceSystem('EPSG:4326')
    
            uri = 'Point?crs={}'.format(crs.authid())
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            lyrOptions = QgsVectorLayer.LayerOptions(loadDefaultStyle=False, readExtentFromXml=False)
    
            super(TemporalProfileLayer, self).__init__(uri, name, 'memory', lyrOptions)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            from collections import OrderedDict
    
            self.mProfiles = OrderedDict()
            self.mTimeSeries = timeSeries
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            #symbol = QgsFillSymbol.createSimple({'style': 'no', 'color': 'red', 'outline_color': 'black'})
            #self.mLocations.renderer().setSymbol(symbol)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.setName('EOTS Temporal Profiles')
    
            #fields.append(createQgsField(FN_ID, self.mNextID))
            fields.append(createQgsField(FN_NAME, ''))
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            fields.append(createQgsField(FN_X, 0.0, comment='Longitude'))
            fields.append(createQgsField(FN_Y, 0.0, comment='Latitude'))
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            #fields.append(createQgsField(FN_N_TOTAL, 0, comment='Total number of band values'))
            #fields.append(createQgsField(FN_N_NODATA,0, comment='Total of no-data values.'))
            #fields.append(createQgsField(FN_N_LOADED, 0, comment='Loaded valid band values.'))
            #fields.append(createQgsField(FN_N_LOADED_PERCENT,0.0, comment='Loading progress (%)'))
    
            assert self.startEditing()
            assert self.dataProvider().addAttributes(fields)
            assert self.commitChanges()
            self.initConditionalStyles()
    
            self.committedFeaturesAdded.connect(self.onFeaturesAdded)
            self.committedFeaturesRemoved.connect(self.onFeaturesRemoved)
    
            return list(self.mProfiles.values())[slice]
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def loadMissingData(self, backgroundProcess=False):
            assert isinstance(self.mTimeSeries, TimeSeries)
    
            # Get or create the TimeSeriesProfiles which will store the loaded values
            tasks = []
    
            theGeometries = []
    
            # Define which (new) bands need to be loaded for each sensor
            LUT_bandIndices = dict()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            for sensor in self.mTimeSeries.sensors():
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    LUT_bandIndices[sensor] = list(range(sensor.nb))
    
            PL = PixelLoader()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            PL.sigPixelLoaded.connect(self.addPixelLoaderResult)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            # update new / existing points
    
            for tsd in self.mTimeSeries:
                assert isinstance(tsd, TimeSeriesDatum)
    
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                requiredIndices = LUT_bandIndices[tsd.mSensor]
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                requiredIndexKeys = [bandIndex2bandKey(b) for b in requiredIndices]
                TPs = []
                missingIndices = set()
                for TP in self.mProfiles.values():
                    assert isinstance(TP, TemporalProfile)
                    dataValues = TP.mData[tsd]
                    existingKeys = list(dataValues.keys())
                    missingIdx = [bandKey2bandIndex(k) for k in requiredIndexKeys if k not in existingKeys]
                    if len(missingIdx) > 0:
                        TPs.append(TP)
                        missingIndices.union(set(missingIdx))
    
                if len(TPs) > 0:
                    theGeometries = [tp.coordinate() for tp in TPs]
                    theIDs = [tp.id() for tp in TPs]
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    for pathImg in tsd.sourceUris():
                        task = PixelLoaderTask(pathImg, theGeometries,
                                               bandIndices=requiredIndices,
                                               temporalProfileIDs=theIDs)
                        tasks.append(task)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
            if len(tasks) > 0:
    
                if backgroundProcess:
                    PL.startLoading(tasks)
                else:
    
                    import eotimeseriesviewer.pixelloader
                    tasks = [PixelLoaderTask.fromDump(eotimeseriesviewer.pixelloader.doLoaderTask(None, task.toDump())) for task in tasks]
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    l = len(tasks)
                    for i, task in enumerate(tasks):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                        PL.sigPixelLoaded.emit(task)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
    
            else:
                if DEBUG:
                    print('Data for geometries already loaded')
    
            s = ""
    
        def saveTemporalProfiles(self, pathVector, loadMissingValues=False, sep='\t'):
            if pathVector is None or len(pathVector) == 0:
                global DEFAULT_SAVE_PATH
                if DEFAULT_SAVE_PATH == None:
                    DEFAULT_SAVE_PATH = 'temporalprofiles.shp'
                d = os.path.dirname(DEFAULT_SAVE_PATH)
                filters = QgsProviderRegistry.instance().fileVectorFilters()
                pathVector, filter = QFileDialog.getSaveFileName(None, 'Save {}'.format(self.name()), DEFAULT_SAVE_PATH,
                                                                 filter=filters)
    
                if len(pathVector) == 0:
                    return None
                else:
                    DEFAULT_SAVE_PATH = pathVector
    
            if loadMissingValues:
                self.loadMissingData(backgroundProcess=False)
                for p in self.mProfiles.values():
                    assert isinstance(p, TemporalProfile)
                    p.loadMissingData()
    
            drvName = QgsVectorFileWriter.driverForExtension(os.path.splitext(pathVector)[-1])
            QgsVectorFileWriter.writeAsVectorFormat(self, pathVector, 'utf-8', destCRS=self.crs(), driverName=drvName)
    
            pathCSV = os.path.splitext(pathVector)[0] + '.data.csv'
            # write a flat list of profiles
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            csvLines = ['Temporal Profiles']
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            nBands = max([s.nb for s in self.mTimeSeries.sensors()])
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            csvLines.append(sep.join(['id', 'name', 'sensor', 'date', 'doy', 'sensor'] + ['b{}'.format(b+1) for b in range(nBands)]))
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            for p in list(self.getFeatures()):
    
                assert isinstance(p, QgsFeature)
                fid = p.id()
                tp = self.mProfiles.get(fid)
                if tp is None:
                    continue
                assert isinstance(tp, TemporalProfile)
                name = tp.name()
                for tsd, values in tp.mData.items():
                    assert isinstance(tsd, TimeSeriesDatum)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    line = [fid, name, tsd.mSensor.name(), tsd.mDate, tsd.mDOY]
                    for b in range(tsd.mSensor.nb):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                        key = 'b{}'.format(b+1)
                        line.append(values.get(key))
    
                    line = ['' if v == None else str(v) for v in line]
                    line = sep.join([str(l) for l in line])
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    csvLines.append(line)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                s = ""
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            # write CSV file
            with open(pathCSV, 'w', encoding='utf8') as f:
                f.write('\n'.join(csvLines))
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            return [pathVector, pathCSV]
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
            Returns the TimeSeries instance.
            :return: TimeSeries
            """
    
        def onFeaturesAdded(self, layerID, addedFeatures):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
            Create a TemporalProfile object for each QgsFeature added to the backend QgsVectorLayer
            :param layerID:
            :param addedFeatures:
            :return:
            """
    
                temporalProfiles = []
                for feature in addedFeatures:
                    fid = feature.id()
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    if fid < 0:
                        continue
    
                    self.mProfiles[fid] = tp
                    temporalProfiles.append(tp)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                if len(temporalProfiles) > 0:
                    pass
                    #self.sigTemporalProfilesAdded.emit(temporalProfiles)
    
        def onFeaturesRemoved(self,  layerID, removedFIDs):
    
        def initConditionalStyles(self):
            styles = self.conditionalStyles()
            assert isinstance(styles, QgsConditionalLayerStyles)
    
            for fieldName in self.fields().names():
                red = QgsConditionalStyle("@value is NULL")
                red.setTextColor(QColor('red'))
                styles.setFieldStyles(fieldName, [red])
    
            #styles.setRowStyles([red])
    
        def createTemporalProfiles(self, coordinates, names:list=None)->list:
    
            """
            Creates temporal profiles
            :param coordinates:
            :return:
            """
    
    
            if isinstance(coordinates, QgsVectorLayer):
                lyr = coordinates
                coordinates = []
                names = []
                trans = QgsCoordinateTransform()
                trans.setSourceCrs(lyr.crs())
                trans.setDestinationCrs(self.crs())
    
                nameField = None
                if isinstance(names, str) and names in lyr.fields().names():
                    nameField = names
                else:
                    for name in lyr.fields().names():
                        if re.search('names?', name, re.I):
                            nameField = name
                            break
                if nameField is None:
                    nameField = lyr.fields().names()[0]
    
                for f in lyr.getFeatures():
                    assert isinstance(f, QgsFeature)
                    g = f.geometry()
                    if g.isEmpty():
                        continue
                    g = g.centroid()
                    assert g.transform(trans) == 0
                    coordinates.append(SpatialPoint(self.crs(), g.asPoint()))
                    names.append(f.attribute(nameField))
    
                del trans
    
            elif not isinstance(coordinates, list):
    
            assert isinstance(coordinates, list)
    
            if not isinstance(names, list):
                n = self.featureCount()
                names = []
                for i in range(len(coordinates)):
                    names.append('Profile {}'.format(n+i+1))
    
            assert len(coordinates) == len(names)
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            n = self.dataProvider().featureCount()
    
            for i, (coordinate, name) in enumerate(zip(coordinates, names)):
    
                assert isinstance(coordinate, SpatialPoint)
    
                f = QgsFeature(self.fields())
                f.setGeometry(QgsGeometry.fromPointXY(coordinate.toCrs(self.crs())))
    
                #f.setAttribute(FN_ID, self.mNextID)
                f.setAttribute(FN_NAME, name)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                f.setAttribute(FN_X, coordinate.x())
                f.setAttribute(FN_Y, coordinate.y())
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                #f.setAttribute(FN_N_LOADED_PERCENT, 0.0)
                #f.setAttribute(FN_N_LOADED, 0)
                #f.setAttribute(FN_N_TOTAL, 0)
                #f.setAttribute(FN_N_NODATA, 0)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            self.startEditing()
    
    
            newFeatures = []
            def onFeaturesAdded(lid, fids):
                newFeatures.extend(fids)
    
            self.committedFeaturesAdded.connect(onFeaturesAdded)
            self.beginEditCommand('Add {} profile locations'.format(len(features)))
    
            success = self.addFeatures(features)
    
            self.committedFeaturesAdded.disconnect(onFeaturesAdded)
    
            assert self.featureCount() == len(self.mProfiles)
            profiles = [self.mProfiles[f.id()] for f in newFeatures]
            return profiles
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def saveEdits(self, leaveEditable=False, triggerRepaint=True):
    
            """
            function to save layer changes-
            :param layer:
            :param leaveEditable:
            :param triggerRepaint:
            """
            if not self.isEditable():
                return
            if not self.commitChanges():
                self.commitErrors()
    
            if leaveEditable:
                self.startEditing()
    
            if triggerRepaint:
                self.triggerRepaint()
    
        def addMissingFields(self, fields):
            missingFields = []
            for field in fields:
                assert isinstance(field, QgsField)
                i = self.dataProvider().fieldNameIndex(field.name())
                if i == -1:
                    missingFields.append(field)
            if len(missingFields) > 0:
    
                b = self.isEditable()
                self.startEditing()
                self.dataProvider().addAttributes(missingFields)
                self.saveEdits(leaveEditable=b)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            return self.dataProvider().featureCount()
    
        def __iter__(self):
            r = QgsFeatureRequest()
            for f in self.getFeatures(r):
                yield self.mProfiles[f.id()]
    
        def __contains__(self, item):
            return item in self.mProfiles.values()
    
    
    
        def temporalProfileToLocationFeature(self, tp:TemporalProfile):
    
            self.mLocations.selectByIds([tp.id()])
            for f in self.mLocations.selectedFeatures():
                assert isinstance(f, QgsFeature)
                return f
    
            return None
    
    
        def fromSpatialPoint(self, spatialPoint):
            """ Tests if a Temporal Profile already exists for the given spatialPoint"""
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    
            for p in list(self.mProfiles.values()):
                assert isinstance(p, TemporalProfile)
                if p.coordinate() == spatialPoint:
                    return p
            """
    
            spatialPoint = spatialPoint.toCrs(self.crs())
            unit = QgsUnitTypes.toAbbreviatedString(self.crs().mapUnits()).lower()
            x = spatialPoint.x() + 0.00001
            y = spatialPoint.y() + 0.
    
            if 'degree' in unit:
                dx = dy = 0.000001
    
                dx = dy = 0.1
            rect = QgsRectangle(x-dx,y-dy, x+dy,y+dy)
            for f  in self.getFeatures(rect):
                return self.mProfiles[f.id()]
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            """
    
    
        def removeTemporalProfiles(self, temporalProfiles):
            """
            Removes temporal profiles from this collection
            :param temporalProfile: TemporalProfile
            """
    
            if isinstance(temporalProfiles, TemporalProfile):
                temporalProfiles = [temporalProfiles]
            assert isinstance(temporalProfiles, list)
    
    
            temporalProfiles = [tp for tp in temporalProfiles if isinstance(tp, TemporalProfile) and tp.id() in self.mProfiles.keys()]
    
                b = self.isEditable()
                assert self.startEditing()
    
                fids = [tp.mID for tp in temporalProfiles]
    
                self.deleteFeatures(fids)
                self.saveEdits(leaveEditable=b)
    
    
                self.sigTemporalProfilesRemoved.emit(temporalProfiles)
    
    
        def loadCoordinatesFromOgr(self, path):
            """Loads the TemporalProfiles for vector geometries in data source 'path' """
            if path is None:
                filters = QgsProviderRegistry.instance().fileVectorFilters()
                defDir = None
                if isinstance(DEFAULT_SAVE_PATH, str) and len(DEFAULT_SAVE_PATH) > 0:
                    defDir = os.path.dirname(DEFAULT_SAVE_PATH)
                path, filter = QFileDialog.getOpenFileName(directory=defDir, filter=filters)
    
            if isinstance(path, str) and len(path) > 0:
                sourceLyr = QgsVectorLayer(path)
                nameAttribute = None
    
                fieldNames = [n.lower() for n in sourceLyr.fields().names()]
                for candidate in ['name', 'id']:
                    if candidate in fieldNames:
                        nameAttribute = sourceLyr.fields().names()[fieldNames.index(candidate)]
                        break
    
                if len(self.timeSeries()) == 0:
                    sourceLyr.selectAll()
                else:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    extent = self.timeSeries().maxSpatialExtent(sourceLyr.crs())
    
                    sourceLyr.selectByRect(extent)
                newProfiles = []
                for feature in sourceLyr.selectedFeatures():
                    assert isinstance(feature, QgsFeature)
                    geom = feature.geometry()
                    if isinstance(geom, QgsGeometry):
                        point = geom.centroid().constGet()
                        try:
                            TPs = self.createTemporalProfiles(SpatialPoint(sourceLyr.crs(), point))
                            for TP in TPs:
                                if nameAttribute:
                                    name = feature.attribute(nameAttribute)
                                else:
                                    name = 'FID {}'.format(feature.id())
                                TP.setName(name)
                                newProfiles.append(TP)
                        except Exception as ex:
                            print(ex)
    
    
        def addPixelLoaderResult(self, d):
            assert isinstance(d, PixelLoaderTask)
            if d.success():
    
                for fid in d.temporalProfileIDs:
                    TP = self.mProfiles.get(fid)
    
                    if isinstance(TP, TemporalProfile):
                        TP.pullDataUpdate(d)
                    else:
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                        pass
    
    
        def clear(self):
            #todo: remove TS Profiles
            #self.mTemporalProfiles.clear()
            #self.sensorPxLayers.clear()
            pass
    
    
    
    class TemporalProfileTableFilterModel(QgsAttributeTableFilterModel):
    
        def __init__(self, sourceModel, parent=None):
    
            dummyCanvas = QgsMapCanvas(parent)
    
            dummyCanvas.setDestinationCrs(DEFAULT_CRS)
            dummyCanvas.setExtent(QgsRectangle(-180,-90,180,90))
    
            super(TemporalProfileTableFilterModel, self).__init__(dummyCanvas, sourceModel, parent=parent)
    
    class TemporalProfileTableModel(QgsAttributeTableModel):
    
        #sigPlotStyleChanged = pyqtSignal(SpectralProfile)
        #sigAttributeRemoved = pyqtSignal(str)
        #sigAttributeAdded = pyqtSignal(str)
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        AUTOGENERATES_COLUMNS = [FN_ID, FN_Y, FN_X]
                                 #FN_N_LOADED, FN_N_TOTAL, FN_N_NODATA,
                                 #FN_N_LOADED_PERCENT
    
    
        def __init__(self, temporalProfileLayer=None, parent=None):
    
            if temporalProfileLayer is None:
                temporalProfileLayer = TemporalProfileLayer()
    
            cache = QgsVectorLayerCache(temporalProfileLayer, 1000)
    
            super(TemporalProfileTableModel, self).__init__(cache, parent)
            self.mTemporalProfileLayer = temporalProfileLayer
            self.mCache = cache
    
            assert self.mCache.layer() == self.mTemporalProfileLayer
    
            self.loadLayer()
    
        def columnNames(self):
            return self.mTemporalProfileLayer.fields().names()
    
        def feature(self, index):
    
            id = self.rowToId(index.row())
            f = self.layer().getFeature(id)
    
            return f
    
        def temporalProfile(self, index):
            feature = self.feature(index)
            return self.mTemporalProfileLayer.temporalProfileFromFeature(feature)
    
    
        def data(self, index, role=Qt.DisplayRole):
    
            Returns Temporal Profile Layer values
    
            :param index: QModelIndex
            :param role: enum Qt.ItemDataRole
            :return: value
            """
    
            if role is None or not index.isValid():
                return None
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            result = super(TemporalProfileTableModel, self).data(index, role=role)
    
    
        def setData(self, index, value, role=None):
            """
            Sets Temporal Profile Data.
            :param index: QModelIndex()
            :param value: value to set
            :param role: role
            :return: True | False
            """
            if role is None or not index.isValid():
                return False
    
            f = self.feature(index)
            result = False
    
            if value == None:
                value = QVariant()
            cname = self.columnNames()[index.column()]
            if role == Qt.EditRole and cname not in TemporalProfileTableModel.AUTOGENERATES_COLUMNS:
                i = f.fieldNameIndex(cname)
                if f.attribute(i) == value:
                    return False
                b = self.mTemporalProfileLayer.isEditable()
                self.mTemporalProfileLayer.startEditing()
                self.mTemporalProfileLayer.changeAttributeValue(f.id(), i, value)
                self.mTemporalProfileLayer.saveEdits(leaveEditable=b)
                result = True
                #f = self.layer().getFeature(profile.id())
                #i = f.fieldNameIndex(SpectralProfile.STYLE_FIELD)
                #self.layer().changeAttributeValue(f.id(), i, value)
                #result = super().setData(self.index(index.row(), self.mcnStyle), value, role=Qt.EditRole)
                #if not b:
                #    self.layer().commitChanges()
            if result:
                self.dataChanged.emit(index, index, [role])
            else:
                result = super().setData(index, value, role=role)
    
    
            return result
    
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
        def headerData(self, section:int, orientation:Qt.Orientation, role:int):
            data = super(TemporalProfileTableModel, self).headerData(section, orientation, role)
            if role == Qt.ToolTipRole and orientation == Qt.Horizontal:
                #add the field comment to column description
                field = self.layer().fields().at(section)
                assert isinstance(field, QgsField)
                comment = field.comment()
                if len(comment) > 0:
                    data = re.sub('</p>$', ' <i>{}</i></p>'.format(comment), data)
    
            return data
    
    
        def supportedDragActions(self):
            return Qt.CopyAction | Qt.MoveAction
    
        def supportedDropActions(self):
            return Qt.CopyAction | Qt.MoveAction
    
    
        def supportedDragActions(self):
            return Qt.CopyAction
    
        def supportedDropActions(self):
            return Qt.CopyAction
    
        def flags(self, index):
    
            if index.isValid():
                columnName = self.columnNames()[index.column()]
                flags = super(TemporalProfileTableModel, self).flags(index) | Qt.ItemIsSelectable
                #if index.column() == 0:
                #    flags = flags | Qt.ItemIsUserCheckable
    
    
                if columnName in TemporalProfileTableModel.AUTOGENERATES_COLUMNS:
                    flags = flags ^ Qt.ItemIsEditable
    
                return flags
            return None
    
    
    class TemporalProfileFeatureSelectionManager(QgsIFeatureSelectionManager):
    
    
        def __init__(self, layer, parent=None):
            s =""
            super(TemporalProfileFeatureSelectionManager, self).__init__(parent)
            assert isinstance(layer, QgsVectorLayer)
            self.mLayer = layer
            self.mLayer.selectionChanged.connect(self.selectionChanged)
    
        def layer(self):
            return self.mLayer
    
        def deselect(self, ids):
    
            if len(ids) > 0:
                selected = [id for id in self.selectedFeatureIds() if id not in ids]
                self.mLayer.deselect(ids)
    
                self.selectionChanged.emit(selected, ids, True)
    
        def select(self, ids):
            self.mLayer.select(ids)
    
        def selectFeatures(self, selection, command):
    
            super(TemporalProfileFeatureSelectionManager, self).selectF
            s = ""
        def selectedFeatureCount(self):
            return self.mLayer.selectedFeatureCount()
    
        def selectedFeatureIds(self):
            return self.mLayer.selectedFeatureIds()
    
        def setSelectedFeatures(self, ids):
            self.mLayer.selectByIds(ids)
    
    
    
    class TemporalProfileTableView(QgsAttributeTableView):
    
        def __init__(self, parent=None):
            super(TemporalProfileTableView, self).__init__(parent)
    
    
            #self.setSelectionBehavior(QAbstractItemView.SelectRows)
            #self.setSelectionMode(QAbstractItemView.SingleSelection)
            self.horizontalHeader().setSectionsMovable(True)
            self.willShowContextMenu.connect(self.onWillShowContextMenu)
            self.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
    
    
            self.mSelectionManager = None
    
        def setModel(self, filterModel):
    
            super(TemporalProfileTableView, self).setModel(filterModel)
    
    
            self.mSelectionManager = TemporalProfileFeatureSelectionManager(self.model().layer())
            self.setFeatureSelectionManager(self.mSelectionManager)
            #self.selectionModel().selectionChanged.connect(self.onSelectionChanged)
    
            self.mContextMenuActions = []
    
        def setContextMenuActions(self, actions:list):
            self.mContextMenuActions = actions
    
    
        #def contextMenuEvent(self, event):
        def onWillShowContextMenu(self, menu, index):
            assert isinstance(menu, QMenu)
            assert isinstance(index, QModelIndex)
    
    
            featureIDs = self.temporalProfileLayer().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())
    
    
            for a in self.mContextMenuActions:
                menu.addAction(a)
    
    
            for a in self.actions():
                menu.addAction(a)
    
    
        def temporalProfileLayer(self):
            return self.model().layer()
    
    
    
        def fidsToIndices(self, fids):
            """
            Converts feature ids into FilterModel QModelIndices
            :param fids: [list-of-int]
            :return:
            """
            if isinstance(fids, int):
                fids = [fids]
            assert isinstance(fids, list)
            fmodel = self.model()
            indices = [fmodel.fidToIndex(id) for id in fids]
            return [fmodel.index(idx.row(), 0) for idx in indices]
    
        def onRemoveFIDs(self, fids):
    
            layer = self.temporalProfileLayer()
            assert isinstance(layer, TemporalProfileLayer)
            b = layer.isEditable()
            layer.startEditing()
            layer.deleteFeatures(fids)
            layer.saveEdits(leaveEditable=b)
    
    
        def dropEvent(self, event):
            assert isinstance(event, QDropEvent)
            mimeData = event.mimeData()
    
            if self.model().rowCount() == 0:
                index = self.model().createIndex(0,0)
    
                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