From 665b7355f1e864ca9ea3e48467bb30165f040200 Mon Sep 17 00:00:00 2001 From: "Benjamin Jakimow benjamin.jakimow@geo.hu-berlin.de" <benjamin.jakimow@geo.hu-berlin.de> Date: Fri, 17 Jan 2020 13:43:17 +0100 Subject: [PATCH] pulled qps updates timeseries.py TimeSeriesSource changed staticmethods to classmethods which allows to inherit the TimeSeriesSource object Signed-off-by: Benjamin Jakimow benjamin.jakimow@geo.hu-berlin.de <benjamin.jakimow@geo.hu-berlin.de> --- doc/source/installation.rst | 2 +- eotimeseriesviewer/dateparser.py | 13 +- eotimeseriesviewer/externals/qps/__init__.py | 3 +- .../externals/qps/make/deploy.py | 94 +++++++ .../externals/qps/qpsresources.qrc | 1 + eotimeseriesviewer/externals/qps/testing.py | 6 +- .../qps/ui/icons/raster_flagimage.svg | 241 ++++++++++++++++++ eotimeseriesviewer/timeseries.py | 62 ++++- tests/test_main.py | 3 +- 9 files changed, 410 insertions(+), 15 deletions(-) create mode 100644 eotimeseriesviewer/externals/qps/ui/icons/raster_flagimage.svg diff --git a/doc/source/installation.rst b/doc/source/installation.rst index ad55873b..2d15b107 100644 --- a/doc/source/installation.rst +++ b/doc/source/installation.rst @@ -5,7 +5,7 @@ Installation ============ -.. important:: The EO TSV plugin requires `QGIS Version 3.4 or higher <https://www.qgis.org/en/site/forusers/download.html>`_ +.. important:: The EO TSV plugin requires `QGIS Version 3.10 or higher <https://www.qgis.org/en/site/forusers/download.html>`_ diff --git a/eotimeseriesviewer/dateparser.py b/eotimeseriesviewer/dateparser.py index 89c770f6..fca6c2ae 100644 --- a/eotimeseriesviewer/dateparser.py +++ b/eotimeseriesviewer/dateparser.py @@ -31,13 +31,20 @@ def matchOrNone(regex, text): else: return None -def dateDOY(date): +def dateDOY(date:datetime.date)->int: + """ + Returns the DOY + :param date: + :type date: + :return: + :rtype: + """ if isinstance(date, np.datetime64): date = date.astype(datetime.date) return date.timetuple().tm_yday -def daysPerYear(year): - +def daysPerYear(year)->int: + """Returns the days per year""" if isinstance(year, np.datetime64): year = year.astype(datetime.date) if isinstance(year, datetime.date): diff --git a/eotimeseriesviewer/externals/qps/__init__.py b/eotimeseriesviewer/externals/qps/__init__.py index d87ea698..b2a7713a 100644 --- a/eotimeseriesviewer/externals/qps/__init__.py +++ b/eotimeseriesviewer/externals/qps/__init__.py @@ -1,6 +1,7 @@ import sys, importlib, site, os from qgis.core import QgsApplication - +from qgis.gui import QgisInterface +__version__ = '0.2' def initResources(): """ diff --git a/eotimeseriesviewer/externals/qps/make/deploy.py b/eotimeseriesviewer/externals/qps/make/deploy.py index 245357a2..8e100a34 100644 --- a/eotimeseriesviewer/externals/qps/make/deploy.py +++ b/eotimeseriesviewer/externals/qps/make/deploy.py @@ -50,6 +50,100 @@ URL_WIKI = r'https://api.bitbucket.org/2.0/repositories/hu-geomatics/enmap-box/w +class QGISMetadataFileWriter(object): + + def __init__(self): + self.mName = None + + self.mDescription = None + self.mVersion = None + self.mQgisMinimumVersion = '3.8' + self.mQgisMaximumVersion = '3.99' + self.mAuthor = None + self.mAbout = None + self.mEmail = None + self.mHomepage = None + self.mIcon = None + self.mTracker = None + self.mRepository = None + self.mIsExperimental = False + self.mTags = None + self.mCategory = None + self.mChangelog = '' + + def validate(self)->bool: + + return True + + def metadataString(self)->str: + assert self.validate() + + lines = ['[general]'] + lines.append('name={}'.format(self.mName)) + lines.append('author={}'.format(self.mAuthor)) + if self.mEmail: + lines.append('email={}'.format(self.mEmail)) + + lines.append('description={}'.format(self.mDescription)) + lines.append('version={}'.format(self.mVersion)) + lines.append('qgisMinimumVersion={}'.format(self.mQgisMinimumVersion)) + lines.append('qgisMaximumVersion={}'.format(self.mQgisMaximumVersion)) + lines.append('about={}'.format(re.sub('\n', '', self.mAbout))) + + lines.append('icon={}'.format(self.mIcon)) + + lines.append('tags={}'.format(', '.join(self.mTags))) + lines.append('category={}'.format(self.mRepository)) + + lines.append('homepage={}'.format(self.mHomepage)) + if self.mTracker: + lines.append('tracker={}'.format(self.mTracker)) + if self.mRepository: + lines.append('repository={}'.format(self.mRepository)) + if isinstance(self.mIsExperimental, bool): + lines.append('experimental={}'.format(self.mIsExperimental)) + + + #lines.append('deprecated={}'.format(self.mIsDeprecated)) + lines.append('') + lines.append('changelog={}'.format(self.mChangelog)) + + return '\n'.join(lines) + """ + [general] + name=dummy + description=dummy + version=dummy + qgisMinimumVersion=dummy + qgisMaximumVersion=dummy + author=dummy + about=dummy + email=dummy + icon=dummy + homepage=dummy + tracker=dummy + repository=dummy + experimental=False + deprecated=False + tags=remote sensing, raster, time series, data cube, landsat, sentinel + category=Raster + """ + + def writeMetadataTxt(self, path:str): + with open(path, 'w', encoding='utf-8') as f: + f.write(self.metadataString()) + # read again and run checks + import pyplugin_installer.installer_data + + # test if we could read the plugin + import pyplugin_installer.installer_data + P = pyplugin_installer.installer_data.Plugins() + plugin = P.getInstalledPlugin(self.mName, os.path.dirname(path), True) + + #if hasattr(pyplugin_installer.installer_data, 'errorDetails'): + # raise Exception('plugin structure/metadata error:\n{}'.format(pyplugin_installer.installer_data.errorDetails)) + s = "" + def buildId()->str: diff --git a/eotimeseriesviewer/externals/qps/qpsresources.qrc b/eotimeseriesviewer/externals/qps/qpsresources.qrc index 922bb104..1a39abfe 100644 --- a/eotimeseriesviewer/externals/qps/qpsresources.qrc +++ b/eotimeseriesviewer/externals/qps/qpsresources.qrc @@ -1,5 +1,6 @@ <RCC> <qresource prefix="qps"> + <file>ui/icons/raster_flagimage.svg</file> <file>ui/icons/poly2speclib.svg</file> <file>ui/icons/speclib_add.svg</file> <file>ui/icons/speclib_clear.svg</file> diff --git a/eotimeseriesviewer/externals/qps/testing.py b/eotimeseriesviewer/externals/qps/testing.py index c8c0ce69..baa39cb3 100644 --- a/eotimeseriesviewer/externals/qps/testing.py +++ b/eotimeseriesviewer/externals/qps/testing.py @@ -185,6 +185,9 @@ def initQgisApplication(*args, qgisResourceDir: str = None, qgsApp = qgis.testing.start_app() if not QgsProviderRegistry.instance().libraryDirectory().exists(): QgsProviderRegistry.instance().setLibraryDirectory(QDir(QApplication.instance().libraryPaths()[0])) + s = "" + + qgsApp.setPkgDataPath(re.sub(r'(/envs/[^/]+)/\.$', r'\1/Library', qgsApp.pkgDataPath())) elif QOperatingSystemVersion.current().type() == QOperatingSystemVersion.Unknown: @@ -407,12 +410,13 @@ class QgisMockup(QgisInterface): def layerTreeView(self) -> QgsLayerTreeView: return self.mLayerTreeView - def addRasterLayer(self, path, baseName=''): + def addRasterLayer(self, path, baseName:str='')->QgsRasterLayer: l = QgsRasterLayer(path, os.path.basename(path)) self.lyrs.append(l) QgsProject.instance().addMapLayer(l, True) self.mRootNode.addLayer(l) # self.mCanvas.setLayers(self.mCanvas.layers() + l) + return l def createActions(self): m = self.ui.menuBar().addAction('Add Vector') diff --git a/eotimeseriesviewer/externals/qps/ui/icons/raster_flagimage.svg b/eotimeseriesviewer/externals/qps/ui/icons/raster_flagimage.svg new file mode 100644 index 00000000..363b707b --- /dev/null +++ b/eotimeseriesviewer/externals/qps/ui/icons/raster_flagimage.svg @@ -0,0 +1,241 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="svg2" + viewBox="0 0 120 120" + height="128" + width="128" + inkscape:version="0.92.2 (5c3e80d, 2017-08-06)" + sodipodi:docname="raster_flagimage.svg" + inkscape:export-filename="C:\Users\geo_beja\Desktop\flag_renderer.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96"> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="1137" + id="namedview6021" + showgrid="false" + inkscape:zoom="4" + inkscape:cx="17.923393" + inkscape:cy="99.828371" + inkscape:window-x="1912" + inkscape:window-y="-8" + inkscape:window-maximized="1" + inkscape:current-layer="g4936" + units="px" /> + <defs + id="defs4"> + <inkscape:path-effect + effect="simplify" + id="path-effect5115" + is_visible="true" + steps="1" + threshold="0.000408163" + smooth_angles="360" + helper_size="0" + simplify_individual_paths="false" + simplify_just_coalesce="false" + simplifyindividualpaths="false" + simplifyJustCoalesce="false" /> + <inkscape:path-effect + effect="simplify" + id="path-effect5111" + is_visible="true" + steps="1" + threshold="0.000408163" + smooth_angles="360" + helper_size="0" + simplify_individual_paths="false" + simplify_just_coalesce="false" + simplifyindividualpaths="false" + simplifyJustCoalesce="false" /> + <inkscape:path-effect + effect="simplify" + id="path-effect5107" + is_visible="true" + steps="1" + threshold="0.000408163" + smooth_angles="360" + helper_size="0" + simplify_individual_paths="false" + simplify_just_coalesce="false" + simplifyindividualpaths="false" + simplifyJustCoalesce="false" /> + <inkscape:path-effect + effect="simplify" + id="path-effect5103" + is_visible="true" + steps="1" + threshold="0.000408163" + smooth_angles="360" + helper_size="0" + simplify_individual_paths="false" + simplify_just_coalesce="false" + simplifyindividualpaths="false" + simplifyJustCoalesce="false" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5099" + is_visible="true" /> + <inkscape:path-effect + effect="spiro" + id="path-effect5093" + is_visible="true" /> + <inkscape:path-effect + effect="bspline" + id="path-effect5089" + is_visible="true" + weight="33.333333" + steps="2" + helper_size="0" + apply_no_weight="true" + apply_with_weight="true" + only_selected="false" /> + <inkscape:path-effect + effect="bspline" + id="path-effect5085" + is_visible="true" + weight="33.333333" + steps="2" + helper_size="0" + apply_no_weight="true" + apply_with_weight="true" + only_selected="false" /> + <inkscape:path-effect + effect="bspline" + id="path-effect5081" + is_visible="true" + weight="33.333333" + steps="2" + helper_size="0" + apply_no_weight="true" + apply_with_weight="true" + only_selected="false" /> + <inkscape:path-effect + effect="spiro" + id="path-effect4553" + is_visible="true" /> + <inkscape:path-effect + effect="bspline" + id="path-effect4531" + is_visible="true" + weight="33.333333" + steps="2" + helper_size="0" + apply_no_weight="true" + apply_with_weight="true" + only_selected="false" /> + <linearGradient + id="a"> + <stop + id="stop4878" + offset="0" + stop-color="#aec7e2" /> + <stop + id="stop4880" + offset="1" + stop-color="#6e97c4" /> + </linearGradient> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath6909"> + <path + id="path6911" + d="m 1.9298437,3.4084573 c 6.5392607,4.2773092 6.382906,-4.32324781 13.2315343,-7e-7 v 9.9001064 c -5.9513246,-6.1251931 -6.6101075,4.332478 -13.2315344,0 z" + inkscape:connector-curvature="0" + style="display:inline;fill:#00ff00;fill-opacity:1;stroke-width:2.64110875" + sodipodi:nodetypes="ccccc" /> + </clipPath> + </defs> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="g1423" + transform="translate(-0.29108467,-0.14214042)"> + <g + id="g6444" + transform="matrix(88.9034,-82.097222,82.126829,88.871349,-87385.716,-92384.398)"> + <path + style="display:inline;fill:none;stroke:#2b3b4d;stroke-width:0.05750267;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 12.295807,1050.9981 -0.567748,0.6145" + id="path4559" + inkscape:connector-curvature="0" /> + <g + id="g4936" + transform="matrix(0.03764367,0.03477435,-0.03478686,0.03765724,12.32214,1050.8888)"> + <ellipse + style="opacity:1;fill:#2b3b4d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.11532462;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="path5077" + cx="1.0853573" + cy="18.380617" + rx="1.1546977" + ry="0.89853734" /> + <g + id="g6907" + clip-path="url(#clipPath6909)" + transform="translate(-8.1726307e-8,-1.2091792)"> + <path + style="display:inline;fill:#aec7e2" + inkscape:connector-curvature="0" + d="m 6.333,2 h 4.334 V 6.333 H 6.333 Z" + id="path4918" /> + <path + style="display:inline;fill:#2b3b4d" + inkscape:connector-curvature="0" + d="M 2,2 H 6.333 V 6.333 H 2 Z m 4.333,4.333 h 4.334 v 4.334 H 6.333 Z M 10.667,2 H 15 v 4.333 h -4.333 z" + id="path4920" /> + <path + style="display:inline;fill:#aec7e2;stroke-width:1" + inkscape:connector-curvature="0" + d="m 10.667,6.333 h 4.334 v 4.333 h -4.334 z" + id="path4918-8" /> + <path + style="display:inline;fill:#aec7e2;stroke-width:1" + inkscape:connector-curvature="0" + d="m 2,6.333 h 4.3339999 v 4.333 H 2 Z" + id="path4918-7" /> + <path + style="display:inline;fill:#aec7e2;stroke-width:1" + inkscape:connector-curvature="0" + d="m 6.333,10.666 h 4.334 v 4.333 H 6.333 Z" + id="path4918-7-1" /> + <path + style="display:inline;fill:#2b3b4d;fill-opacity:1;stroke-width:1" + inkscape:connector-curvature="0" + d="m 10.667,10.667 h 4.334 V 15 h -4.334 z" + id="path4918-7-8" /> + <path + style="display:inline;fill:#2b3b4d;fill-opacity:1;stroke-width:1" + inkscape:connector-curvature="0" + d="M 1.9989997,10.666 H 6.333 v 4.333 H 1.9989997 Z" + id="path4918-7-3" /> + </g> + </g> + </g> + </g> +</svg> diff --git a/eotimeseriesviewer/timeseries.py b/eotimeseriesviewer/timeseries.py index b37f372c..f5b8e352 100644 --- a/eotimeseriesviewer/timeseries.py +++ b/eotimeseriesviewer/timeseries.py @@ -33,6 +33,8 @@ from qgis.PyQt.QtGui import * from qgis.PyQt.QtWidgets import * from qgis.PyQt.QtCore import * +from osgeo import osr, ogr, gdal, gdal_array + DEFAULT_WKT = QgsCoordinateReferenceSystem('EPSG:4326').toWkt() LUT_WAVELENGTH_UNITS = {} @@ -386,20 +388,20 @@ def verifyInputImage(datasource): class TimeSeriesSource(object): """Provides some information on source images""" - @staticmethod - def fromJson(jsonData:str): + @classmethod + def fromJson(cls, jsonData:str): """ Returs a TimeSeriesSource from its JSON representation :param json: :return: """ - source = TimeSeriesSource(None) + source = cls(None) state = json.loads(jsonData) source.__setstatedictionary(state) return source - @staticmethod - def create(source): + @classmethod + def create(cls, source): """ Reads the argument and returns a TimeSeriesSource :param source: gdal.Dataset, str, QgsRasterLayer @@ -441,7 +443,11 @@ class TimeSeriesSource(object): if not isinstance(ds, gdal.Dataset): raise Exception('Unsupported source: {}'.format(source)) - return TimeSeriesSource(ds) + srs = osr.SpatialReference() + assert srs.ImportFromWkt(ds.GetProjection()) == ogr.OGRERR_NONE, 'Can not read spatial reference from {}'.format(ds.GetDescription()) + + + return cls(ds) def __init__(self, dataset:gdal.Dataset=None): @@ -543,7 +549,9 @@ class TimeSeriesSource(object): for k, v in state.items(): self.__dict__[k] = v self.mCRS = QgsCoordinateReferenceSystem(self.mWKT) - assert self.mCRS.isValid() + if not self.mCRS.isValid(): + srs = osr.SpatialReference() + assert srs.ImportFromWkt(self.mCRS.toWkt()) == ogr.OGRERR_NONE, 'Unable to import spatial reference of {}'.format(self.mUri) self.mUL = QgsPointXY(QgsGeometry.fromWkt(self.mUL).asPoint()) self.mLR = QgsPointXY(QgsGeometry.fromWkt(self.mLR).asPoint()) self.mDate = np.datetime64(self.mDate) @@ -636,21 +644,59 @@ class TimeSeriesSource(object): self.mTimeSeriesDate = tsd def date(self)->np.datetime64: + """ + Returns the date-time-group of the source image + :return: + :rtype: + """ return self.mDate def crs(self)->QgsCoordinateReferenceSystem: + """ + Returns the coordinate system as QgsCoordinateReferenceSystem + :return: + :rtype: + """ return self.mCRS def spatialExtent(self)->SpatialExtent: + """ + Returns the SpatialExtent + :return: + :rtype: + """ if not isinstance(self.mSpatialExtent, SpatialExtent): self.mSpatialExtent = SpatialExtent(self.mCRS, self.mUL, self.mLR) return self.mSpatialExtent + def asDataset(self)->gdal.Dataset: + """ + Returns the source as gdal.Dataset + :return: + :rtype: + """ + return gdal.Open(self.uri()) + + def asArray(self)->np.ndarray: + """ + Returns the entire image as numpy array + :return: + :rtype: + """ + return gdal_array.LoadFile(self.uri()) + def __eq__(self, other): if not isinstance(other, TimeSeriesSource): return False return self.mUri == other.mUri + def __lt__(self, other): + assert isinstance(other, TimeSeriesSource) + return self.date() < other.date() + + def __hash__(self): + return hash(self.mUri) + class TimeSeriesDate(QAbstractTableModel): """ @@ -1509,7 +1555,7 @@ class TimeSeries(QAbstractItemModel): self.checkSensorList() self.sigTimeSeriesDatesRemoved.emit(removed) - def tsds(self, date:np.datetime64=None, sensor:SensorInstrument=None)->list: + def tsds(self, date:np.datetime64=None, sensor:SensorInstrument=None)->typing.List[TimeSeriesDate]: """ Returns a list of TimeSeriesDate of the TimeSeries. By default all TimeSeriesDate will be returned. diff --git a/tests/test_main.py b/tests/test_main.py index 19005f7c..5725854c 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -32,8 +32,9 @@ import eotimeseriesviewer eotimeseriesviewer.initResources() from eotimeseriesviewer.mapcanvas import * from eotimeseriesviewer.tests import TestObjects - +from qgis.testing import start_app QGIS_APP = initQgisApplication() + SHOW_GUI = True and os.environ.get('CI') is None -- GitLab