Skip to content
Snippets Groups Projects
temporalprofiles.py 62 KiB
Newer Older
  • Learn to ignore specific revisions
  •     assert isinstance(tasks, list)
        n = len(tasks)
        qgsTask.setProgress(0)
        results = []
    
        for i, task in enumerate(tasks):
            assert isinstance(task, TemporalProfileLoaderTask)
            try:
                ds = gdal.Open(task.mSourcePath)
                assert isinstance(ds, gdal.Dataset)
                nb, ns, nl = ds.RasterCount, ds.RasterXSize, ds.RasterYSize
                gt = ds.GetGeoTransform()
                pxIndices = {}
    
    
    
                # calculate pixel indices to load
                for tpId, wkt in task.mGeometries.items():
                    geom = QgsGeometry.fromWkt(wkt)
                    assert isinstance(geom, QgsGeometry)
                    pt = geom.centroid().asPoint()
    
                    px = geo2px(pt, gt)
                    if px.x() < 0 or px.x() > ns or px.y() < 0  or px.y() > nl:
                        task.mERRORS.append('TemporalProfile {} is out of image bounds: {} = pixel {}'.format(tpId, geom, px))
                        continue
    
                    task.mRESULTS[tpId] = {'px_x':px.x(),
                                           'px_y':px.y(),
                                           'geo_x':pt.x(),
                                           'geo_y':px.y(),
                                           'gt':gt}
                    pxIndices[tpId] = px
    
                # todo: implement load balancing
    
                for j, bandIndex in enumerate([b for b in task.mBandIndices if b >= 0 and b < nb]):
    
                    band = ds.GetRasterBand(bandIndex + 1)
                    assert isinstance(band, gdal.Band)
                    no_data = band.GetNoDataValue()
    
                    bandName = 'b{}'.format(bandIndex + 1)
    
                    for tpId, px in pxIndices.items():
                        assert isinstance(px, QPoint)
    
                        value = band.ReadAsArray(px.x(), px.y(), 1, 1).flatten()[0]
                        if no_data and value == no_data:
                            value = np.NaN
    
                        task.mRESULTS[tpId][bandName] = value
    
    
            except Exception as ex:
                task.mERRORS.append('Error source image {}:\n{}'.format(task.mSourcePath, ex))
            results.append(task)
            qgsTask.setProgress(100 * (i + 1) / n)
        return pickle.dumps(results)
    
    
    
    
    class TemporalProfilePlotDataItem(pg.PlotDataItem):
    
        def __init__(self, plotStyle, parent=None):
            assert isinstance(plotStyle, TemporalProfile2DPlotStyle)
    
    
            super(TemporalProfilePlotDataItem, self).__init__([], [], parent=parent)
            self.menu = None
            #self.setFlags(QGraphicsItem.ItemIsSelectable)
            self.mPlotStyle = plotStyle
            self.setAcceptedMouseButtons(Qt.LeftButton | Qt.RightButton)
            self.mPlotStyle.sigUpdated.connect(self.updateDataAndStyle)
            self.updateStyle()
    
        # On right-click, raise the context menu
        def mouseClickEvent(self, ev):
            if ev.button() == QtCore.Qt.RightButton:
                if self.raiseContextMenu(ev):
                    ev.accept()
    
        def raiseContextMenu(self, ev):
            menu = self.getContextMenus()
    
            # Let the scene add on to the end of our context menu
            # (this is optional)
            menu = self.scene().addParentContextMenus(self, menu, ev)
    
            pos = ev.screenPos()
            menu.popup(QtCore.QPoint(pos.x(), pos.y()))
            return True
    
        # This method will be called when this item's _children_ want to raise
        # a context menu that includes their parents' menus.
        def getContextMenus(self, event=None):
            if self.menu is None:
                self.menu = QMenu()
                self.menu.setTitle(self.name + " options..")
    
                green = QAction("Turn green", self.menu)
                green.triggered.connect(self.setGreen)
                self.menu.addAction(green)
                self.menu.green = green
    
                blue = QAction("Turn blue", self.menu)
                blue.triggered.connect(self.setBlue)
                self.menu.addAction(blue)
                self.menu.green = blue
    
                alpha = QWidgetAction(self.menu)
                alphaSlider = QSlider()
                alphaSlider.setOrientation(QtCore.Qt.Horizontal)
                alphaSlider.setMaximum(255)
                alphaSlider.setValue(255)
                alphaSlider.valueChanged.connect(self.setAlpha)
                alpha.setDefaultWidget(alphaSlider)
                self.menu.addAction(alpha)
                self.menu.alpha = alpha
                self.menu.alphaSlider = alphaSlider
            return self.menu
    
    
        def updateDataAndStyle(self):
    
            TP = self.mPlotStyle.temporalProfile()
            sensor = self.mPlotStyle.sensor()
    
            if isinstance(TP, TemporalProfile) and isinstance(sensor, SensorInstrument):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                x, y = TP.dataFromExpression(self.mPlotStyle.sensor(), self.mPlotStyle.expression(), dateType='date')
    
    
                if np.any(np.isfinite(y)):
                    self.setData(x=x, y=y, connect='finite')
    
                    self.setData(x=[], y=[]) # dummy
            else:
                self.setData(x=[], y=[])  # dummy for empty data
    
        def updateStyle(self):
            """
            Updates visibility properties
            """
    
            self.setVisible(self.mPlotStyle.isVisible())
            self.setSymbol(self.mPlotStyle.markerSymbol)
            self.setSymbolSize(self.mPlotStyle.markerSize)
            self.setSymbolBrush(self.mPlotStyle.markerBrush)
            self.setSymbolPen(self.mPlotStyle.markerPen)
            self.setPen(self.mPlotStyle.linePen)
            self.update()
    
    
    
        def setClickable(self, b, width=None):
            assert isinstance(b, bool)
            self.curve.setClickable(b, width=width)
    
        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):
            pen = pg.mkPen(self.opts['pen'])
            assert isinstance(pen, QPen)
            pen.setWidth(width)
            self.setPen(pen)
    
    
    
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
    VSI_DIR = r'/vsimem/temporalprofiles/'
    
    
    class TemporalProfileLayer(QgsVectorLayer):
    
        """
        A collection to store the TemporalProfile data delivered by a PixelLoader
        """
    
        #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 saveTemporalProfiles(self, pathVector, sep='\t'):
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            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
    
    
            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, TimeSeriesDate)
    
    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
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
                    tp = TemporalProfile(self, fid, feature.geometry())
    
                    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)))
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            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
    
    
        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
    
    Benjamin Jakimow's avatar
    Benjamin Jakimow committed
            else:
                s = ""
    
    
        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