Skip to content
Snippets Groups Projects
main.py 43.1 KiB
Newer Older
unknown's avatar
unknown committed
# -*- coding: utf-8 -*-
"""
/***************************************************************************
 EnMAPBox
                                 A QGIS plugin
 EnMAP-Box V3
                              -------------------
        begin                : 2015-08-20
        git sha              : $Format:%H$
        copyright            : (C) 2015 by HU-Berlin
        email                : bj@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.                                   *
 *                                                                         *
 ***************************************************************************/
"""
unknown's avatar
unknown committed

# Import the code for the dialog
import os, sys, re, fnmatch, collections, copy, traceback, six
from qgis.core import *
#os.environ['PATH'] += os.pathsep + r'C:\OSGeo4W64\bin'

from osgeo import gdal, ogr, osr, gdal_array

Benjamin Jakimow's avatar
Benjamin Jakimow committed
DEBUG = True
import qgis.analysis
unknown's avatar
unknown committed
try:
    from qgis.gui import *
unknown's avatar
unknown committed
    import qgis
unknown's avatar
unknown committed
    import qgis_add_ins
unknown's avatar
unknown committed
    qgis_available = True
    #import console.console_output
    #console.show_console()
    #sys.stdout = console.console_output.writeOut()
    #sys.stderr = console.console_output.writeOut()
unknown's avatar
unknown committed
except:
    print('Can not find QGIS instance')
unknown's avatar
unknown committed
    qgis_available = False

import numpy as np
import multiprocessing, site
Benjamin Jakimow's avatar
Benjamin Jakimow committed
from PyQt4.QtCore import *
from PyQt4.QtGui import *
Benjamin Jakimow's avatar
Benjamin Jakimow committed
from PyQt4.uic.Compiler.qtproxies import QtGui, QtCore
Benjamin Jakimow's avatar
Benjamin Jakimow committed
#abbreviations
from timeseriesviewer import jp, mkdir, DIR_SITE_PACKAGES, file_search, dprint
Benjamin Jakimow's avatar
Benjamin Jakimow committed

site.addsitedir(DIR_SITE_PACKAGES)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
from timeseriesviewer.ui import widgets
from timeseriesviewer.timeseries import TimeSeries, TimeSeriesDatum, SensorInstrument

Benjamin Jakimow's avatar
Benjamin Jakimow committed

#I don't know why, but this is required to run this in QGIS
#todo: still required?
Benjamin Jakimow's avatar
Benjamin Jakimow committed
path = os.path.abspath(jp(sys.exec_prefix, '../../bin/pythonw.exe'))
unknown's avatar
unknown committed
if os.path.exists(path):
    multiprocessing.set_executable(path)
    sys.argv = [ None ]
unknown's avatar
unknown committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
#ensure that required non-standard modules are available
unknown's avatar
unknown committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed

unknown's avatar
unknown committed
class TimeSeriesTableModel(QAbstractTableModel):
    columnames = ['date','sensor','ns','nl','nb','image','mask']
unknown's avatar
unknown committed

    def __init__(self, TS, parent=None, *args):
        super(QAbstractTableModel, self).__init__()
        assert isinstance(TS, TimeSeries)
        self.TS = TS

    def rowCount(self, parent = QModelIndex()):
        return len(self.TS)

    def columnCount(self, parent = QModelIndex()):
        return len(self.columnames)

    def removeRows(self, row, count , parent=QModelIndex()):
        self.beginRemoveRows(parent, row, row+count-1)
        toRemove = self._data[row:row+count]
        for i in toRemove:
            self._data.remove(i)

        self.endRemoveRows()

unknown's avatar
unknown committed
    def getDateFromIndex(self, index):
        if index.isValid():
            i = index.row()
            if i >= 0 and i < len(self.TS):
                return self.TS.getTSDs()[i]
unknown's avatar
unknown committed
        return None
unknown's avatar
unknown committed

    def getTimeSeriesDatumFromIndex(self, index):

        if index.isValid():
            i = index.row()
            if i >= 0 and i < len(self.TS):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                return self.TS.data[i]
unknown's avatar
unknown committed

        return None



    def data(self, index, role = Qt.DisplayRole):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if role is None or not index.isValid():
unknown's avatar
unknown committed
            return None


        value = None
        ic_name = self.columnames[index.column()]
        TSD = self.getTimeSeriesDatumFromIndex(index)
        keys = list(TSD.__dict__.keys())
        if role == Qt.DisplayRole or role == Qt.ToolTipRole:
            if ic_name == 'name':
                value = os.path.basename(TSD.pathImg)
            elif ic_name == 'sensor':
                if role == Qt.ToolTipRole:
                    value = TSD.sensor.getDescription()
                else:
                    value = str(TSD.sensor)
unknown's avatar
unknown committed
            elif ic_name == 'date':
                value = '{}'.format(TSD.date)
            elif ic_name == 'image':
                value = TSD.pathImg
            elif ic_name == 'mask':
                value = TSD.pathMsk
            elif ic_name in keys:
                value = TSD.__dict__[ic_name]
            else:
                s = ""
        elif role == Qt.BackgroundColorRole:
            value = None
        elif role == Qt.UserRole:
unknown's avatar
unknown committed

        return value

    #def flags(self, index):
    #    return Qt.ItemIsEnabled

    def flags(self, index):
        if index.isValid():
            item = self.getTimeSeriesDatumFromIndex(index)
            cname = self.columnames[index.column()]
            if cname.startswith('d'): #relative values can be edited
                flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
            else:
                flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
            return flags
            #return item.qt_flags(index.column())
        return None

    def headerData(self, col, orientation, role):
        if Qt is None:
            return None
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return self.columnames[col]
        elif orientation == Qt.Vertical and role == Qt.DisplayRole:
            return col
        return None

unknown's avatar
unknown committed
class TimeSeriesItemModel(QAbstractItemModel):
unknown's avatar
unknown committed

unknown's avatar
unknown committed
    def __init__(self, TS):
unknown's avatar
unknown committed
        QAbstractItemModel.__init__(self)
        #self.rootItem = TreeItem[]
unknown's avatar
unknown committed
        assert type(TS) is TimeSeries
        self.TS = TS
unknown's avatar
unknown committed

    def index(self, row, column, parent = QModelIndex()):
        if not parent.isValid():
            parentItem = self.rootItem
        else:
            parentItem = parent.internalPointer()
        childItem = parentItem.child(row)
        if childItem:
            return self.createIndex(row, column, childItem)
        else:
            return QModelIndex()

    def setData(self, index, value, role = Qt.EditRole):
        if role == Qt.EditRole:
            row = index.row()

            return False
        return False

    def data(self, index, role=Qt.DisplayRole):
        data = None
        if role == Qt.DisplayRole or role == Qt.EditRole:
            data = 'sampletext'


        return data

    def flags(self, QModelIndex):
        return Qt.ItemIsSelectable

    def rowCount(self, index=QModelIndex()):
unknown's avatar
unknown committed
        return len(self.TS)
unknown's avatar
unknown committed

    #---------------------------------------------------------------------------
    def columnCount(self, index=QModelIndex()):
        return 1


Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def __init__(self, TS, recommended_bands=None):
        super(BandView, self).__init__()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert type(TS) is TimeSeries
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.representation = collections.OrderedDict()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.TS = TS
        self.TS.sensorAdded.connect(self.checkSensors)
        self.TS.changed.connect(self.checkSensors)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        self.Sensors = self.TS.Sensors
Benjamin Jakimow's avatar
Benjamin Jakimow committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        for sensor in self.Sensors:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            self.initSensor(copy.deepcopy(sensor))
    def checkSensors(self):
        represented_sensors = set(self.representation.keys())
        ts_sensors = set(self.TS.Sensors.keys())

        to_add = ts_sensors - represented_sensors
        to_remove = represented_sensors - ts_sensors
        for S in to_remove:
            self.representation[S].getWidget().close()
            self.representation.pop(S)
        for S in to_add:
            self.initSensor(S)
Benjamin Jakimow's avatar
Benjamin Jakimow committed


    def initSensor(self, sensor):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        """
        :param sensor:
        :return:
        """
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert type(sensor) is SensorInstrument
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if sensor not in self.representation.keys():
            x = widgets.ImageChipViewSettings(sensor)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            self.representation[sensor] = x
Benjamin Jakimow's avatar
Benjamin Jakimow committed


    def getWidget(self, sensor):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert type(sensor) is SensorInstrument
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        return self.representation[sensor]
Benjamin Jakimow's avatar
Benjamin Jakimow committed

    def __init__(self, TSD, renderer, destinationId=None):
        assert isinstance(TSD, TimeSeriesDatum)
        assert isinstance(renderer, QgsRasterRenderer)
        self.TSD = TSD
        self.renderer = renderer
        self.destinationId = destinationId

    def __eq__(self, other):
        if not isinstance(other, RenderJob):
            return False
        return self.TSD == other.TSD and \
               self.renderer == other.renderer and \
               self.destinationId == other.destinationId
Benjamin Jakimow's avatar
Benjamin Jakimow committed

class PixmapBuffer(QObject):
    sigProgress = pyqtSignal(int, int)
    sigPixmapCreated = pyqtSignal(RenderJob, QPixmap)

    def __init__(self):
        super(PixmapBuffer, self).__init__()

        self.extent = None
        self.crs = None
        self.size = None
        self.nWorkersMax = 1
        self.Workers = []
        self.PIXMAPS = dict()
        self.JOBS = []
        self.nTotalJobs = -1
        self.nJobsDone = -1

    def addWorker(self):
        w = Worker()
        w.setCrsTransformEnabled(True)
        w.sigPixmapCreated.connect(self.newPixmapCreated)
        if self.isValid():
            self.setWorkerProperties(w)
        self.Workers.append(w)

    def setWorkerProperties(self, worker):
        assert isinstance(worker, Worker)
        worker.setFixedSize(self.size)
        worker.setDestinationCrs(self.crs)
        worker.setExtent(self.extent)
        worker.setCenter(self.extent.center())

    def newPixmapCreated(self, renderJob, pixmap):
        self.JOBS.remove(renderJob)
        self.nJobsDone += 1
        self.sigPixmapCreated.emit(renderJob, pixmap)
        self.sigProgress.emit(self.nJobsDone, self.nTotalJobs)

    def isValid(self):
        return self.extent != None and self.crs != None and self.size != None

    def setExtent(self, extent, crs, maxPx):
        self.stopRendering()
        assert isinstance(extent, QgsRectangle)
        assert isinstance(crs, QgsCoordinateReferenceSystem)
        assert isinstance(maxPx, int)
        ratio = extent.width() / extent.height()
        if ratio < 1:  # x is largest side
            size = QSize(maxPx, int(maxPx / ratio))
        else:  # y is largest
            size = QSize(int(maxPx * ratio), maxPx)

        self.crs = crs
        self.size = size
        self.extent = extent
        for w in self.Workers:
            self.setWorkerProperties(w)
        return size

    def stopRendering(self):
        for w in self.Workers:
            w.stopRendering()
            w.clear()
        while len(self.JOBS) > 0:
            self.JOBS.pop(0)
        self.sigProgress.emit(0, 0)

    def loadSubsets(self, jobs):
        for j in jobs:
            assert isinstance(j, RenderJob)

        self.stopRendering()

        self.JOBS.extend(jobs)
        self.nTotalJobs = len(self.JOBS)
        self.nJobsDone = 0
        self.sigProgress.emit(0, self.nTotalJobs)

        if len(self.Workers) == 0:
            self.addWorker()

        #split jobs to number of workers
        i = 0
        chunkSize = int(len(self.JOBS) / len(self.Workers))
        assert chunkSize > 0
        for i in range(0, len(self.Workers), chunkSize):
            worker = self.Workers[i]
            j = min(i+chunkSize, len(self.JOBS))
            worker.startLayerRendering(self.JOBS[i:j])

class Worker(QgsMapCanvas):

    sigPixmapCreated = pyqtSignal(RenderJob, QPixmap)


    def __init__(self, *args, **kwds):
        super(Worker,self).__init__(*args, **kwds)
        self.reg = QgsMapLayerRegistry.instance()
        self.painter = QPainter()
        self.renderJobs = list()
        self.mapCanvasRefreshed.connect(self.createPixmap)

    def isBusy(self):
        return len(self.renderJobs) != 0

    def createPixmap(self, *args):
        if len(self.renderJobs) > 0:
            pixmap = QPixmap(self.size())
            self.painter.begin(pixmap)
            self.map().paint(self.painter)
            self.painter.end()
            assert not pixmap.isNull()
            job = self.renderJobs.pop(0)
            self.sigPixmapCreated.emit(job, pixmap)
            self.startSingleLayerRendering()

    def stopLayerRendering(self):
        self.stopRendering()
        del self.renderJobs[:]
        assert self.isBusy() is False

    def startLayerRendering(self, renderJobs):
        assert isinstance(renderJobs, list)
        self.renderJobs.extend(renderJobs)
        self.startSingleLayerRendering()



    def startSingleLayerRendering(self):

        if len(self.renderJobs) > 0:
            renderJob = self.renderJobs[0]
            assert isinstance(renderJob, RenderJob)
            #mapLayer = QgsRasterLayer(renderJob.TSD.pathImg)
            mapLayer = renderJob.TSD.lyrImg
            mapLayer.setRenderer(renderJob.renderer)
            dprint('QgsMapLayerRegistry count: {}'.format(self.reg.count()))
            self.reg.addMapLayer(mapLayer)

            lyrSet = [QgsMapCanvasLayer(mapLayer)]
            self.setLayerSet(lyrSet)

            #todo: add crosshair
            self.refreshAllLayers()
unknown's avatar
unknown committed

class ImageChipLabel(QLabel):
    clicked = pyqtSignal(object, object)

    def __init__(self, time_series_viewer, TSD, renderer):
        assert isinstance(time_series_viewer, TimeSeriesViewer)
        assert isinstance(TSD, TimeSeriesDatum)
        assert isinstance(renderer, QgsRasterRenderer)

        super(ImageChipLabel, self).__init__(time_series_viewer.ui)
        self.TSV = time_series_viewer
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.TSD = TSD
        self.bn = os.path.basename(self.TSD.pathImg)
        self.setContextMenuPolicy(Qt.DefaultContextMenu)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.setFrameShape(QFrame.StyledPanel)
        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)

        tt = ['Date: {}'.format(TSD.date) \
             ,'Name: {}'.format(self.bn) \
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        self.setToolTip(list2str(tt))

    def mouseReleaseEvent(self, event):
	    self.clicked.emit(self, event)

    def contextMenuEvent(self, event):
        menu = QMenu()
        #add general options
        action = menu.addAction('Copy to clipboard')
        action.triggered.connect(lambda : QApplication.clipboard().setPixmap(self.pixmap()))

        #add QGIS specific options
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            action = menu.addAction('Add {} to QGIS layers'.format(self.bn))
            action.triggered.connect(lambda : qgis_add_ins.add_QgsRasterLayer(self.iface, self.TSD.pathImg, self.bands))
unknown's avatar
unknown committed


def getBoundingBoxPolygon(points, srs=None):
    ring = ogr.Geometry(ogr.wkbLinearRing)
    for point in points:
        ring.AddPoint(point[0], point[1])
    bb = ogr.Geometry(ogr.wkbPolygon)
    bb.AddGeometry(ring)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    if isinstance(srs, QgsCoordinateReferenceSystem):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        _crs = osr.SpatialReference()
        _crs.ImportFromWkt(srs.toWkt())
        bb.AssignSpatialReference(_crs)
    return bb
unknown's avatar
unknown committed
def getDS(ds):
    if type(ds) is not gdal.Dataset:
        ds = gdal.Open(ds)
    return ds

benjamin.jakimow@geo.hu-berlin.de's avatar
benjamin.jakimow@geo.hu-berlin.de committed


def getBandNames(lyr):
    assert isinstance(lyr, QgsRasterLayer)
    dp = lyr.dataProvider()
    assert isinstance(dp, QgsRasterDataProvider)
    if str(dp.name()) == 'gdal':
        s = ""
    else:
        return lyr

unknown's avatar
unknown committed
def getImageDate(ds):
    if type(ds) is str:
        ds = gdal.Open(ds)

    path = ds.GetFileList()[0]
    to_check = [os.path.basename(path), os.path.dirname(path)]

    regAcqDate = re.compile(r'acquisition (time|date|day)', re.I)
    for key, value in ds.GetMetadata_Dict().items():
        if regAcqDate.search(key):
            to_check.insert(0, value)

    for text in to_check:
        date = parseAcquisitionDate(text)
        if date:
            return date

    raise Exception('Can not identify acquisition date of {}'.format(path))



def getChip3d(chips, rgb_idx, ranges):
    assert len(rgb_idx) == 3 and len(rgb_idx) == len(ranges)
    for i in rgb_idx:
        assert i in chips.keys()

    nl, ns = chips[rgb_idx[0]].shape
    a3d = np.ndarray((3,nl,ns), dtype='float')

    for i, rgb_i in enumerate(rgb_idx):
        range = ranges[i]
        data = chips[rgb_i].astype('float')
        data -= range[0]
        data *= 255./range[1]
        a3d[i,:] = data

    np.clip(a3d, 0, 255, out=a3d)

    return a3d.astype('uint8')

def Array2Image(d3d):
    nb, nl, ns = d3d.shape
    byteperline = nb
    d3d = d3d.transpose([1,2,0]).copy()

    return QImage(d3d.data, ns, nl, QImage.Format_RGB888)

Benjamin Jakimow's avatar
Benjamin Jakimow committed

class VerticalLabel(QLabel):
    def __init__(self, text):
        super(VerticalLabel, self).__init__(text)
        self.update()
        self.updateGeometry()
    def paintEvent(self, ev):
        p = QPainter(self)
        p.rotate(-90)
        rgn = QRect(-self.height(), 0, self.height(), self.width())
        align = self.alignment()
        self.hint = p.drawText(rgn, align, self.text())
        p.end()
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        self.setMaximumWidth(self.hint.height())
        self.setMinimumWidth(0)
        self.setMaximumHeight(16777215)
        self.setMinimumHeight(self.hint.width())

    def sizeHint(self):
        if hasattr(self, 'hint'):
            return QSize(self.hint.height(), self.hint.width())
        else:
            return QSize(19, 50)

unknown's avatar
unknown committed

unknown's avatar
unknown committed
class ImageChipBuffer(object):


    def __init__(self):
        self.data = dict()
        self.BBox = None
        self.SRS = None
unknown's avatar
unknown committed
        pass

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def hasDataCube(self, TSD):
        return TSD in self.data.keys()
unknown's avatar
unknown committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def getMissingBands(self, TSD, bands):
unknown's avatar
unknown committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        missing = set(bands)
        if TSD in self.data.keys():
            missing = missing - set(self.data[TSD].keys())
unknown's avatar
unknown committed
        return missing

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def addDataCube(self, TSD, chipData):
unknown's avatar
unknown committed

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert self.BBox is not None, 'Please initialize the bounding box first.'
unknown's avatar
unknown committed
        assert isinstance(chipData, dict)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if TSD not in self.data.keys():
            self.data[TSD] = dict()
        self.data[TSD].update(chipData)

    def getDataCube(self, TSD):
        return self.data.get(TSD)
unknown's avatar
unknown committed

    def getChipArray(self, TSD, band_view, mode='rgb'):
        assert mode in ['rgb', 'bgr']
        bands = band_view.getBands(TSD.sensor)
        band_ranges = band_view.getRanges(TSD.sensor)
        nb = len(bands)
        assert nb == 3 and nb == len(band_ranges)
        assert TSD in self.data.keys(), 'Time Series Datum {} is not in buffer'.format(TSD.getDate())
        chipData = self.data[TSD]
        for b in bands:
            assert b in chipData.keys()



        nl, ns = chipData[bands[0]].shape

        dtype= 'uint8'
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        array_data = np.ndarray((nl,ns, nb), dtype=dtype)

        if mode == 'rgb':
            ch_dst = [0,1,2]
        elif mode == 'bgr':
            # r -> dst channel 2
            # g -> dst channel 1
            # b -> dst channel 0
            ch_dst = [2,1,0]
        for i, i_dst in enumerate(ch_dst):

            offset = band_ranges[i][0]
            scale = 255./band_ranges[i][1]

            res = pg.rescaleData(chipData[bands[i]], scale, offset, dtype='float')
            np.clip(res, 0, 255, out=res)
            array_data[:,:,i_dst] = res

        return array_data


Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def getChipRGB(self, TSD, band_view):
        bands = band_view.getBands(TSD.sensor)
        band_ranges = band_view.getRanges(TSD.sensor)
unknown's avatar
unknown committed
        assert len(bands) == 3 and len(bands) == len(band_ranges)
        assert TSD in self.data.keys(), 'Time Series Datum {} is not in buffer'.format(TSD.getDate())
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        chipData = self.data[TSD]
unknown's avatar
unknown committed
        for b in bands:
            assert b in chipData.keys()

        nl, ns = chipData[bands[0]].shape
        rgb_data = np.ndarray((3,nl,ns), dtype='float')

        for i, b in enumerate(bands):
            range = band_ranges[i]
            data = chipData[b].astype('float')
            data -= range[0]
            data *= 255./range[1]
            rgb_data[i,:] = data

        np.clip(rgb_data, 0, 255, out=rgb_data)
        rgb_data = rgb_data.astype('uint8')

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if band_view.useMaskValues():
            rgb = band_view.getMaskColor()
unknown's avatar
unknown committed
            is_masked = np.where(np.logical_not(chipData['mask']))
            for i, c in enumerate(rgb):
                rgb_data[i, is_masked[0], is_masked[1]] = c

unknown's avatar
unknown committed

    def getChipImage(self, date, view):
        rgb = self.getChipRGB(date, view)
        nb, nl, ns = rgb.shape
        rgb = rgb.transpose([1,2,0]).copy('C')
        return QImage(rgb.data, ns, nl, QImage.Format_RGB888)
unknown's avatar
unknown committed

    def clear(self):
        self.data.clear()

    def setBoundingBox(self, BBox):
        assert type(BBox) is ogr.Geometry
        SRS = BBox.GetSpatialReference()
        assert SRS is not None
        if self.BBox is None or not self.BBox.Equals(BBox) or not self.SRS.IsSame(SRS):
unknown's avatar
unknown committed
            self.BBox = BBox
            self.SRS = SRS

    def __repr__(self):
        info = ['Chipbuffer']
        info.append('Bounding Box: {}'.format(self.bbBoxWkt))
        info.append('Chips: {}'.format(len(self.data)))
        return '\n'.join(info)


list2str = lambda ll : '\n'.join([str(l) for l in ll])

class TimeSeriesViewer:
unknown's avatar
unknown committed
    """QGIS Plugin Implementation."""

    def __init__(self, iface):
        """Constructor.

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface
        """
        # Save reference to the QGIS interface
        self.iface = iface
        if isinstance(self.iface, qgis.gui.QgisInterface):
            import console
            console.show_console()
unknown's avatar
unknown committed
        # Create the dialog (after translation) and keep reference
        from timeseriesviewer.ui.widgets import TimeSeriesViewerUI
        self.ui = TimeSeriesViewerUI()
        D = self.ui

        #init empty time series
        self.TS = TimeSeries()
        self.hasInitialCenterPoint = False
        self.TS.datumAdded.connect(self.ua_datumAdded)
        self.TS.changed.connect(self.timeseriesChanged)
        self.TS.progress.connect(self.ua_TSprogress)
Benjamin Jakimow's avatar
Benjamin Jakimow committed

        #init TS model
        TSM = TimeSeriesTableModel(self.TS)
        D = self.ui
        D.tableView_TimeSeries.setModel(TSM)
        D.tableView_TimeSeries.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents)
        self.BAND_VIEWS = list()
unknown's avatar
unknown committed
        self.ImageChipBuffer = ImageChipBuffer()
        self.PIXMAPS = PixmapBuffer()
        self.PIXMAPS.sigPixmapCreated.connect(self.showSubset)
unknown's avatar
unknown committed
        self.CHIPWIDGETS = collections.OrderedDict()

        self.ValidatorPxX = QIntValidator(0,99999)
        self.ValidatorPxY = QIntValidator(0,99999)
        D.btn_showPxCoordinate.clicked.connect(lambda: self.showSubsetsStart())
unknown's avatar
unknown committed
        D.btn_selectByCoordinate.clicked.connect(self.ua_selectByCoordinate)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        D.btn_selectByRectangle.clicked.connect(self.ua_selectByRectangle)
        D.btn_addBandView.clicked.connect(lambda :self.ua_addBandView())
unknown's avatar
unknown committed
        D.btn_addTSImages.clicked.connect(lambda :self.ua_addTSImages())
        D.btn_addTSMasks.clicked.connect(lambda :self.ua_addTSMasks())
        D.btn_loadTSFile.clicked.connect(self.ua_loadTSFile)
        D.btn_saveTSFile.clicked.connect(self.ua_saveTSFile)
        D.btn_addTSExample.clicked.connect(self.ua_loadExampleTS)
        D.btn_labeling_clear.clicked.connect(D.tb_labeling_text.clear)
        D.actionAdd_Images.triggered.connect(lambda :self.ua_addTSImages())
        D.actionAdd_Masks.triggered.connect(lambda :self.ua_addTSMasks())
        D.actionLoad_Time_Series.triggered.connect(self.ua_loadTSFile)
        D.actionSave_Time_Series.triggered.connect(self.ua_saveTSFile)
        D.actionLoad_Example_Time_Series.triggered.connect(self.ua_loadExampleTS)
        D.actionAbout.triggered.connect( \
            lambda: QMessageBox.about(self.ui, 'SenseCarbon TimeSeriesViewer', 'A viewer to visualize raster time series data'))

        D.btn_removeTSD.clicked.connect(lambda : self.ua_removeTSD(None))
        D.btn_removeTS.clicked.connect(self.ua_clear_TS)

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        D.sliderDOI.sliderMoved.connect(self.setDOILabel)
unknown's avatar
unknown committed
        D.spinBox_ncpu.setRange(0, multiprocessing.cpu_count())

Benjamin Jakimow's avatar
Benjamin Jakimow committed
        self.RectangleMapTool = None
        self.PointMapTool = None
        self.canvasCrs = QgsCoordinateReferenceSystem()
unknown's avatar
unknown committed
        if self.iface:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            self.canvas = self.iface.mapCanvas()
unknown's avatar
unknown committed

unknown's avatar
unknown committed
            self.RectangleMapTool = qgis_add_ins.RectangleMapTool(self.canvas)
            self.RectangleMapTool.rectangleDrawed.connect(self.setSpatialSubset)
unknown's avatar
unknown committed
            self.PointMapTool = qgis_add_ins.PointMapTool(self.canvas)
            self.PointMapTool.coordinateSelected.connect(self.setSpatialSubset)
            #self.RectangleMapTool.connect(self.ua_selectByRectangle_Done)
        self.ICP = self.ui.scrollArea_imageChip_content.layout()
        self.ui.scrollArea_bandViews_content.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
        self.BVP = self.ui.scrollArea_bandViews_content.layout()
unknown's avatar
unknown committed
        self.check_enabled()
        s = ""

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def setDOILabel(self, i):
        TSD = self.TS.data[i-1]
        self.ui.labelDOI.setText(str(TSD.date))
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        s = ""

    @staticmethod
    def icon():
        return QIcon(':/plugins/SenseCarbon/icon.png')

    def icon(self):
        return TimeSeriesViewer.icon()

Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def timeseriesChanged(self):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        D.sliderDOI.setMinimum(1)
        D.sliderDOI.setMaximum(len(self.TS.data))
        if len(self.TS.data)>0 and not self.hasInitialCenterPoint:
            extent = self.TS.getMaxExtent(self.canvasCrs)
            self.setSpatialSubset(extent.center(), self.canvasCrs)
            self.hasInitialCenterPoint = True
        if len(self.TS.data) == 0:
            self.hasInitialCenterPoint = False

    def ua_loadTSFile(self, path=None):
        if path is None or path is False:
            path = QFileDialog.getOpenFileName(self.ui, 'Open Time Series file', '')
            M = self.ui.tableView_TimeSeries.model()
            M.beginResetModel()
            self.ua_clear_TS()
            self.TS.loadFromFile(path)
            M.endResetModel()

            self.refreshBandViews()
        self.check_enabled()

    def ua_saveTSFile(self):
        path = QFileDialog.getSaveFileName(self.ui, caption='Save Time Series file')
        if path is not None:
            self.TS.saveToFile(path)


    def ua_loadExampleTS(self):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        from timeseriesviewer import PATH_EXAMPLE_TIMESERIES
        if not os.path.exists(PATH_EXAMPLE_TIMESERIES):
            QMessageBox.information(self.ui, 'File not found', '{} - this file describes an exemplary time series.'.format(path_example))
Benjamin Jakimow's avatar
Benjamin Jakimow committed
            self.ua_loadTSFile(path=PATH_EXAMPLE_TIMESERIES)
unknown's avatar
unknown committed
    def ua_selectByRectangle(self):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if self.RectangleMapTool is not None:
            self.canvas.setMapTool(self.RectangleMapTool)
unknown's avatar
unknown committed

    def ua_selectByCoordinate(self):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        if self.PointMapTool is not None:
            self.canvas.setMapTool(self.PointMapTool)

    def setCanvasSRS(self,srs):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        assert isinstance(srs, QgsCoordinateReferenceSystem)
        #self.canvas_srs = srs
        self.canvasCrs = srs
        self.ui.tb_bb_srs.setPlainText(self.canvasCrs.toWkt())
    def setSpatialSubset(self, geometry, crs):
        assert isinstance(crs, QgsCoordinateReferenceSystem)
        assert isinstance(geometry, QgsRectangle) or isinstance(geometry, QgsPoint)
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        x = D.spinBox_coordinate_x.value()
        y = D.spinBox_coordinate_x.value()
        dx = D.doubleSpinBox_subset_size_x.value()
        dy = D.doubleSpinBox_subset_size_y.value()

Benjamin Jakimow's avatar
Benjamin Jakimow committed

        if type(geometry) is QgsRectangle:
            center = geometry.center()
            x = center.x()
            y = center.y()

            dx = geometry.xMaximum() - geometry.xMinimum()
            dy = geometry.yMaximum() - geometry.yMinimum()

        if type(geometry) is QgsPoint:
            x = geometry.x()
            y = geometry.y()

        D.doubleSpinBox_subset_size_x.setValue(dx)
        D.doubleSpinBox_subset_size_y.setValue(dy)
        D.spinBox_coordinate_x.setValue(x)
        D.spinBox_coordinate_y.setValue(y)

        if D.cb_loadSubsetDirectly.isChecked():
Benjamin Jakimow's avatar
Benjamin Jakimow committed
    def qgs_handleMouseDown(self, pt, btn):
unknown's avatar
unknown committed


    def ua_TSprogress(self, v_min, v, v_max):
        assert v_min <= v and v <= v_max
        if v_min < v_max:
            if P.minimum() != v_min or P.maximum() != v_max:
                P.setRange(v_min, v_max)
            else:
                s = ""

            P.setValue(v)
unknown's avatar
unknown committed

    def ua_datumAdded(self, TSD):
        if len(self.TS) == 1:
            self.setCanvasSRS(TSD.lyrImg.crs())
            if self.ui.spinBox_coordinate_x.value() == 0.0 and \
               self.ui.spinBox_coordinate_y.value() == 0.0:
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                bbox = self.TS.getMaxExtent(srs=self.canvas_srs)

                self.ui.spinBox_coordinate_x.setRange(bbox.xMinimum(), bbox.xMaximum())
                self.ui.spinBox_coordinate_y.setRange(bbox.yMinimum(), bbox.yMaximum())
                #x, y = self.TS.getSceneCenter()
Benjamin Jakimow's avatar
Benjamin Jakimow committed
                c = bbox.center()
                self.ui.spinBox_coordinate_x.setValue(c.x())
                self.ui.spinBox_coordinate_y.setValue(c.y())
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        #self.dlg.sliderDOI

        self.ui.tableView_TimeSeries.resizeColumnsToContents()
unknown's avatar
unknown committed

    def check_enabled(self):
Benjamin Jakimow's avatar
Benjamin Jakimow committed
        hasTS = len(self.TS) > 0 or DEBUG
        hasTSV = len(self.BAND_VIEWS) > 0
unknown's avatar
unknown committed
        hasQGIS = qgis_available

        #D.tabWidget_viewsettings.setEnabled(hasTS)
unknown's avatar
unknown committed
        D.btn_showPxCoordinate.setEnabled(hasTS and hasTSV)
        D.btn_selectByCoordinate.setEnabled(hasQGIS)
        D.btn_selectByRectangle.setEnabled(hasQGIS)


unknown's avatar
unknown committed


    # noinspection PyMethodMayBeStatic
    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        We implement this ourselves since we do not inherit QObject.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        """
        # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
        return QCoreApplication.translate('EnMAPBox', message)





    def ua_addTSD_to_QGIS(self, TSD, bands):
unknown's avatar
unknown committed
    def unload(self):
        """Removes the plugin menu item and icon """
        self.iface.removeToolBarIcon(self.action)
unknown's avatar
unknown committed

    def run(self):
unknown's avatar
unknown committed


    def scrollToDate(self, date_of_interest):