diff --git a/CHANGELOG b/CHANGELOG index dcb5b8d268b4e671d74aca8de83a964019a8b8a1..7efcf9ae18b9b4bcb5651dbdb674f8c7ea7b1c20 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,31 @@ ============== Changelog ============== +2019-11-25 (version 1.10): + * improved matching of source images to sensors: matching can be specified in the settings dialog. Sensor matching + based on ground sampling distance + number of bands + data type and optionally wavelength and/or sensor name + * fixed copying of layer styles to maps of same sensor and map view type + * improved speed of mapping and layer buffering + * failed image sources are logged in the EO Time Series Viewer log panel + * Spectral Library Viewer better handles large collections of spectral profiles + +2019-10-02 (version 1.9): + * includes several smaller updates + * fixed error 'shortcutVisibleInContextMenu' error that occurred with Qt < 5.10 + * enhanced wavelength extraction from GDAL metadata: wavelength can be specified per band + +2019-09-19 (version 1.8): + * updated spectral library module + * fixed #104: error in case of wrong spatial extent + * default CRS properly shown in map view settings + * user-defined CRS visible + 2019-08-06 (version 1.7): * increased contrast for default map view text - * improved detect of wavelength information, e.g. from Pleiades, Sentinel-2 and RapidEye data + * improved reading of wavelength information, e.g. from Pleiades, Sentinel-2 and RapidEye data + * temporal profile plot: data gaps can be shown by breaks in the profile line, data source information is correctly shown for selected points only + * current extent can be copied via MapCanvas context menu + * fixed #102: move maps to date of interest selected in a temporal profile plot 2019-07-16 (version 1.6): * re-design of map visualization: faster and more compact, the number of maps is fixed to n dates x m map views @@ -37,7 +59,7 @@ Changelog * fixed #97: TSV does not start (Linux) 2019-05-31 (version 1.2): - * added SaveAllMapsDialog and menu option to export all maps as image files. + * added SaveAllMapsDialog and menu option to export all maps as image files. * fixed #91: select Temporal Profile / Spectral Profile button activates the required map tools. * fixed #92: map canvas context menu "copy to clipboard" options. diff --git a/CHANGELOG.html b/CHANGELOG.html index 8b782698577a1aa01f7ebe0503562d85e5cebaf6..182a2d06b324391afa7a1dc30635c1f3b9569fe8 100644 --- a/CHANGELOG.html +++ b/CHANGELOG.html @@ -3,7 +3,7 @@ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> -<meta name="generator" content="Docutils 0.14: http://docutils.sourceforge.net/" /> +<meta name="generator" content="Docutils 0.15.2: http://docutils.sourceforge.net/" /> <title>Changelog</title> <style type="text/css"> @@ -364,6 +364,40 @@ ul.auto-toc { <h1 class="title">Changelog</h1> <dl class="docutils"> +<dt>2019-11-25 (version 1.10):</dt> +<dd><ul class="first last simple"> +<li>improved matching of source images to sensors: matching can be specified in the settings dialog. Sensor matching +based on ground sampling distance + number of bands + data type and optionally wavelength and/or sensor name</li> +<li>fixed copying of layer styles to maps of same sensor and map view type</li> +<li>improved speed of mapping and layer buffering</li> +<li>failed image sources are logged in teh EO Time Series Viewer log panel</li> +<li>Spectral Library Viewer better handles large collections of spectral profiles</li> +</ul> +</dd> +<dt>2019-10-02 (version 1.9):</dt> +<dd><ul class="first last simple"> +<li>includes several smaller updates</li> +<li>fixed error 'shortcutVisibleInContextMenu' error that occurred with Qt < 5.10</li> +<li>enhanced wavelength extraction from GDAL metadata: wavelength can be specified per band</li> +</ul> +</dd> +<dt>2019-09-19 (version 1.8):</dt> +<dd><ul class="first last simple"> +<li>updated spectral library module</li> +<li>fixed <a class="reference external" href="https://bitbucket.org/jakimowb/eo-time-series-viewer/issues/104">#104</a>: error in case of wrong spatial extent</li> +<li>default CRS properly shown in map view settings</li> +<li>user-defined CRS visible</li> +</ul> +</dd> +<dt>2019-08-06 (version 1.7):</dt> +<dd><ul class="first last simple"> +<li>increased contrast for default map view text</li> +<li>improved reading of wavelength information, e.g. from Pleiades, Sentinel-2 and RapidEye data</li> +<li>temporal profile plot: data gaps can be shown by breaks in the profile line, data source information is correctly shown for selected points only</li> +<li>current extent can be copied via MapCanvas context menu</li> +<li>fixed <a class="reference external" href="https://bitbucket.org/jakimowb/eo-time-series-viewer/issues/102">#102</a>: move maps to date of interest selected in a temporal profile plot</li> +</ul> +</dd> <dt>2019-07-16 (version 1.6):</dt> <dd><ul class="first last simple"> <li>re-design of map visualization: faster and more compact, the number of maps is fixed to n dates x m map views</li> @@ -405,7 +439,7 @@ ul.auto-toc { </dd> <dt>2019-05-31 (version 1.2):</dt> <dd><ul class="first last simple"> -<li>added SaveAllMapsDialog and menu option to export all maps as image files.</li> +<li>added SaveAllMapsDialog and menu option to export all maps as image files.</li> <li>fixed <a class="reference external" href="https://bitbucket.org/jakimowb/eo-time-series-viewer/issues/91">#91</a>: select Temporal Profile / Spectral Profile button activates the required map tools.</li> <li>fixed <a class="reference external" href="https://bitbucket.org/jakimowb/eo-time-series-viewer/issues/92">#92</a>: map canvas context menu "copy to clipboard" options.</li> </ul> diff --git a/LICENSE.html b/LICENSE.html index c30eb7ebdbad79e329625350927f37c09f940342..873098d576ae535fe583f50a6c5df3bd908ae6da 100644 --- a/LICENSE.html +++ b/LICENSE.html @@ -3,7 +3,7 @@ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> -<meta name="generator" content="Docutils 0.14: http://docutils.sourceforge.net/" /> +<meta name="generator" content="Docutils 0.15.2: http://docutils.sourceforge.net/" /> <title><string></title> <style type="text/css"> diff --git a/README.md b/README.md index 9f5ea64e908863fd17892c757d4d20e6b88e63c0..e54be7f9c6d6e7c673a77f790b2f5f700095417b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ The EO Time Series Viewer is a [http://www.qgis.org](QGIS) plugin to visualize time series of remote sensing images. Its major purpose is to ease the visualization and labeling of images from multiple sensors. -Please visit [Wiki](https://bitbucket.org/jakimowb/eo-time-series-viewer) for more information. +Please visit http://eo-time-series-viewer.readthedocs.io/en/latest/ for more information. ## Licence and Use ## diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml index abb6ec1128a99211357dad2070e4cf3d90fee182..921326779758f235c2cacffafeb01e138a79897b 100644 --- a/bitbucket-pipelines.yml +++ b/bitbucket-pipelines.yml @@ -14,14 +14,19 @@ pipelines: - docker - pip script: # Modify the commands below to build your repository. - - python -m pip install -r requirements.txt - - python -m pip install nose2 + - python3 -m pip install -r requirements.txt + - python3 -m pip install nose2 - apt-get update - apt-get -y install xvfb + - apt-get -y install git-lfs - Xvfb :1 -screen 0 1024x768x16 &> xvfb.log & - ps aux | grep X - DISPLAY=:1.0 - export DISPLAY - mkdir test-reports - - python make/setuprepository.py - - python -m nose2 discover tests "test_*.py" > test-reports/test-report.txt + - git lfs install + - git lfs fetch + - git lfs pull + - git lfs checkout + - python3 make/setuprepository.py + - python3 -m nose2 discover tests "test_*.py" > test-reports/test-report.txt diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst index 36f46eee72cef3cd8a8d3b191b6b1d11903db917..650f62af922fea85d3307fc6e399c367f4fe021a 100644 --- a/doc/source/changelog.rst +++ b/doc/source/changelog.rst @@ -1,9 +1,31 @@ ============== Changelog ============== +2019-11-25 (version 1.10): + * improved matching of source images to sensors: matching can be specified in the settings dialog. Sensor matching + based on ground sampling distance + number of bands + data type and optionally wavelength and/or sensor name + * fixed copying of layer styles to maps of same sensor and map view type + * improved speed of mapping and layer buffering + * failed image sources are logged in teh EO Time Series Viewer log panel + * Spectral Library Viewer better handles large collections of spectral profiles + +2019-10-02 (version 1.9): + * includes several smaller updates + * fixed error 'shortcutVisibleInContextMenu' error that occurred with Qt < 5.10 + * enhanced wavelength extraction from GDAL metadata: wavelength can be specified per band + +2019-09-19 (version 1.8): + * updated spectral library module + * fixed `#104 <https://bitbucket.org/jakimowb/eo-time-series-viewer/issues/104>`_: error in case of wrong spatial extent + * default CRS properly shown in map view settings + * user-defined CRS visible + 2019-08-06 (version 1.7): * increased contrast for default map view text - * improved detect of wavelength information, e.g. from Pleiades, Sentinel-2 and RapidEye data + * improved reading of wavelength information, e.g. from Pleiades, Sentinel-2 and RapidEye data + * temporal profile plot: data gaps can be shown by breaks in the profile line, data source information is correctly shown for selected points only + * current extent can be copied via MapCanvas context menu + * fixed `#102 <https://bitbucket.org/jakimowb/eo-time-series-viewer/issues/102>`_: move maps to date of interest selected in a temporal profile plot 2019-07-16 (version 1.6): * re-design of map visualization: faster and more compact, the number of maps is fixed to n dates x m map views @@ -37,7 +59,7 @@ Changelog * fixed `#97 <https://bitbucket.org/jakimowb/eo-time-series-viewer/issues/97>`_: TSV does not start (Linux) 2019-05-31 (version 1.2): - * added SaveAllMapsDialog and menu option to export all maps as image files. + * added SaveAllMapsDialog and menu option to export all maps as image files. * fixed `#91 <https://bitbucket.org/jakimowb/eo-time-series-viewer/issues/91>`_: select Temporal Profile / Spectral Profile button activates the required map tools. * fixed `#92 <https://bitbucket.org/jakimowb/eo-time-series-viewer/issues/92>`_: map canvas context menu "copy to clipboard" options. diff --git a/doc/source/changelog_visual.rst b/doc/source/changelog_visual.rst index 31267035f471259a5c19177efe369229fba50447..bcc86d6e10bd16a55a94fc2e8f6afe317133f9dd 100644 --- a/doc/source/changelog_visual.rst +++ b/doc/source/changelog_visual.rst @@ -2,6 +2,88 @@ Visual Changelog ================ +Version 1.10 +------------ + +* improved matching of source images to sensors: matching can be specified in the settings dialog. Sensor matching + based on ground sampling distance + number of bands + data type and optionally wavelength and/or sensor name + + .. figure:: img/settings_sensor_matching.png + +* fixed copying of layer styles to maps of same sensor and map view type +* improved speed of mapping and layer buffering +* failed image sources are logged in teh EO Time Series Viewer log panel + + .. figure:: img/changelog.1.10/logpanel_failed_datasource_message.png + +Version 1.9 +----------- +* includes several smaller updates +* fixed error 'shortcutVisibleInContextMenu' error that occurred with Qt < 5.10 +* enhanced wavelength extraction from GDAL metadata: wavelength can be specified per band + +Version 1.8 +----------- + +* updated spectral library module +* fixed `#104 <https://bitbucket.org/jakimowb/eo-time-series-viewer/issues/104>`_: error in case of wrong spatial extent +* default CRS properly shown in map view settings +* user-defined CRS visible + + +Version 1.7 +----------- + +* increased contrast for default map view text +* improved reading of wavelength information, e.g. from Pleiades, Sentinel-2 and RapidEye data +* temporal profile plot: data gaps can be shown by breaks in the profile line, data source information is correctly shown for selected points only +* current extent can be copied via MapCanvas context menu +* fixed `#102 <https://bitbucket.org/jakimowb/eo-time-series-viewer/issues/102>`_: move maps to date of interest selected in a temporal profile plot + + +Version 1.6 +----------- + +* re-design of map visualization: faster and more compact, the number of maps is fixed to n dates x m map views +* date, sensor or map view information can be plotted within each map and become available in screenshots +* releases map layers that are not required any more +* slider + buttons to navigate over time series +* fixed preview in crosshair dialog + +Version 1.5 +----------- + +* closing the EO Time Series Viewer instance will release all of its resources +* added "Lock Map Panel" to avoid unwanted resizing of central widget +* fixed missing updates of time series tree view when adding / removing source images +* map canvas context menu lists layers with spatial extent intersecting the cursor position only +* fixes feature selection error +* added quick label source image to label the path of raster layer + +Version 1.4 +----------- + +* adding vector layers with sublayers will add all sublayers +* map canvas context menu "Focus on Spatial Extent" will hide maps without time series data for the current spatial extent + +.. image:: img/changelog.1.4/mapcanvas_contextmenu_focus_spatial_extent.png + +* labeling dock allows to iterate over vector features. the spatial map extent will be centered to each feature (`#26 <https://bitbucket.org/jakimowb/eo-time-series-viewer/issues/26>`_) + +.. image:: img/changelog.1.4/quick_labeling_goto_next_feature.png + +* added several convenience function to TimeSeriesViewer object +* fixed a bug that did not allow to create new polygon features +* temporal profile visualization: fixed icons to preview selected plot style, coordinate described by "<fid> <name>", e.g. "42 Deforested", fixed plot style preview +* updated SpectralLibraryViewer +* fixed spelling error in stacked band input dialog +* MapViews can add raster layers that have been opened in QGIS, e.g. XYZ Tile with OpenStreetMap data + + +Version 1.3 +----------- + +Bugfixes only Version 1.2 ----------- diff --git a/doc/source/img/changelog.1.10/logpanel_failed_datasource_message.png b/doc/source/img/changelog.1.10/logpanel_failed_datasource_message.png new file mode 100644 index 0000000000000000000000000000000000000000..51c4e7ec06270b2b5d91b697b971edf25722eb5d Binary files /dev/null and b/doc/source/img/changelog.1.10/logpanel_failed_datasource_message.png differ diff --git a/doc/source/img/changelog.1.4/mapcanvas_contextmenu_focus_spatial_extent.png b/doc/source/img/changelog.1.4/mapcanvas_contextmenu_focus_spatial_extent.png new file mode 100644 index 0000000000000000000000000000000000000000..fe319c71f46c86ccec8ac97b8bade677a13f0cdc Binary files /dev/null and b/doc/source/img/changelog.1.4/mapcanvas_contextmenu_focus_spatial_extent.png differ diff --git a/doc/source/img/changelog.1.4/quick_labeling_goto_next_feature.png b/doc/source/img/changelog.1.4/quick_labeling_goto_next_feature.png new file mode 100644 index 0000000000000000000000000000000000000000..65a65e3bec2eab0f1e6005d434eef0a8a81c97a2 Binary files /dev/null and b/doc/source/img/changelog.1.4/quick_labeling_goto_next_feature.png differ diff --git a/doc/source/img/settings_dtg_precission.png b/doc/source/img/settings_dtg_precission.png new file mode 100644 index 0000000000000000000000000000000000000000..b2408a8c1b4dd23aa78508f3f7eb167556673264 Binary files /dev/null and b/doc/source/img/settings_dtg_precission.png differ diff --git a/doc/source/img/settings_sensor_matching.png b/doc/source/img/settings_sensor_matching.png new file mode 100644 index 0000000000000000000000000000000000000000..73d4cc3b9559939fafaf032c5400701f2b1082de Binary files /dev/null and b/doc/source/img/settings_sensor_matching.png differ diff --git a/doc/source/index.rst b/doc/source/index.rst index 38581fb96baff9ad6a1a17b913389d7bad9b27c6..51266fbdc54f485c230a8fdf683ed35b7c42cf75 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -6,14 +6,21 @@ EO Time Series Viewer ====================== +The Earth Observation Time Series Viewer (EOTSV) is a free-and-open source QGIS Plugin to visualize and label +raster-based earth observation time series data. + .. image:: img/screenshot1.png -The Earth Observation Time Series Viewer (EOTSV) is a free-and-open source QGIS Plugin to visualize raster-based earth observation time series data. -* `Online documentation https://eo-time-series-viewer.readthedocs.io/en/latest/ <https://eo-time-series-viewer.readthedocs.io/en/latest/>`_ -* `Source Code https://bitbucket.org/jakimowb/eo-time-series-viewer <https://bitbucket.org/jakimowb/eo-time-series-viewer>`_ -* `Issue tracker https://bitbucket.org/jakimowb/eo-time-series-viewer/issues <https://bitbucket.org/jakimowb/eo-time-series-viewer/issues?status=new&status=open>`_ + +.. table:: + + ==================== ================================================================================================ + Online documentation https://eo-time-series-viewer.readthedocs.io/en/latest + Source Code https://bitbucket.org/jakimowb/eo-time-series-viewer + Issue tracker https://bitbucket.org/jakimowb/eo-time-series-viewer/issues + ==================== ================================================================================================ Features @@ -28,7 +35,10 @@ Features -.. seealso:: `EnMAP Box 3 <https://bitbucket.org/hu-geomatics/enmap-box/wiki/Home>`_ - Another QGIS Plugin developed at Humboldt-Universität zu Berlin +.. seealso:: + `Virtual Raster Builder <https://virtual-raster-builder.readthedocs.io/en/latest/>`_ - A QGIS Plugin to create Virtual Raster images. + + `EnMAP Box 3 <https://enmap-box.readthedocs.io/en/latest/>`_ - A QGIS Plugin to visualize and process Multi- and Hyperspectral raster images. License and Use @@ -44,8 +54,7 @@ This program is free software; you can redistribute it and/or modify it under th installation.rst User Guide <user_guide.rst> - Visual Changelog <changelog_visual.rst> - Changelog <changelog.rst> + Changelog <changelog_visual.rst> gallery.rst License <LICENSE.md> diff --git a/doc/source/screenshots.py b/doc/source/screenshots.py index 0eb8a6bd35de241b4d9523c51a792906b025e8d9..03a4c3fe5523a61a83280e1b17d2168e2e828aef 100644 --- a/doc/source/screenshots.py +++ b/doc/source/screenshots.py @@ -25,15 +25,15 @@ DATE_OF_INTEREST = np.datetime64('2014-07-02') TSV = TimeSeriesViewer() TSV.show() -TSV.spatialTemporalVis.setMapSize(QSize(300, 150)) +TSV.setMapSize(QSize(300, 150)) QApplication.processEvents() #set up example settings from example.Images import Img_2014_04_21_LC82270652014111LGN00_BOA, re_2014_06_25 if True: - TSV.loadExampleTimeSeries() - center = TSV.mTimeSeries.maxSpatialExtent().spatialCenter() + TSV.loadExampleTimeSeries(loadAsync=False) + center = TSV.timeSeries().maxSpatialExtent().spatialCenter() else: dirTestData = r'F:\TSData' files = list(file_search(dirTestData, re.compile('\.tif$'))) diff --git a/eotimeseriesviewer/__init__.py b/eotimeseriesviewer/__init__.py index 36175536466d547efbf9070f4cb550c3acd31d41..9d5424ad6c9ec5777950ac355b31fecb6ae046de 100644 --- a/eotimeseriesviewer/__init__.py +++ b/eotimeseriesviewer/__init__.py @@ -21,9 +21,10 @@ # noinspection PyPep8Naming -__version__ = '1.7' # sub-subversion number is added automatically +__version__ = '1.10' # sub-subversion number is added automatically LICENSE = 'GNU GPL-3' TITLE = 'EO Time Series Viewer' +LOG_MESSAGE_TAG = TITLE DESCRIPTION = 'Visualization of multi-sensor Earth observation time series data.' HOMEPAGE = 'https://bitbucket.org/jakimowb/eo-time-series-viewer' DOCUMENTATION = 'http://eo-time-series-viewer.readthedocs.io/en/latest/' @@ -84,17 +85,13 @@ if not os.environ.get('READTHEDOCS') in ['True', 'TRUE', True]: UI_DIRECTORIES.append(DIR_UI) -def messageLog(msg, level=None): +def messageLog(msg, level=Qgis.Info): """ Writes a log message to the QGIS EO TimeSeriesViewer log :param msg: log message string :param level: QgsMessageLog::MessageLevel with MessageLevel =[INFO | ALL | WARNING | CRITICAL | NONE] """ - - if level is None: - level = Qgis.Warning - - QgsApplication.instance().messageLog().logMessage(msg, 'EO TSV', level) + QgsApplication.instance().messageLog().logMessage(msg, LOG_MESSAGE_TAG, level) def initResources(): """ diff --git a/eotimeseriesviewer/__main__.py b/eotimeseriesviewer/__main__.py index fa9e2e0b5569d8d00f325fd7a3622699c0321e6f..76367e497639ffa49efe5b7206ff223ebe942b07 100644 --- a/eotimeseriesviewer/__main__.py +++ b/eotimeseriesviewer/__main__.py @@ -17,9 +17,15 @@ *************************************************************************** """ +import sys, os, pathlib + + def run(): # add site-packages to sys.path + pluginDir = pathlib.Path(__file__).parents[1] + sys.path.append(pluginDir.as_posix()) + print(pluginDir) from eotimeseriesviewer.tests import initQgisApplication import qgis.utils @@ -35,7 +41,7 @@ def run(): from eotimeseriesviewer.main import TimeSeriesViewer ts = TimeSeriesViewer() - ts.run() + ts.show() if not qgisIface: qgsApp.exec_() diff --git a/eotimeseriesviewer/dateparser.py b/eotimeseriesviewer/dateparser.py index 52f27de599d9a08f4379dd71abc0ecb5854c3094..89c770f63c1a79b58530443f189beb812c6711ed 100644 --- a/eotimeseriesviewer/dateparser.py +++ b/eotimeseriesviewer/dateparser.py @@ -6,10 +6,10 @@ from qgis import * from qgis.PyQt.QtCore import QDate -#regular expression. compile them only once +# regular expression. compile them only once -#thanks user "funkwurm" in -#http://stackoverflow.com/questions/28020805/regex-validate-correct-iso8601-date-string-with-time +# thanks to user "funkwurm" in +# http://stackoverflow.com/questions/28020805/regex-validate-correct-iso8601-date-string-with-time regISODate1 = re.compile(r'(?:[1-9]\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:Z|[+-][01]\d:[0-5]\d)') regISODate3 = re.compile(r'([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?') regISODate2 = re.compile(r'(19|20|21\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?') @@ -195,7 +195,8 @@ class ImageDateReaderDefault(ImageDateReader): for key, value in md.items(): if self.regDateKeys.search(key): try: - dtg = np.datetime64(value) + # remove timezone characters from end of string, e.g. 'Z' in '2013-03-25T13:45:03.0Z' + dtg = np.datetime64(re.sub(r'\D+$', '', value)) return dtg except Exception as ex: pass @@ -272,7 +273,7 @@ class ImageDateParserLandsat(ImageDateReader): dateParserList = [c for c in ImageDateReader.__subclasses__()] -dateParserList.insert(0, dateParserList.pop(dateParserList.index(ImageDateReaderDefault))) #set to first position +dateParserList.insert(0, dateParserList.pop(dateParserList.index(ImageDateReaderDefault))) # set to first position def parseDateFromDataSet(dataSet:gdal.Dataset)->np.datetime64: assert isinstance(dataSet, gdal.Dataset) @@ -282,12 +283,3 @@ def parseDateFromDataSet(dataSet:gdal.Dataset)->np.datetime64: return dtg return None - -if __name__ == '__main__': - - p = r'E:\_EnMAP\temp\temp_bj\landsat\37S\EB\LE71720342015009SG100\LE71720342015009SG100_sr.tif' - p = r'D:\Repositories\QGIS_Plugins\hub-timeseriesviewer\example\Images\2012-04-07_LE72270652012098EDC00_BOA.bsq' - ds = gdal.Open(p) - - print(datetime64FromYYYYMMDD('20141212')) - print(parseDateFromDataSet(ds)) \ No newline at end of file diff --git a/eotimeseriesviewer/externals/qps/__init__.py b/eotimeseriesviewer/externals/qps/__init__.py index 5df16d52df4419048c895bcbe6ec4facd3ca99f9..d87ea698883a81bdf8435a5cfee9a55755fd5555 100644 --- a/eotimeseriesviewer/externals/qps/__init__.py +++ b/eotimeseriesviewer/externals/qps/__init__.py @@ -64,3 +64,8 @@ def registerEditorWidgets(): print(ex, file=sys.stderr) + +def initAll(): + + initResources() + registerEditorWidgets() \ No newline at end of file diff --git a/eotimeseriesviewer/externals/qps/classification/classificationscheme.py b/eotimeseriesviewer/externals/qps/classification/classificationscheme.py index c2a31acf7f343a34d86d9920a9acffbd88397407..f9b120693360fb350b6b990ec15e2c3e8dc9f449 100644 --- a/eotimeseriesviewer/externals/qps/classification/classificationscheme.py +++ b/eotimeseriesviewer/externals/qps/classification/classificationscheme.py @@ -19,12 +19,13 @@ *************************************************************************** """ -import os, json, pickle, warnings, csv, re, sys +import os, json, pickle, warnings, csv, re, sys, typing from qgis.core import * from qgis.gui import * from qgis.PyQt.QtCore import * from qgis.PyQt.QtGui import * from qgis.PyQt.QtWidgets import * +from qgis.PyQt.QtXml import * import numpy as np from osgeo import gdal from ..utils import gdalDataset, nextColor, loadUIFormClass, findMapLayer, registeredMapLayers @@ -38,7 +39,7 @@ DEFAULT_FIRST_COLOR = QColor('#a6cee3') MIMEDATA_KEY = 'hub-classscheme' MIMEDATA_KEY_TEXT = 'text/plain' MIMEDATA_INTERNAL_IDs = 'classinfo_ids' - +MIMEDATA_KEY_QGIS_STYLE = 'application/qgis.style' MAX_UNIQUE_CLASSES = 100 def findMapLayersWithClassInfo()->list: @@ -319,8 +320,22 @@ class ClassificationScheme(QAbstractTableModel): cs.insertClasses(classes) mimeData = QMimeData() mimeData.setData(MIMEDATA_KEY, cs.qByteArray()) - mimeData.setData(MIMEDATA_INTERNAL_IDs, QByteArray(pickle.dumps([id(c) for c in classes ]))) + mimeData.setData(MIMEDATA_INTERNAL_IDs, QByteArray(pickle.dumps([id(c) for c in classes]))) mimeData.setText(cs.toString()) + + renderer = self.featureRenderer() + + doc = QDomDocument() + err = '' + for typeName in ['POLYGON']: + lyr = QgsVectorLayer('{}?crs=epsg:4326&field=id:integer'.format(typeName), cs.name(), 'memory') + assert isinstance(lyr, QgsVectorLayer) and lyr.isValid() + lyr.setRenderer(renderer.clone()) + err = lyr.exportNamedStyle(doc) + xml = doc.toString() + s = "" + mimeData.setData(MIMEDATA_KEY_QGIS_STYLE, doc.toByteArray()) + mimeData.setText(doc.toString()) return mimeData def mimeTypes(self)->list: @@ -578,17 +593,17 @@ class ClassificationScheme(QAbstractTableModel): return cs - def featureRenderer(self)->QgsCategorizedSymbolRenderer: + def featureRenderer(self, symbolType:typing.Union[QgsMarkerSymbol, QgsFillSymbol, QgsLineSymbol]=QgsFillSymbol)->QgsCategorizedSymbolRenderer: """ Returns the ClassificationScheme as QgsCategorizedSymbolRenderer :return: ClassificationScheme """ - r = QgsCategorizedSymbolRenderer('dummy', []) + r = QgsCategorizedSymbolRenderer(self.name(), []) for c in self: assert isinstance(c, ClassInfo) - symbol = QgsMarkerSymbol() + symbol = symbolType() symbol.setColor(QColor(c.color())) cat = QgsRendererCategory(c.label(), symbol, c.name(), render=True) r.addCategory(cat) @@ -989,10 +1004,11 @@ class ClassificationScheme(QAbstractTableModel): if isinstance(ba, ClassificationScheme): return ba if MIMEDATA_KEY_TEXT in mimeData.formats(): - ba = ClassificationScheme.fromQByteArray(mimeData.data(MIMEDATA_KEY_TEXT)) if isinstance(ba, ClassificationScheme): return ba + if MIMEDATA_KEY_QGIS_STYLE in mimeData.formats(): + s = "" return None @@ -1543,7 +1559,7 @@ class ClassificationSchemeWidget(QWidget, loadClassificationUI('classificationsc a.triggered.connect(lambda _, lyr=layer, f=idx: self.onLoadClassesFromField(lyr, idx)) if isinstance(layer.renderer(), QgsCategorizedSymbolRenderer): - a = m.addAction('Current Symbology'.format(layer.name())) + a = m.addAction('Current Symbols'.format(layer.name())) a.triggered.connect(lambda _, lyr=layer: self.onLoadClassesFromRenderer(lyr)) @@ -1572,7 +1588,7 @@ class ClassificationSchemeWidget(QWidget, loadClassificationUI('classificationsc def onClipboard(self, *args): mimeData = QApplication.clipboard().mimeData() - b = isinstance(mimeData, QMimeData) and MIMEDATA_KEY_TEXT in mimeData.formats() + b = isinstance(mimeData, QMimeData) and (MIMEDATA_KEY_TEXT in mimeData.formats() or MIMEDATA_KEY_QGIS_STYLE in mimeData.formats()) self.actionPasteClasses.setEnabled(b) @@ -1765,6 +1781,7 @@ class ClassificationSchemeEditorConfigWidget(QgsEditorConfigWidget): def setConfig(self, config:dict): self.mLastConfig = config cs = classSchemeFromConfig(config) + cs.setName(self.layer().fields()[self.field()].name()) self.mSchemeWidget.setClassificationScheme(cs) def resetClassificationScheme(self): @@ -1875,7 +1892,7 @@ class ClassificationSchemeWidgetFactory(QgsEditorWidgetFactory): assert isinstance(field, QgsField) if re.search('(int|float|double|text|string)', field.typeName(), re.I): if re.search('class', field.name(), re.I): - return 10 + return 5 # should we return 10 for showing specialized support? else: return 5 else: diff --git a/eotimeseriesviewer/externals/qps/layerproperties.py b/eotimeseriesviewer/externals/qps/layerproperties.py index 322676cf3dd01eae12e8de2214eb5b48e624c77a..37f74eb6ef6ccba81bb241eab02b7e3a94b27add 100644 --- a/eotimeseriesviewer/externals/qps/layerproperties.py +++ b/eotimeseriesviewer/externals/qps/layerproperties.py @@ -21,7 +21,7 @@ import collections import os import re - +import typing from osgeo import gdal, ogr, osr import numpy as np from qgis.gui import * @@ -74,6 +74,59 @@ DUMMY_RASTERINTERFACE = QgsSingleBandGrayRenderer(None, 0) MDF_QGIS_LAYER_STYLE = 'application/qgis.style' MDF_TEXT_PLAIN = 'text/plain' +def openRasterLayerSilent(uri, name, provider)->QgsRasterLayer: + """ + Opens a QgsRasterLayer without asking for its CRS in case it is undefined. + :param uri: path + :param name: name of layer + :param provider: provider string + :return: QgsRasterLayer + """ + key = '/Projections/defaultBehavior' + v = QgsSettings().value(key) + isPrompt = v == 'prompt' + + if isPrompt: + # do not ask! + QgsSettings().setValue(key, 'useProject') + + loptions = QgsRasterLayer.LayerOptions(loadDefaultStyle=False) + lyr = QgsRasterLayer(uri, name, provider, options=loptions) + + if isPrompt: + QgsSettings().setValue(key, v) + return lyr + +class SubDataSetInputTableModel(QAbstractTableModel): + + def __init__(self, *args, **kwds): + super(SubDataSetInputTableModel, self).__init__(*args, **kwds) + + self.cnID = '#' + self.cnName = 'name' + self.cnPath = 'path' + + self.cnSamples = 'ns' + self.cnLines = 'nl' + self.cnBands = 'nb' + + self.mInputBands = [] + + + + + + def setSourceDataSet(self, ds:gdal.Dataset): + pass + + + +class SubDataSetSelectionDialog(QDialog, loadUI('subdatasetselectiondialog.ui')): + + + pass + + def rendererFromXml(xml): """ Reads a string `text` and returns the first QgsRasterRenderer or QgsFeatureRenderer (if defined). @@ -144,8 +197,9 @@ def defaultRasterRenderer(layer:QgsRasterLayer, bandIndices:list=None, sampleSiz if not isinstance(bandIndices, list): if nb >= 3: + if isinstance(defaultRenderer, QgsMultiBandColorRenderer): - bandIndices = [defaultRenderer.redBand()-1, defaultRenderer.greenBand()-1, defaultRenderer.blueBand()-1] + bandIndices = defaultBands(layer) else: bandIndices = [2, 1, 0] else: @@ -154,9 +208,7 @@ def defaultRasterRenderer(layer:QgsRasterLayer, bandIndices:list=None, sampleSiz assert isinstance(bandIndices, list) # get band stats - bandStats = [layer.dataProvider().bandStatistics(b + 1, - stats=QgsRasterBandStats.All, - sampleSize=256) for b in bandIndices] + bandStats = [layer.dataProvider().bandStatistics(b + 1, stats=QgsRasterBandStats.Min | QgsRasterBandStats.Max, sampleSize=sampleSize) for b in bandIndices] dp = layer.dataProvider() assert isinstance(dp, QgsRasterDataProvider) @@ -187,7 +239,7 @@ def defaultRasterRenderer(layer:QgsRasterLayer, bandIndices:list=None, sampleSiz ce.setMinimumValue(0) ce.setMaximumValue(255) else: - vmin, vmax = layer.dataProvider().cumulativeCut(b, 0.02, 0.98) + vmin, vmax = layer.dataProvider().cumulativeCut(b, 0.02, 0.98, sampleSize=sampleSize) ce.setMinimumValue(vmin) ce.setMaximumValue(vmax) @@ -207,7 +259,7 @@ def defaultRasterRenderer(layer:QgsRasterLayer, bandIndices:list=None, sampleSiz assert isinstance(ce, QgsContrastEnhancement) ce.setContrastEnhancementAlgorithm(QgsContrastEnhancement.StretchToMinimumMaximum, True) - vmin, vmax = layer.dataProvider().cumulativeCut(b, 0.02, 0.98) + vmin, vmax = layer.dataProvider().cumulativeCut(b, 0.02, 0.98, sampleSize=sampleSize) if dt == Qgis.Byte: #standard RGB photo? if False and layer.bandCount() == 3: @@ -327,7 +379,12 @@ def pasteStyleFromClipboard(layer:QgsMapLayer): layer.triggerRepaint() -def subLayerDefinitions(mapLayer:QgsMapLayer)->list: +def subLayerDefinitions(mapLayer:QgsMapLayer)->typing.List[QgsSublayersDialog.LayerDefinition]: + """ + + :param mapLayer:QgsMapLayer + :return: list of sublayer definitions + """ definitions = [] dp = mapLayer.dataProvider() @@ -371,7 +428,7 @@ def subLayerDefinitions(mapLayer:QgsMapLayer)->list: return definitions -def subLayers(mapLayer:QgsMapLayer, subLayers:list=None)->list: +def subLayers(mapLayer:QgsMapLayer, subLayers:list=None)->typing.List[QgsMapLayer]: """ Returns a list of QgsMapLayer instances extracted from the input QgsMapLayer. Returns the "parent" QgsMapLayer in case no sublayers can be extracted @@ -1327,22 +1384,38 @@ class RasterLayerProperties(QgsOptionsDialogBase, loadUI('rasterlayerpropertiesd self.mRenderTypeComboBox.setModel(RASTERRENDERER_CREATE_FUNCTIONSV2) renderer = self.mRasterLayer.renderer() - - for func in RASTERRENDERER_CREATE_FUNCTIONSV2.optionValues(): + iCurrent = None + for i, constructor in enumerate(RASTERRENDERER_CREATE_FUNCTIONSV2.optionValues()): extent = self.canvas.extent() - w = func(self.mRasterLayer, extent) + w = constructor(self.mRasterLayer, extent) w.setMapCanvas(self.canvas) #w.sizePolicy().setVerticalPolicy(QSizePolicy.Maximum) assert isinstance(w, QgsRasterRendererWidget) + w.setRasterLayer(self.mRasterLayer) minMaxWidget = w.minMaxWidget() if isinstance(minMaxWidget, QgsRasterMinMaxWidget): minMaxWidget.setCollapsed(False) w.setParent(self.mRendererStackedWidget) w.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) self.mRendererStackedWidget.addWidget(w) - f2 = getattr(w, 'setFromRenderer', None) - if f2: - f2(renderer) + + if type(w.renderer()) == type(renderer): + iCurrent = i + + try: + w.setFromRenderer(renderer) + + if isinstance(w, QgsSingleBandPseudoColorRendererWidget) and isinstance(renderer, QgsSingleBandPseudoColorRenderer): + w.setMin(renderer.classificationMin()) + w.setMax(renderer.classificationMax()) + elif isinstance(w, QgsSingleBandPseudoColorRendererWidget) and isinstance(renderer, QgsSingleBandGrayRenderer): + pass + + except Exception as ex: + s = "" + + if isinstance(iCurrent, int): + self.mRenderTypeComboBox.setCurrentIndex(iCurrent) @@ -1383,7 +1456,7 @@ class RasterLayerProperties(QgsOptionsDialogBase, loadUI('rasterlayerpropertiesd renderer = mRendererWidget.renderer() assert isinstance(renderer, QgsRasterRenderer) renderer.setOpacity(self.sliderOpacity.value() / 100.) - self.mRasterLayer.setRenderer(renderer) + self.mRasterLayer.setRenderer(renderer.clone()) self.mRasterLayer.triggerRepaint() self.setResult(QDialog.Accepted) s = "" @@ -1562,12 +1635,14 @@ def showLayerPropertiesDialog(layer:QgsMapLayer, result = dialog.exec_() return result + + RASTERRENDERER_CREATE_FUNCTIONSV2 = OptionListModel() #RASTERRENDERER_CREATE_FUNCTIONSV2.addOption(Option(MultiBandColorRendererWidget.create, name='multibandcolor')) -RASTERRENDERER_CREATE_FUNCTIONSV2.addOption(Option(QgsMultiBandColorRendererWidget.create, name='multibandcolor (QGIS)')) -RASTERRENDERER_CREATE_FUNCTIONSV2.addOption(Option(QgsPalettedRendererWidget.create, name='paletted')) +RASTERRENDERER_CREATE_FUNCTIONSV2.addOption(Option(QgsMultiBandColorRendererWidget, name='multibandcolor (QGIS)')) +RASTERRENDERER_CREATE_FUNCTIONSV2.addOption(Option(QgsPalettedRendererWidget, name='paletted')) #RASTERRENDERER_CREATE_FUNCTIONSV2.addOption(Option(SingleBandGrayRendererWidget.create, name='singlegray')) -RASTERRENDERER_CREATE_FUNCTIONSV2.addOption(Option(QgsSingleBandGrayRendererWidget.create, name='singlegray (QGIS)')) +RASTERRENDERER_CREATE_FUNCTIONSV2.addOption(Option(QgsSingleBandGrayRendererWidget, name='singlegray (QGIS)')) #RASTERRENDERER_CREATE_FUNCTIONSV2.addOption(Option(SingleBandPseudoColorRendererWidget.create, name='singlebandpseudocolor')) -RASTERRENDERER_CREATE_FUNCTIONSV2.addOption(Option(QgsSingleBandPseudoColorRendererWidget.create, name='singlebandpseudocolor (QGIS)')) +RASTERRENDERER_CREATE_FUNCTIONSV2.addOption(Option(QgsSingleBandPseudoColorRendererWidget, name='singlebandpseudocolor (QGIS)')) diff --git a/eotimeseriesviewer/externals/qps/make/iconselect.py b/eotimeseriesviewer/externals/qps/make/iconselect.py index f50c2f2136908a2fd086b9a4cab2d9226960f60a..a7e4b574cfdc4962b21678341115a2e6b3a4249d 100644 --- a/eotimeseriesviewer/externals/qps/make/iconselect.py +++ b/eotimeseriesviewer/externals/qps/make/iconselect.py @@ -3,7 +3,7 @@ from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * from qgis.gui import QgsCollapsibleGroupBox -from ..testing import initQgisApplication +from qps.testing import initQgisApplication STANDARD_ICONS = [ 'SP_ArrowBack', @@ -150,7 +150,7 @@ class AvailableIcons(QWidget): def findResourceDirs(self, resource:QResource)->list: dirs = [] for path in resource.children(): - r = QResource(resource.fileName() +'/'+ path) + r = QResource(resource.fileName() + '/' + path) assert isinstance(r, QResource) if r.isDir(): dirs.append(r.fileName()) diff --git a/eotimeseriesviewer/externals/qps/make/make.py b/eotimeseriesviewer/externals/qps/make/make.py index 7ab8814a06ab44b630abc40a907d249ed13df0d4..b59e0523af7c3f4d9ce3610a0ce4b49ff390659b 100644 --- a/eotimeseriesviewer/externals/qps/make/make.py +++ b/eotimeseriesviewer/externals/qps/make/make.py @@ -5,8 +5,37 @@ app = initQgisApplication() from ..utils import * from osgeo import gdal, ogr, osr -DIR_QGIS_REPO = r'C:\Users\geo_beja\Repositories\QGIS' -DIR_REPO = dn(findUpwardPath(__file__, '.git')) +DIR_QGIS_REPO = os.environ.get('DIR_QGIS_REPO') + +DIR_REPO = findUpwardPath(__file__, '.git') +if not DIR_REPO is None: + DIR_REPO = dn(DIR_REPO) + + +def remove_shortcutVisibleInContextMenu(rootDir): + """ + This routine searches for *.ui files and removes the shortcutVisibleInContextMenu <property> from which causes errors with Qt < 5.10. + :param rootDir: str + """ + + uiFiles = file_search(rootDir, '*.ui', recursive=True) + + regex = re.compile(r'<property name="shortcutVisibleInContextMenu">[^<]*<bool>true</bool>[^<]*</property>', re.MULTILINE) + + + for p in uiFiles: + assert isinstance(p, str) + assert os.path.isfile(p) + + with open(p, encoding='utf-8') as f: + xml = f.read() + + if 'shortcutVisibleInContextMenu' in xml: + print('remove "shortcutVisibleInContextMenu" properties from {}'.format(p)) + xml = regex.sub('', xml) + + with open(p, 'w', encoding='utf-8') as f: + f.write(xml) def rasterize_vector_labels(pathRef, pathDst, pathShp, label_field, band_ref=1, label_layer=0): @@ -156,7 +185,7 @@ def searchAndCompileResourceFiles(dirRoot:str, targetDir:str=None): Defaults to the *.qrc's directory """ # find ui files - assert os.path.isdir(dirRoot) + assert os.path.isdir(dirRoot), '"dirRoot" is not a directory: {}'.format(dirRoot) ui_files = list(file_search(dirRoot, '*.ui', recursive=True)) qrcs = set() @@ -179,13 +208,20 @@ def searchAndCompileResourceFiles(dirRoot:str, targetDir:str=None): resourcefiles = list(qrcs) assert len(resourcefiles) > 0 + qrcFiles = [] + for root_dir, f in resourcefiles: pathQrc = os.path.normpath(jp(root_dir, f)) if not os.path.exists(pathQrc): print('Resource file does not exist: {}'.format(pathQrc)) continue - compileResourceFile(pathQrc, targetDir=targetDir) + if pathQrc not in qrcFiles: + qrcFiles.append(pathQrc) + + print('Compile {} *.qrc files'.format(len(qrcFiles))) + for qrcFiles in qrcFiles: + compileResourceFile(qrcFiles, targetDir=targetDir) def compileResourceFile(pathQrc:str, targetDir:str=None): @@ -231,180 +267,6 @@ def fileNeedsUpdate(file1, file2): else: return os.path.getmtime(file1) > os.path.getmtime(file2) -def createResourceIconPackage(dirIcons, pathResourceFile): - import numpy as np - - pathInit = jp(dirIcons, '__init__.py') - code = ['#!/usr/bin/env python', - '"""', - 'This file is auto-generated.', - 'Do not edit manually, as changes might get overwritten.', - '"""', - '__author__ = "auto-generated by {}"'.format(os.path.relpath(__file__, DIR_REPO)), - '__date__ = "{}"'.format(np.datetime64('now')), - '', - 'import sys, os', - '', - 'thisDir = os.path.dirname(__file__)', - '# File path attributes:', - ] - files = list(file_search(dirIcons, '*.png', recursive=True)) - - filePathAttributes = set() - - def addFiles(files, comment=None, numberPrefix='File'): - if len(files) > 0: - if comment: - code.append('# ' + comment) - for f in files: - an, ext = os.path.splitext(os.path.basename(f)) - if re.search(r'^\d', an): - an = numberPrefix + an - an = re.sub(r'[-.]', '_', an) - - assert an not in filePathAttributes - relpath = os.path.relpath(f, dirIcons) - code.append("{} = os.path.join(thisDir,r'{}')".format(an, relpath)) - filePathAttributes.add(an) - code.append('\n') - - raster = [f for f in files if re.search(r'.*\.(bsq|bip|bil|tif|tiff)$', f)] - vector = [f for f in files if re.search(r'.*\.(shp|kml|kmz)$', f)] - - addFiles(raster, 'Raster files:', numberPrefix='Img_') - addFiles(vector, 'Vector files:', numberPrefix='Shp_') - - # add self-test for file existence - if len(filePathAttributes) > 0: - code.extend( - [ - "", - "# self-test to check each file path attribute", - "for a in dir(sys.modules[__name__]):", - " v = getattr(sys.modules[__name__], a)", - " if type(v) == str and os.path.isabs(v):", - " if not os.path.exists(v):", - " sys.stderr.write('Missing package attribute file: {}={}'.format(a, v))", - "", - "# cleanup", - "del thisDir ", - ] - ) - - open(pathInit, 'w').write('\n'.join(code)) - print('Created ' + pathInit) - - -def createIconProvider(resourceFiles:list, pathIconProvider:str, className = 'IconProvider'): - - """ - - class IconProvider: - EnMAP_Logo = ':/enmapbox/icons/enmapbox.svg' - Map_Link_Remove = ':/enmapbox/icons/link_open.svg' - Map_Link = ':/enmapbox/icons/link_basic.svg' - Map_Link_Center = ':/enmapbox/icons/link_center.svg' - - :param resourceFiles: - :param pathIconProvider: - :return: - """ - info = ['#autogenerated file. do not modify'] - info.append('class {}:'.format(className)) - - for file in resourceFiles: - assert os.path.isfile(file) - assert file.endswith('.qrc') - - xml = '' - with open(file, encoding='utf8') as f: - xml = f.read() - #:/qps/ui/icons/classinfo.svg - tree = ET.fromstring(xml) - for xmlResource in tree.findall('qresource'): - prefix = xmlResource.attrib['prefix'] - for xmlFile in xmlResource.findall('file'): - uri = xmlFile.text - - uriSrc = ':/{}/{}'.format(prefix, uri) - - name, ext = os.path.splitext(uriSrc) - varname = '_'.join([s for s in re.split(r'[ :/\.]+', name) if len(s) > 0]) - #name = ' '.join(re.split('[:/\.]', name)) - #name = ''.join(x for x in name.title() if not x.isspace()) - info.append(" {} = '{}'".format(varname, uriSrc)) - - - - - -def createFilePackage(dirData): - import numpy as np - pathInit = jp(dirData, '__init__.py') - code = ['#!/usr/bin/env python', - '"""', - 'This file is auto-generated.', - 'Do not edit manually, as changes might get overwritten.', - '"""', - '__author__ = "auto-generated by {}"'.format(os.path.relpath(__file__, DIR_REPO)), - '__date__ = "{}"'.format(np.datetime64('now')), - '', - 'import sys, os', - '', - 'thisDir = os.path.dirname(__file__)', - '# File path attributes:', - ] - files = list(file_search(dirData, '*', recursive=True)) - - filePathAttributes = set() - def addFiles(files, comment=None, numberPrefix='File'): - if len(files) > 0: - if comment: - code.append('# '+comment) - for f in files: - attributeName, ext = os.path.splitext(os.path.basename(f)) - #take care of leading numbers - if re.search(r'^\d', attributeName): - attributeName = numberPrefix+attributeName - # append extension - attributeName += ext - #take care of not allowed characters - attributeName = re.sub(r'[-.]', '_',attributeName) - - - - assert attributeName not in filePathAttributes, attributeName - relpath = os.path.relpath(f, dirData) - code.append("{} = os.path.join(thisDir,r'{}')".format(attributeName, relpath)) - filePathAttributes.add(attributeName) - code.append('\n') - - raster = [f for f in files if re.search(r'.*\.(bsq|bip|bil|tif|tiff)$', f)] - vector = [f for f in files if re.search(r'.*\.(shp|kml|kmz)$', f)] - - addFiles(raster, 'Raster files:', numberPrefix='Img_') - addFiles(vector, 'Vector files:', numberPrefix='Shp_') - - #add self-test for file existence - if len(filePathAttributes) > 0: - code.extend( - [ - "", - "# self-test to check each file path attribute", - "for a in dir(sys.modules[__name__]):", - " v = getattr(sys.modules[__name__], a)", - " if type(v) == str and os.path.isabs(v):" , - " if not os.path.exists(v):", - " sys.stderr.write('Missing package attribute file: {}={}'.format(a, v))", - "", - "# cleanup", - "del thisDir ", - ] - ) - - open(pathInit, 'w').write('\n'.join(code)) - print('Created '+pathInit) - def compileQGISResourceFiles(pathQGISRepo:str, target:str=None): """ @@ -419,8 +281,9 @@ def compileQGISResourceFiles(pathQGISRepo:str, target:str=None): pathQGISRepo = pathQGISRepo.strip("'").strip('"') - assert os.path.isdir(pathQGISRepo) - if not isinstance(target, str): - target = jp(DIR_REPO, 'qgisresources') - searchAndCompileResourceFiles(pathQGISRepo, targetDir=target) - + if os.path.isdir(pathQGISRepo): + if not isinstance(target, str): + target = jp(DIR_REPO, 'qgisresources') + searchAndCompileResourceFiles(pathQGISRepo, targetDir=target) + else: + print('Unable to find local QGIS_REPOSITORY') diff --git a/eotimeseriesviewer/externals/qps/make/updateexternals.py b/eotimeseriesviewer/externals/qps/make/updateexternals.py index 6fd915c68d811e00ad5a71d999f79893f7007be5..fe7983d0490f75c8b1b99f8c6596b4119b871b5b 100644 --- a/eotimeseriesviewer/externals/qps/make/updateexternals.py +++ b/eotimeseriesviewer/externals/qps/make/updateexternals.py @@ -167,7 +167,7 @@ def updateRemoteLocations(locationsToUpdate:list): for id in locationsToUpdate: assert isinstance(id, str) - assert id in REMOTEINFOS.keys() + assert id in REMOTEINFOS.keys(), 'Unknown remote location key "{}"'.format(id) # check existing remotes print('Remotes:') diff --git a/eotimeseriesviewer/externals/qps/maptools.py b/eotimeseriesviewer/externals/qps/maptools.py index be1c7e1d9addb26a1f2532f2c2e74730a4c71b54..326f32d3f9252544240add5170044ac3ef26dbcb 100644 --- a/eotimeseriesviewer/externals/qps/maptools.py +++ b/eotimeseriesviewer/externals/qps/maptools.py @@ -292,7 +292,7 @@ class PixelScaleExtentMapTool(QgsMapTool): """ def __init__(self, canvas): super(PixelScaleExtentMapTool, self).__init__(canvas) - #see defintion getThemePixmap(const QString &):QPixmap in qgsapplication.cpp + self.mCursor = createCursor(':/qps/ui/icons/cursor_zoom_pixelscale.svg') self.setCursor(self.mCursor) canvas.setCursor(self.mCursor) @@ -363,7 +363,7 @@ class SpatialExtentMapTool(QgsMapToolEmitPoint): sigSpatialExtentSelected = pyqtSignal(SpatialExtent) def __init__(self, canvas:QgsMapCanvas): - QgsMapToolEmitPoint.__init__(self, self.canvas) + super(SpatialExtentMapTool, self).__init__(canvas) self.isEmittingPoint = False self.rubberBand = QgsRubberBand(self.canvas(), QgsWkbTypes.PolygonGeometry) self.setStyle(Qt.red, 1) @@ -402,7 +402,7 @@ class SpatialExtentMapTool(QgsMapToolEmitPoint): if crs is not None and rect is not None: extent = SpatialExtent(crs, rect) - self.rectangleDrawed.emit(extent) + self.sigSpatialExtentSelected.emit(extent) def canvasMoveEvent(self, e): diff --git a/eotimeseriesviewer/externals/qps/plotstyling/plotstylewidget.ui b/eotimeseriesviewer/externals/qps/plotstyling/plotstylewidget.ui index f0fc7ebcf4b7e0ade832a104a4055075bc99a10c..1ad4f2f3b3d2d6ca38904c11a17b41904dec7d5c 100644 --- a/eotimeseriesviewer/externals/qps/plotstyling/plotstylewidget.ui +++ b/eotimeseriesviewer/externals/qps/plotstyling/plotstylewidget.ui @@ -6,14 +6,29 @@ <rect> <x>0</x> <y>0</y> - <width>305</width> - <height>204</height> + <width>249</width> + <height>146</height> </rect> </property> <property name="windowTitle"> <string>Form</string> </property> <layout class="QGridLayout" name="gridLayout"> + <property name="leftMargin"> + <number>2</number> + </property> + <property name="topMargin"> + <number>2</number> + </property> + <property name="rightMargin"> + <number>2</number> + </property> + <property name="bottomMargin"> + <number>2</number> + </property> + <property name="spacing"> + <number>2</number> + </property> <item row="3" column="3"> <widget class="QSpinBox" name="sbLinePenWidth"> <property name="enabled"> @@ -121,10 +136,10 @@ <height>16777215</height> </size> </property> - <property name="colorDialogTitle"> + <property name="colorDialogTitle" stdset="0"> <string>Select Map Canvas Background Color</string> </property> - <property name="color"> + <property name="color" stdset="0"> <color> <red>255</red> <green>0</green> @@ -166,10 +181,10 @@ <height>16777215</height> </size> </property> - <property name="colorDialogTitle"> + <property name="colorDialogTitle" stdset="0"> <string>Select Map Canvas Background Color</string> </property> - <property name="color"> + <property name="color" stdset="0"> <color> <red>255</red> <green>0</green> @@ -189,10 +204,10 @@ <height>16777215</height> </size> </property> - <property name="colorDialogTitle"> + <property name="colorDialogTitle" stdset="0"> <string>Select Map Canvas Background Color</string> </property> - <property name="color"> + <property name="color" stdset="0"> <color> <red>255</red> <green>0</green> @@ -225,7 +240,14 @@ </widget> </item> <item row="4" column="1" rowspan="4" colspan="3"> - <widget class="PlotWidget" name="plotWidget"/> + <widget class="PlotWidget" name="plotWidget"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> </item> <item row="4" column="0"> <widget class="QCheckBox" name="cbIsVisible"> diff --git a/eotimeseriesviewer/externals/qps/plotstyling/plotstyling.py b/eotimeseriesviewer/externals/qps/plotstyling/plotstyling.py index 47a48624eb0a03d9dcc6b1deade158a6a74f701c..546268f00fde13ba92c03cf3bd2b44db786be841 100644 --- a/eotimeseriesviewer/externals/qps/plotstyling/plotstyling.py +++ b/eotimeseriesviewer/externals/qps/plotstyling/plotstyling.py @@ -21,7 +21,11 @@ # noinspection PyPep8Naming import os, json, sys + +from qgis.PyQt.QtGui import * +from qgis.PyQt.QtCore import * from ..externals.pyqtgraph.graphicsItems.ScatterPlotItem import drawSymbol +from ..externals.pyqtgraph.graphicsItems.PlotDataItem import PlotDataItem from ..utils import * from ..models import OptionListModel, Option, currentComboBoxValue, setCurrentComboBoxValue from ..externals import pyqtgraph as pg @@ -191,9 +195,26 @@ class PlotStyle(QObject): """ sigUpdated = pyqtSignal() + @staticmethod + def fromPlotDataItem( pdi:PlotDataItem): + + ps = PlotStyle() + linePen = pg.mkPen(pdi.opts['pen']) + + ps.linePen = linePen + ps.markerSymbol = pdi.opts['symbol'] + ps.markerBrush = pg.mkBrush(pdi.opts['symbolBrush']) + ps.markerSize = pdi.opts['symbolSize'] + ps.markerPen = pg.mkPen(pdi.opts['symbolPen']) + ps.mIsVisible = pdi.isVisible() + + return ps + + def __init__(self, **kwds): plotStyle = kwds.get('plotStyle') - if plotStyle: kwds.pop('plotStyle') + if plotStyle: + kwds.pop('plotStyle') super(PlotStyle, self).__init__() self.markerSymbol = MARKERSYMBOLS[0].mValue @@ -221,6 +242,38 @@ class PlotStyle(QObject): if plotStyle: self.copyFrom(plotStyle) + def lineWidth(self)->int: + return self.linePen.width() + + def setLineWidth(self, width:int): + self.linePen.setWidth(width) + + def lineColor(self)->QColor: + return self.linePen.color() + + def setLineColor(self, color:QColor): + if not isinstance(color, QColor): + color = QColor(color) + self.linePen.setColor(color) + + def apply(self, pdi:PlotDataItem, updateItem:bool=True): + + assert isinstance(pdi, PlotDataItem) + + pdi.opts['pen'] = pg.mkPen(self.linePen) + pdi.opts['symbol'] = self.markerSymbol + pdi.opts['symbolPen'] = pg.mkPen(self.markerPen) + pdi.opts['symbolBrush'] = pg.mkBrush(self.markerBrush) + pdi.opts['symbolSize'] = self.markerSize + + pdi.setVisible(self.mIsVisible) + if updateItem: + pdi.updateItems() + + + + + @staticmethod def fromJSON(jsonString: str): """ @@ -312,6 +365,14 @@ class PlotStyle(QObject): """ return self.mIsVisible + def __copy__(self): + style = PlotStyle() + style.copyFrom(self) + return style + + def clone(self): + return copy.copy(self) + def copyFrom(self, plotStyle): """ Copy plot settings from another plot style @@ -434,7 +495,7 @@ class PlotStyle(QObject): class PlotStyleWidget(QWidget, loadUI('plotstylewidget.ui')): sigPlotStyleChanged = pyqtSignal(PlotStyle) - def __init__(self, title='<#>', parent=None, x=None, y=None): + def __init__(self, title='<#>', parent=None, x=None, y=None, plotStyle:PlotStyle=PlotStyle()): super(PlotStyleWidget, self).__init__(parent) self.setupUi(self) assert isinstance(self.plotWidget, pg.PlotWidget) @@ -487,7 +548,7 @@ class PlotStyleWidget(QWidget, loadUI('plotstylewidget.ui')): self.sbLinePenWidth.valueChanged.connect(self.refreshPreview) self.mLastPlotStyle = None self.cbIsVisible.toggled.connect(self.refreshPreview) - self.setPlotStyle(PlotStyle()) + self.setPlotStyle(plotStyle) self.refreshPreview() def toggleWidgetEnabled(self, cb: QComboBox, widgets: list): @@ -502,6 +563,16 @@ class PlotStyleWidget(QWidget, loadUI('plotstylewidget.ui')): assert isinstance(w, QWidget) w.setEnabled(enabled) + def setPreviewVisible(self, b:bool): + """ + Sets the visibility of the preview window. + :param b: + :type b: + """ + assert isinstance(b, bool) + self.plotWidget.setVisible(b) + + def refreshPreview(self, *args): if not self.mBlockUpdates: # log(': REFRESH NOW') @@ -570,7 +641,7 @@ class PlotStyleWidget(QWidget, loadUI('plotstylewidget.ui')): style.markerBrush.setColor(self.btnMarkerBrushColor.color()) - # style.linePen = pg.mkPen(color=self.btnLinePenColor.color(), + # style.linePen = pg.mkPen(plotStyle=self.btnLinePenColor.plotStyle(), # width=self.sbLinePenWidth.value(), # style=currentComboBoxValue(self.cbLinePenStyle)) style.linePen.setColor(self.btnLinePenColor.color()) diff --git a/eotimeseriesviewer/externals/qps/setup.py b/eotimeseriesviewer/externals/qps/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..d92bb6e0fa077fb9c336f1e095caf00fb3f9799c --- /dev/null +++ b/eotimeseriesviewer/externals/qps/setup.py @@ -0,0 +1,26 @@ +import os, sys + +DIR_QGIS_REPO = os.environ.get('DIR_QGIS_REPO') + +def compileQPSResources(): + pathQPSDir = os.path.dirname(__file__) + pathQPSRoot = os.path.dirname(pathQPSDir) + + addSysPath = pathQPSRoot not in sys.path + if addSysPath: + sys.path.append(pathQPSRoot) + + from qps.make.make import searchAndCompileResourceFiles, compileQGISResourceFiles + searchAndCompileResourceFiles(pathQPSDir) + if os.path.isdir(DIR_QGIS_REPO): + compileQGISResourceFiles(DIR_QGIS_REPO, None) + + if addSysPath: + sys.path.remove(pathQPSRoot) + + + +if __name__ == "__main__": + compileQPSResources() + + diff --git a/eotimeseriesviewer/externals/qps/speclib/__init__.py b/eotimeseriesviewer/externals/qps/speclib/__init__.py index 234fbe77f70e5a796e2fadb684118375046dd0db..c13481683ab1032396601f75910f5430c2eaaec6 100644 --- a/eotimeseriesviewer/externals/qps/speclib/__init__.py +++ b/eotimeseriesviewer/externals/qps/speclib/__init__.py @@ -27,11 +27,22 @@ * * *************************************************************************** """ -import sys +import sys, enum from qgis.core import * from qgis.gui import * + from qgis.PyQt.QtCore import QSettings +class SpectralLibrarySettingsKey(enum.Enum): + CURRENT_PROFILE_STYLE = 1 + DEFAULT_PROFILE_STYLE = 2 + BACKGROUND_COLOR = 3 + FOREGROUND_COLOR = 4 + INFO_COLOR = 5 + USE_VECTOR_RENDER_COLORS = 6 + + + def speclibSettings()->QSettings: """ diff --git a/eotimeseriesviewer/externals/qps/speclib/artmo.py b/eotimeseriesviewer/externals/qps/speclib/artmo.py new file mode 100644 index 0000000000000000000000000000000000000000..c037f50f2f77896b08aee58e4489fed555c4fe72 --- /dev/null +++ b/eotimeseriesviewer/externals/qps/speclib/artmo.py @@ -0,0 +1,136 @@ + +import os, sys, re, pathlib, json, io, re, linecache, collections +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.PyQt.QtWidgets import * +import csv as pycsv +from .spectrallibraries import SpectralProfile, SpectralLibrary, AbstractSpectralLibraryIO, FIELD_FID, FIELD_VALUES, FIELD_NAME, findTypeFromString, createQgsField + +class ARTMOSpectralLibraryIO(AbstractSpectralLibraryIO): + """ + I/O Interface for ARTMO CSV profile outputs. + See https://artmotoolbox.com/tools.html for details. + """ + @staticmethod + def canRead(path:str): + """ + Returns true if it can read the source defined by path + :param path: source uri + :return: True, if source is readable. + """ + if not isinstance(path, str) and os.path.isfile(path): + return False + + # check if an _meta.txt exists + pathMeta = os.path.splitext(path)[0] + '_meta.txt' + if not os.path.isfile(pathMeta): + return False + + with open(pathMeta, 'r', encoding='utf-8') as f: + for line in f: + if re.search(r'Line 1, Column \d \.{3} end:', line, re.I): + return True + + return False + + @staticmethod + def readFrom(path, progressDialog:QProgressDialog=None)->SpectralLibrary: + """ + Returns the SpectralLibrary read from "path" + :param path: source of SpectralLibrary + :return: SpectralLibrary + """ + delimiter = ',' + xUnit = 'nm' + bn = os.path.basename(path) + + pathMeta = os.path.splitext(path)[0]+'_meta.txt' + + assert os.path.isfile(path) + assert os.path.isfile(pathMeta) + + + with open(pathMeta, 'r', encoding='utf-8') as f: + + meta = f.read() + + header = re.search(r'Line (\d+).*Column (\d+) ... end: Wavelength', meta) + firstLine = int(header.group(1)) - 1 + firstXValueColumn = int(header.group(2)) - 1 + + COLUMNS = collections.OrderedDict() + for c, name in re.findall(r'Column (\d+): ([^\t]+)', meta): + COLUMNS[int(c)-1] = name + + + + speclib = SpectralLibrary() + speclib.startEditing() + + for name in COLUMNS.values(): + speclib.addAttribute(createQgsField(name, 1.0)) + speclib.commitChanges() + + + profiles = [] + + with open(path, 'r', encoding='utf-8') as f: + for iLine, line in enumerate(f.readlines()): + + if len(line) == 0: + continue + + parts = line.split(delimiter) + if iLine == firstLine: + # read the header data + + xValues = [float(v) for v in parts[firstXValueColumn:]] + elif iLine > firstLine: + + + yValues = [float(v) for v in parts[firstXValueColumn:]] + profile = SpectralProfile(fields=speclib.fields()) + + name = None + if name is None: + name = '{}:{}'.format(bn, len(profiles) +1) + + profile.setName(name) + + for iCol, name in COLUMNS.items(): + profile.setAttribute(name, float(parts[iCol])) + + profile.setValues(x=xValues, y=yValues, xUnit=xUnit) + profiles.append(profile) + + + + + + speclib.startEditing() + speclib.addProfiles(profiles) + speclib.commitChanges() + return speclib + + + @staticmethod + def addImportActions(spectralLibrary: SpectralLibrary, menu: QMenu) -> list: + + def read(speclib: SpectralLibrary): + + path, filter = QFileDialog.getOpenFileName(caption='ARTMO CSV File', + filter='All type (*.*);;Text files (*.txt);; CSV (*.csv)') + if os.path.isfile(path): + + sl = ARTMOSpectralLibraryIO.readFrom(path) + if isinstance(sl, SpectralLibrary): + speclib.startEditing() + speclib.beginEditCommand('Add ARTMO profiles from {}'.format(path)) + speclib.addSpeclib(sl, True) + speclib.endEditCommand() + speclib.commitChanges() + + m = menu.addAction('ARTMO') + m.setToolTip('Adds profiles from an ARTMO csv text file.') + m.triggered.connect(lambda *args, sl=spectralLibrary: read(sl)) + diff --git a/eotimeseriesviewer/externals/qps/speclib/asd.py b/eotimeseriesviewer/externals/qps/speclib/asd.py index b8d7ef8175ab50aa3bc9c8098fc373ece4f832a1..2c2f6f004a3c8ba9f1a16a96ec33ce0288281ad9 100644 --- a/eotimeseriesviewer/externals/qps/speclib/asd.py +++ b/eotimeseriesviewer/externals/qps/speclib/asd.py @@ -5,25 +5,498 @@ asd.py Reading Spectral Profiles from ASD data --------------------- - Date : Okt 2018 - Copyright : (C) 2018 by Benjamin Jakimow + Date : Aug 2019 + Copyright : (C) 2019 by Benjamin Jakimow Email : benjamin.jakimow@geo.hu-berlin.de -*************************************************************************** -* * -* This file is part of the EnMAP-Box. * -* * -* The EnMAP-Box 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 3 of the License, or * -* (at your option) any later version. * -* * -* The EnMAP-Box is distributed in the hope that it will be useful, * -* but WITHOUT ANY WARRANTY; without even the implied warranty of * -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -* GNU General Public License for more details. * -* * -* You should have received a copy of the GNU General Public License * -* along with the EnMAP-Box. If not, see <http://www.gnu.org/licenses/>. * -* * -*************************************************************************** -""" \ No newline at end of file +""" + +import os, sys, re, pathlib, json, enum, struct, time, datetime, typing, collections +import numpy as np +import csv as pycsv +from .spectrallibraries import * + + +""" + +Offset Size Type Description Comment +3 char co[3]; // File Version - as6 +3 157 char comments[157]; // comment field +160 18 struct tm when; // time when spectrum was saved +178 1 byte program_version; // ver. of the programcreatinf this file. +// major ver in upper nibble, min in lower +179 1 byte file_version; // spectrum file format version +180 1 byte itime; // Not used after v2.00 +181 1 byte dc_corr; // 1 if DC subtracted, 0 if not +182 4 time_t (==long) dc_time; // Time of last dc, seconds since 1/1/1970 +186 1 byte data_type; // see *_TYPE below +187 4 time_t (==long) ref_time; // Time of last wr, seconds since 1/1/1970 +191 4 float ch1_wavel; // calibrated starting wavelength in nm +195 4 float wavel_step; // calibrated wavelength step in nm +199 1 byte data_format; // format of spectrum. +200 1 byte old_dc_count; // Num of DC measurements in the avg +201 1 byte old_ref_count; // Num of WR in the average +202 1 byte old_sample_count; // Num of spec samples in the avg +203 1 byte application; // Which application created APP_DATA +204 2 ushort channels; // Num of channels in the detector +206 128 APP_DATA app_data; // Application-specific data +334 56 GPS_DATA gps_data; // GPS position, course, etc. +390 4 ulong it; // The actual integration time in ms +394 2 int fo; // The fo attachment's view in degrees +396 2 int dcc; // The dark current correction value +398 2 uint calibration; // calibration series +400 2 uint instrument_num; // instrument number +402 4 float ymin; // setting of the y axis' min value +406 4 float ymax; // setting of the y axis' max value +410 4 float xmin; // setting of the x axis' min value +414 4 float xmax; // setting of the x axis' max value +418 2 uint ip_numbits; // instrument's dynamic range +420 1 byte xmode; // x axis mode. See *_XMODE +421 4 byte flags[4]; // flags(0) = AVGFIX'ed +// flags(1) see below +425 2 unsigned dc_count; // Num of DC measurements in the avg +427 2 unsigned ref_count; // Num of WR in the average +429 2 unsigned sample_count; // Num of spec samples in the avg +431 1 byte instrument; // Instrument type. See defs below +432 4 ulong bulb; // The id number of the cal bulb +436 2 uint swir1_gain; // gain setting for swir 1 +438 2 uint swir2_gain; // gain setting for swir 2 +440 2 uint swir1_offset; // offset setting for swir 1 +442 2 uint swir2_offset; // offset setting for swir 2 +444 4 float splice1_wavelength; // wavelength of VNIR and SWIR1 splice +448 4 float splice2_wavelength; // wavelength of SWIR1 and SWIR2 splice +452 27 float SmartDetectorType // Data from OL731 device +479 5 byte spare[5]; // fill to 484 bytes +""" + +ASD_VERSIONS = ['ASD', 'asd', 'as6', 'as7', 'as8'] + + +class SpectrumDataType(enum.IntEnum): + + RAW_TYPE = 0 + REF_TYPE = 1 + RAD_TYPE = 2 + NOUNITS_TYPE = 3 + IRRAD_TYPE = 4 + QI_TYPE = 5 + TRANS_TYPE = 6 + UNKNOWN_TYPE = 7 + ABS_TYPE = 8 + + + +class SpectrumDataFormat(enum.Enum): + + FLOAT_FORMAT = 0 + INTEGER_FORMAT = 1 + DOUBLE_FORMAT = 2 + UNKOWN_FORMAT = 3 + +class InstrumentType(enum.Enum): + + UNKOWN_INSTRUMENT = 0 + PSII_INSTRUMENT = 1 + LSVNIR_INSTRUMENT = 2 + FSVNIR_INSTRUMENT = 3 + FSFR_INSTRUMENT = 4 + FSNIR_INSTRUMENT = 5 + CHEM__INSTRUMENT = 6 + FSFR_UNATTENDED_INSTRUMENT = 7 + +class GPS_DATA(object): + + def __init__(self, DATA): + ASD_GPS_DATA = struct.Struct("= 5d 2b cl 2b 5B 2c").unpack(DATA) + + + self.true_heading = self.speed = self.latitude = self.longitude = self.altitude = ASD_GPS_DATA[0:5] + self.flags = ASD_GPS_DATA[5:7] + self.hardware_mode = ASD_GPS_DATA[7] + self.timestamp = np.datetime64('1970-01-01') + np.timedelta64(ASD_GPS_DATA[8],'s') + +class SmartDetectorType(object): + + def __init__(self, DATA): + """ + + :param DATA: + """ + # 27 byte struct + # 1 i int 4 4 + # 3 f float 12 16 + # 1 h short 2 18 + # 1 b byte 1 19 + # 2 f float 8 27 + + if DATA is not None: + DETECTOR = struct.Struct('= 1i 3f 1h 1b 2f').unpack(DATA) + else: + DETECTOR = [None, None, None, None, None, None, None, None] + self.serial_number, self.Signal, self.dark, self.ref, self.Status, self.avg, self.humid, self.temp = DETECTOR + + + + +class TM_STRUCT(object): + + def __init__(self, DATA): + self.tm_sec, self.tm_min, self.tm_hour, self.tm_mday, \ + self.tm_mon, self.tm_year, self.tm_wday, \ + self.tm_yday, self.tm_isdst = struct.Struct("= 9h").unpack(DATA) + + def date(self): + return datetime.date(self.year(), self.month(), self.day()) + + def datetime(self): + return datetime.datetime(self.year(), self.month(), self.day(), hour=self.tm_hour, minute=self.tm_min, second=self.tm_sec) + + def time(self): + return datetime.time(hour=self.tm_hour, minute=self.tm_min, second=self.tm_sec) + + def datetime64(self)->np.datetime64: + return np.datetime64('{:04}-{:02}-{:02}T{:02}:{:02}:{:02}'.format(self.year(), self.month(), self.day(), self.tm_hour, self.tm_min, self.tm_sec)) + + def doy(self)->int: + return self.tm_yday + + def day(self)->int: + return self.tm_mday + + def month(self)->int: + return self.tm_mon + 1 + + def year(self)->int: + return self.tm_year + 1900 + + +class ASDBinaryFile(object): + """ + Wrapper class to access a ASD File Format binary file. + See ASD File Format, version 8, revision B, ASD Inc., a PANalytical company, 2555 55th Street, Suite 100 Boulder, CO 80301. + """ + + + def __init__(self): + super(ASDBinaryFile, self).__init__() + + # initialize all variables in the ASD Binary file header + self.co = None + self.comments = None + self.when = None + self.program_version = None + self.file_version = None + self.itime = None + self.dc_corr = None + self.dc_time = None + self.data_type = None + self.ref_time = None + self.ch1_wavel = None + self.wavel_step = None + self.data_format = None + self.old_dc_count = None + self.old_ref_count = None + self.old_sample_count = None + self.application = None + self.channels = None + self.app_data = None + self.gps_data = None + self.it = None + self.fo = None + self.dcc = None + self.calibration = None + self.instrument_num = None + self.ymin = None + self.ymax = None + self.xmin = None + self.ymax = None + self.ip_numbits = None + self.xmode = None + self.flags = None + self.dc_count = None + self.ref_count = None + self.sample_count = None + self.instrument = None + self.bulb = None + self.swir1_gain = None + self.swir2_gain = None + self.swir1_offset = None + self.swir2_offset = None + self.splice1_wavelength = None + self.splice2_wavelength = None + self.SmartDetectorType = None + self.spare = None + + + self.Spectrum = None + self.ReferenceFlag = None + self.ReferenceTime = None + self.SpectrumTime = None + self.SpectrumDescription = None + self.Reference = None + + + def xValues(self)->np.ndarray: + values = np.linspace(self.ch1_wavel, self.ch1_wavel + self.channels * self.wavel_step - 1, self.channels) + return values + + + def yValues(self)->np.ndarray: + return self.Spectrum + + + def readFromBinaryFile(self, path: str): + with open(path, 'rb') as f: + + DATA = f.read() + + def sub(start, len): + return DATA[start:start + len] + + + self.co = DATA[0:3].decode('utf-8') + self.comments = DATA[3:(3 + 157)].decode('utf-8') + + self.when = TM_STRUCT(DATA[160:(160 + 18)]) + self.program_version = DATA[178] + self.file_version = DATA[179] + self.itime = DATA[180] + self.dc_corr = DATA[181] + self.dc_time = np.datetime64('1970-01-01') + np.timedelta64(struct.unpack('l', DATA[182:182 + 4])[0], 's') + self.data_type = SpectrumDataType(DATA[186]) + self.ref_time = np.datetime64('1970-01-01') + np.timedelta64(struct.unpack('l', DATA[182:182 + 4])[0], 's') + self.ch1_wavel = struct.unpack('f', sub(191, 4))[0] + self.wavel_step = struct.unpack('f', sub(195, 4))[0] + self.data_format = SpectrumDataFormat(DATA[199]) + self.old_dc_count = DATA[200] + self.old_ref_count = DATA[201] + self.old_sample_count = DATA[202] + self.application = DATA[203] + + self.channels = struct.unpack('H', sub(204, 2))[0] + + self.app_data = sub(206, 128) + self.gps_data = GPS_DATA(sub(334, 56)) + + self.it = struct.unpack('L', sub(390, 4))[0] + self.fo = struct.unpack('h', sub(394, 2))[0] + self.dcc = struct.unpack('h', sub(396, 2))[0] + + self.calibration = struct.unpack('H', sub(398, 2))[0] + self.instrument_num = struct.unpack('H', sub(400, 2))[0] + + self.ymin, self.ymax, self.xmin, self.ymax = struct.unpack('4f', sub(402, 4 * 4)) + + self.ip_numbits = struct.unpack('H', sub(418, 2))[0] + self.xmode = struct.unpack('b', sub(420, 1))[0] + self.flags = struct.unpack('4b', sub(421, 4)) + + self.dc_count, self.ref_count, self.sample_count = struct.unpack('3H', sub(425, 2 * 3)) + + self.instrument = sub(431, 1) + self.bulb = struct.unpack('L', sub(432, 4)) + self.swir1_gain, self.swir2_gain, self.swir1_offset, self.swir2_offset = struct.unpack('4H', sub(436, 2 * 4)) + + self.splice1_wavelength, self.splice2_wavelength = struct.unpack('2f', sub(444, 2 * 4)) + + self.SmartDetectorType = SmartDetectorType(sub(452, 27)) + self.spare = sub(479, 5) + + if self.data_format == SpectrumDataFormat.FLOAT_FORMAT: + size = 4 * self.channels + fmt = '{}f'.format(self.channels) + + elif self.data_format == SpectrumDataFormat.DOUBLE_FORMAT: + size = 8 * self.channels + fmt = '{}d'.format(self.channels) + elif self.data_format == SpectrumDataFormat.INTEGER_FORMAT: + size = 4 * self.channels + fmt = '{}i'.format(self.channels) + else: + raise Exception() + + self.Spectrum = np.array(struct.unpack(fmt, sub(484, size))) + + return self + + +class ASDSpectralLibraryIO(AbstractSpectralLibraryIO): + + + @staticmethod + def addImportActions(spectralLibrary: SpectralLibrary, menu: QMenu) -> list: + + def read(speclib: SpectralLibrary): + + pathes, filter = QFileDialog.getOpenFileNames(caption='ASD FilesCSV File', + filter='All type (*.*);;Text files (*.txt);; CSV (*.csv);;ASD (*.asd)') + + if len(pathes) > 0: + sl = ASDSpectralLibraryIO.readFrom(pathes) + if isinstance(sl, SpectralLibrary): + speclib.startEditing() + speclib.beginEditCommand('Add ASD profiles') + speclib.addSpeclib(sl, True) + speclib.endEditCommand() + speclib.commitChanges() + + a = menu.addAction('ASD') + a.setToolTip('Loads ASD FieldSpec files (binary or text)') + a.triggered.connect(lambda *args, sl=spectralLibrary: read(sl)) + + + @staticmethod + def canRead(path, binary:bool=None)->bool: + """ + Returns true if it can read the source defined by path + :param path: source uri + :return: True, if source is readable. + """ + if not os.path.isfile(path): + return False + + + if isinstance(binary, bool): + if binary: + st = os.stat(path) + + if st.st_size < 484 + 1 or st.st_size > 2 ** 20: + return False + + with open(path, 'rb') as f: + DATA = f.read(3) + co = DATA[0:3].decode('utf-8') + if co not in ASD_VERSIONS: + return False + else: + return True + + return False + else: + try: + with open(path, 'r', encoding='utf-8') as f: + lines = [] + for line in f: + if len(lines) >= 2: + break + line = line.strip() + if len(line) > 0: + lines.append(line) + + if len(lines) == 2: + return re.search(r'^wavelength[;]', lines[0], re.I) is not None \ + and re.search(r'^\d+(\.\d+)?[;]', lines[1]) is not None + + return False + except Exception as ex: + return False + + else: + if ASDSpectralLibraryIO.canRead(path, binary=True): + return True + else: + return ASDSpectralLibraryIO.canRead(path, binary=False) + + + + @staticmethod + def readFrom(pathes:typing.Union[str, list], asdFields:typing.Iterable[str] = None, progressDialog:QProgressDialog=None)->SpectralLibrary: + """ + :param pathes: list of source paths + :param asdFields: list of header information to be extracted from ASD binary files + :return: SpectralLibrary + """ + if asdFields is None: + #default fields to add as meta data + asdFields = ['when', 'ref_time', 'dc_time', 'dc_corr', 'it', 'sample_count', 'instrument_num', 'spec_type'] + + if not isinstance(pathes, list): + pathes = [pathes] + + sl = SpectralLibrary() + + profiles = [] + asddFieldsInitialized = False + + for filePath in pathes: + bn = os.path.basename(filePath) + + if ASDSpectralLibraryIO.canRead(filePath, binary=True): + asd = ASDBinaryFile().readFromBinaryFile(filePath) + if isinstance(asd, ASDBinaryFile): + + if not asddFieldsInitialized: + sl.startEditing() + + asdFields = [n for n in asdFields if n not in sl.fieldNames() and n in asd.__dict__.keys()] + for n in asdFields: + v = asd.__dict__[n] + if isinstance(v, TM_STRUCT): + sl.addAttribute(createQgsField(n, '')) # TM struct will use a VARCHAR field to express the time stamp + else: + sl.addAttribute(createQgsField(n, v)) + + asddFieldsInitialized = True + sl.commitChanges() + sl.startEditing() + + p = SpectralProfile(fields=sl.fields()) + p.setName(bn) + + for n in asdFields: + value = asd.__dict__[n] + if isinstance(value, np.datetime64): + value = str(value) + elif isinstance(value, TM_STRUCT): + value = str(value.datetime64()) + p.setAttribute(n, value) + + p.setValues(asd.xValues(), asd.yValues(), xUnit='nm') + profiles.append(p) + elif ASDSpectralLibraryIO.canRead(filePath, binary=False): + + + with open(filePath, 'r', encoding='utf-8') as f: + profiles = [] + lines = f.readlines() + delimiter = ';' + if len(lines) >= 2: + + hdrLine = lines[0].strip().split(delimiter) + if len(hdrLine) >= 2: + profileNames = hdrLine[1:] + + xValues = [] + DATA = dict() + for line in lines[1:]: + line = line.split(delimiter) + wl = float(line[0]) + xValues.append(wl) + DATA[wl] = [float(v) for v in line[1:]] + + for i, name in enumerate(profileNames): + + yValues = [DATA[wl][i] for wl in xValues] + xUnit = 'nm' + profile = SpectralProfile(fields=sl.fields()) + profile.setName(name) + profile.setValues(x=xValues, y=yValues, xUnit=xUnit) + + profiles.append(profile) + if len(profiles) > 0: + sl.startEditing() + sl.addProfiles(profiles) + profiles.clear() + sl.commitChanges() + sl.startEditing() + + + else: + print('Unable to read {}'.format(filePath), file=sys.stderr) + + sl.startEditing() + sl.addProfiles(profiles, addMissingFields=False) + sl.commitChanges() + return sl + + + diff --git a/eotimeseriesviewer/externals/qps/speclib/clipboard.py b/eotimeseriesviewer/externals/qps/speclib/clipboard.py index 1afe90218f90ca2181a15649adfafbe148a1f3f5..3aee1c76d931a02056c3e6308957947ba9330537 100644 --- a/eotimeseriesviewer/externals/qps/speclib/clipboard.py +++ b/eotimeseriesviewer/externals/qps/speclib/clipboard.py @@ -56,7 +56,7 @@ class ClipboardIO(AbstractSpectralLibraryIO): return False @staticmethod - def readFrom(path=None): + def readFrom(path=None, progressDialog:QProgressDialog=None): clipboard = QApplication.clipboard() mimeData = clipboard.mimeData() assert isinstance(mimeData, QMimeData) @@ -70,7 +70,7 @@ class ClipboardIO(AbstractSpectralLibraryIO): return SpectralLibrary() @staticmethod - def write(speclib, path=None, mode=None, sep=None, newline=None): + def write(speclib, path=None, mode=None, sep=None, newline=None, progressDialog:QProgressDialog): if mode is None: mode = ClipboardIO.WritingModes.ALL assert isinstance(speclib, SpectralLibrary) diff --git a/eotimeseriesviewer/externals/qps/speclib/csvdata.py b/eotimeseriesviewer/externals/qps/speclib/csvdata.py index 2699ec52b8ef2ba4758e5f84ab9a4dd8d52f7c2e..48890687d5524d51ec75f09d1fd1fc5b393dd875 100644 --- a/eotimeseriesviewer/externals/qps/speclib/csvdata.py +++ b/eotimeseriesviewer/externals/qps/speclib/csvdata.py @@ -44,6 +44,40 @@ class CSVSpectralLibraryIO(AbstractSpectralLibraryIO): REGEX_HEADERLINE = re.compile('^'+'[\t;,]'.join(STD_NAMES)+'\\t.*') REGEX_BANDVALUE_COLUMN = re.compile(r'^(?P<bandprefix>\D+)?(?P<band>\d+)[ _]*(?P<xvalue>-?\d+\.?\d*)?[ _]*(?P<xunit>\D+)?', re.IGNORECASE) + @staticmethod + def addImportActions(spectralLibrary:SpectralLibrary, menu:QMenu)->list: + + def read(speclib:SpectralLibrary, dialect): + + path, ext = QFileDialog.getOpenFileName(caption='Import CSV File', filter='All type (*.*);;Text files (*.txt);; CSV (*.csv)') + if isinstance(path, str) and os.path.isfile(path): + + sl = CSVSpectralLibraryIO.readFrom(path, dialect) + if isinstance(sl, SpectralLibrary): + speclib.addSpeclib(sl, True) + m = menu.addMenu('CSV') + + a = m.addAction('Excel (TAB)') + a.setToolTip('Imports Spectral Profiles from a Excel CSV sheet.') + a.triggered.connect(lambda *args, sl=spectralLibrary: read(sl, pycsv.excel_tab)) + + a = m.addAction('Excel (,)') + a.setToolTip('Imports Spectral Profiles from a Excel CSV sheet.') + a.triggered.connect(lambda *args, sl=spectralLibrary: read(sl, pycsv.excel)) + + @staticmethod + def addExportActions(spectralLibrary: SpectralLibrary, menu: QMenu) -> list: + + def write(speclib: SpectralLibrary): + path, filter = QFileDialog.getSaveFileName(caption='Write to CSV File', + filter='CSV (*.csv);;Text files (*.txt)') + if isinstance(path, str) and len(path) > 0: + CSVSpectralLibraryIO.write(spectralLibrary, path) + + + m = menu.addAction('CSV Table') + m.triggered.connect(lambda *args, sl=spectralLibrary: write(sl)) + @staticmethod def isHeaderLine(line: str) -> str: """ @@ -85,7 +119,7 @@ class CSVSpectralLibraryIO(AbstractSpectralLibraryIO): @staticmethod - def write(speclib, path, dialect=pycsv.excel_tab)->list: + def write(speclib, path, progressDialog:QProgressDialog=None, dialect=pycsv.excel_tab)->list: """ Writes the speclib into a CSv file :param speclib: SpectralLibrary @@ -102,7 +136,7 @@ class CSVSpectralLibraryIO(AbstractSpectralLibraryIO): return [path] @staticmethod - def readFrom(path=None, dialect=pycsv.excel_tab): + def readFrom(path=None, progressDialog:QProgressDialog=None, dialect=pycsv.excel_tab): f = open(path, 'r', encoding='utf-8') text = f.read() f.close() diff --git a/eotimeseriesviewer/externals/qps/speclib/ecosis.py b/eotimeseriesviewer/externals/qps/speclib/ecosis.py new file mode 100644 index 0000000000000000000000000000000000000000..821fd7984054035595da0a769ec0d8f0495f2740 --- /dev/null +++ b/eotimeseriesviewer/externals/qps/speclib/ecosis.py @@ -0,0 +1,249 @@ + +import os, sys, re, pathlib, json, io, re, linecache +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.PyQt.QtWidgets import * +from qgis.core import * + + +import csv as pycsv +from .spectrallibraries import SpectralProfile, SpectralLibrary, AbstractSpectralLibraryIO, FIELD_FID, FIELD_VALUES, FIELD_NAME, findTypeFromString, createQgsField + +class EcoSISCSVDialect(pycsv.Dialect): + delimiter = ',' + quotechar = '"' + doublequote = True + skipinitialspace = False + lineterminator = '\n' + escapechar = '\\' + quoting = pycsv.QUOTE_NONE + + +def findDialect(file)->pycsv.Dialect: + + if isinstance(file, str): + file = open(file, 'r', encoding='utf-8') + + line = '' + while len(line) == 0: + line = file.readline() + + + delimiters = [',', ';', '\t'] + counts = [len(line.split(delimiter)) for delimiter in delimiters] + + + dialect = EcoSISCSVDialect() + dialect.delimiter = delimiters[counts.index(max(counts))] + dialect.lineterminator = '\n\r' if line.endswith('\n\r') else line[-1] + + file.seek(0) + s = "" + return dialect + +class EcoSISSpectralLibraryIO(AbstractSpectralLibraryIO): + """ + I/O Interface for the EcoSIS spectral library format. + See https://ecosis.org for details. + """ + @staticmethod + def canRead(path:str): + """ + Returns true if it can read the source defined by path + :param path: source uri + :return: True, if source is readable. + """ + if not isinstance(path, str) and os.path.isfile(path): + return False + try: + with open(path, 'r', encoding='utf-8') as f: + for line in f: + line = f.readline().strip() + if len(line) > 0: + # most-right header name must be a number + lastColumn = [c for c in re.split(r'[\t\n;,]', line) if c != ''][-1] + return re.search(r'^\d+(\.\d+)?$', lastColumn) is not None + except Exception as ex: + print(ex, file=sys.stderr) + + return False + + @staticmethod + def readFrom(path, progressDialog:QProgressDialog=None)->SpectralLibrary: + """ + Returns the SpectralLibrary read from "path" + :param path: source of SpectralLibrary + :return: SpectralLibrary + """ + + # the EcoSIS CSV outputs are encoded as UTF-8 with BOM + with open(path, 'r', encoding='utf-8-sig') as f: + + bn = os.path.basename(path) + + dialect = findDialect(f) + + reader = pycsv.DictReader(f, dialect=dialect) + fieldnames = reader.fieldnames + + if fieldnames[0].startswith('\ufeff'): + s = "" + fieldnames = [n for n in fieldnames if len(n) > 0] + + + xUnit = yUnit = None + xValueNames = [] + for fieldName in reversed(fieldnames): + if re.search(r'^\d+(\.\d+)?$', fieldName): + xValueNames.insert(0, fieldName) + else: + break + s = "" + xValues = [float(n) for n in xValueNames] + if len(xValues) == 0: + s = "" + if xValues[0] > 200: + xUnit = 'nm' + + + fieldnames = [n for n in fieldnames if n not in xValueNames] + + speclib = SpectralLibrary() + speclib.startEditing() + + profiles = [] + LUT_FIELD_TYPES = dict() + missing_field_definitions = [n for n in fieldnames if n not in speclib.fieldNames()] + for i, row in enumerate(reader): + if len(missing_field_definitions) > 0: + for fieldName in missing_field_definitions[:]: + fieldValue = row[fieldName] + if fieldValue == 'NA': + continue + + fieldType = findTypeFromString(fieldValue) + LUT_FIELD_TYPES[fieldName] = fieldType + + qgsField = createQgsField(fieldName, fieldType(fieldValue)) + speclib.addAttribute(qgsField) + assert speclib.commitChanges() + speclib.startEditing() + missing_field_definitions.remove(fieldName) + + profile = SpectralProfile(fields=speclib.fields()) + yValues = [float(row[n]) for n in xValueNames] + profile.setValues(x=xValues, y=yValues, xUnit=xUnit, yUnit=yUnit) + + for fieldName, fieldType in LUT_FIELD_TYPES.items(): + fieldValue = fieldType(row[fieldName]) + profile.setAttribute(fieldName, fieldValue) + + if FIELD_NAME not in fieldnames: + profile.setName('{}:{}'.format(bn, i+1)) + else: + profile.setName(row[FIELD_NAME]) + profiles.append(profile) + + speclib.addProfiles(profiles) + + + s = "" + s = "" + assert speclib.commitChanges() + return speclib + + @staticmethod + def write(speclib:SpectralLibrary, path:str, progressDialog:QProgressDialog, delimiter:str=';'): + """ + Writes the SpectralLibrary to path and returns a list of written files that can be used to open the spectral library with readFrom + """ + assert isinstance(speclib, SpectralLibrary) + basePath, ext = os.path.splitext(path) + s = "" + + writtenFiles = [] + fieldNames = [n for n in speclib.fields().names() if n not in [FIELD_VALUES, FIELD_FID]] + groups = speclib.groupBySpectralProperties() + for i, grp in enumerate(groups.keys()): + # in-memory text buffer + stream = io.StringIO() + xValues, xUnit, yUnit = grp + profiles = groups[grp] + if i == 0: + path = basePath + ext + else: + path = basePath + '{}{}'.format(i+1, ext) + + + headerNames = fieldNames + [str(v) for v in xValues] + W = pycsv.DictWriter(stream, fieldnames=headerNames, dialect=EcoSISCSVDialect()) + W.writeheader() + + for profile in profiles: + assert isinstance(profile, SpectralProfile) + + rowDict = dict() + for n in fieldNames: + v = profile.attribute(n) + if v in [None, QVariant(None), '']: + v = 'NA' + rowDict[n] = v + + yValues = profile.yValues() + for i, xValue in enumerate(xValues): + rowDict[str(xValue)] = yValues[i] + W.writerow(rowDict) + + stream.write('\n') + lines = stream.getvalue().replace('\r', '') + + with open(path, 'w', encoding='utf-8') as f: + f.write(lines) + writtenFiles.append(path) + + return writtenFiles + + @staticmethod + def score(uri:str)->int: + """ + Returns a score value for the give uri. E.g. 0 for unlikely/unknown, 20 for yes, probalby thats the file format the reader can read. + + :param uri: str + :return: int + """ + return 0 + + @staticmethod + def addImportActions(spectralLibrary: SpectralLibrary, menu: QMenu) -> list: + + def read(speclib: SpectralLibrary): + + path, filter = QFileDialog.getOpenFileName(caption='EcoSIS CSV File', + filter='All type (*.*);;Text files (*.txt);; CSV (*.csv)') + if os.path.isfile(path): + + sl = EcoSISSpectralLibraryIO.readFrom(path) + if isinstance(sl, SpectralLibrary): + speclib.startEditing() + speclib.beginEditCommand('Add EcoSIS profiles from {}'.format(path)) + speclib.addSpeclib(sl, True) + speclib.endEditCommand() + speclib.commitChanges() + + m = menu.addAction('EcoSIS') + m.setToolTip('Adds profiles from an EcoSIS csv text file.') + m.triggered.connect(lambda *args, sl=spectralLibrary: read(sl)) + + + @staticmethod + def addExportActions(spectralLibrary:SpectralLibrary, menu:QMenu) -> list: + + def write(speclib: SpectralLibrary): + + path, filter = QFileDialog.getSaveFileName(caption='Write to EcoSIS CSV File', + filter='EcoSIS CSV (*.csv);;Text files (*.txt)') + if isinstance(path, str) and len(path) > 0: + sl = EcoSISSpectralLibraryIO.write(spectralLibrary, path) + + m = menu.addAction('EcoSIS CSV') + m.triggered.connect(lambda *args, sl=spectralLibrary: write(sl)) \ No newline at end of file diff --git a/eotimeseriesviewer/externals/qps/speclib/ecosys.py b/eotimeseriesviewer/externals/qps/speclib/ecosys.py deleted file mode 100644 index 7db403bf1327ff201df5b3f1759ea936cc96e74f..0000000000000000000000000000000000000000 --- a/eotimeseriesviewer/externals/qps/speclib/ecosys.py +++ /dev/null @@ -1,45 +0,0 @@ - -import os, sys, re, pathlib, json -import csv as pycsv -from .spectrallibraries import * - - -class EcoSYSSpectralLibraryIO(object): - """ - I/O Interface for the EcoSYS spectral library format. - See https://ecosis.org for details. - """ - @staticmethod - def canRead(path): - """ - Returns true if it can read the source defined by path - :param path: source uri - :return: True, if source is readable. - """ - return False - - @staticmethod - def readFrom(path): - """ - Returns the SpectralLibrary read from "path" - :param path: source of SpectralLibrary - :return: SpectralLibrary - """ - return None - - @staticmethod - def write(speclib, path): - """Writes the SpectralLibrary to path and returns a list of written files that can be used to open the spectral library with readFrom""" - assert isinstance(speclib, SpectralLibrary) - return [] - - @staticmethod - def score(uri:str)->int: - """ - Returns a score value for the give uri. E.g. 0 for unlikely/unknown, 20 for yes, probalby thats the file format the reader can read. - - :param uri: str - :return: int - """ - return 0 - diff --git a/eotimeseriesviewer/externals/qps/speclib/envi.py b/eotimeseriesviewer/externals/qps/speclib/envi.py index 7778802a3e9c147590189a37217804a23eedc799..6db4c96fffaba78646c0050e970206731c300d37 100644 --- a/eotimeseriesviewer/externals/qps/speclib/envi.py +++ b/eotimeseriesviewer/externals/qps/speclib/envi.py @@ -56,6 +56,7 @@ LUT_GDT_NAME = {gdal.GDT_Byte:'Byte', gdal.GDT_CFloat32:'Float32', gdal.GDT_CFloat64:'Float64'} +FILTER_SLI = 'ENVI Spectral Library (*.sli)' CSV_PROFILE_NAME_COLUMN_NAMES = ['spectra names', 'name'] CSV_GEOMETRY_COLUMN = 'wkt' @@ -200,12 +201,13 @@ def readCSVMetadata(pathESL): for i, row in enumerate(reader): METADATA_LINES.append(tuple(row.values())) - #set emtpy value to None + # set an emtpy value to None def stripped(value:str): if value is None: return None value = value.strip() return None if len(value) == 0 else value + METADATA_LINES = [tuple([stripped(v) for v in row]) for row in METADATA_LINES] @@ -213,30 +215,40 @@ def readCSVMetadata(pathESL): QGSFIELD_PYTHON_TYPES = [] QGSFIELDS = QgsFields() for i, fieldName in enumerate(fieldNames): - fieldValues = [row[i] for row in METADATA_LINES if row[i] is not None] - fieldTypes = [findTypeFromString(v) for v in fieldValues] - if len(fieldTypes) == 0: - fieldTypes = [str] + refValue = None + for lineValues in METADATA_LINES: + + if lineValues[i] not in ['', None, 'NA']: + refValue = lineValues[i] + break + if refValue is None: + refValue = '' + fieldType = findTypeFromString(refValue) - if str in fieldTypes: - t = str + if fieldType is str: a, b = QVariant.String, 'varchar' - elif float in fieldTypes: - t = float + elif fieldType is float: a, b = QVariant.Double, 'double' - elif int in fieldTypes: - t = int + elif fieldType is int: a, b = QVariant.Int, 'int' else: raise NotImplementedError() - QGSFIELD_PYTHON_TYPES.append(t) + + QGSFIELD_PYTHON_TYPES.append(fieldType) QGSFIELDS.append(QgsField(fieldName, a, b)) + # convert metadata string values to basic python type def typeOrNone(value:str, t:type): return value if value is None else t(value) - METADATA_LINES = [tuple(typeOrNone(v, QGSFIELD_PYTHON_TYPES[i]) for i, v in enumerate(line)) for line in METADATA_LINES] + + for i in range(len(METADATA_LINES)): + line = METADATA_LINES[i] + lineTuple = tuple(typeOrNone(cellValue, cellType) for cellValue, cellType in zip(line, QGSFIELD_PYTHON_TYPES)) + METADATA_LINES[i] = lineTuple + + #METADATA_LINES = [tuple(typeOrNone(v, QGSFIELD_PYTHON_TYPES[i]) for i, v in enumerate(line)) for line in METADATA_LINES] return (METADATA_LINES, QGSFIELDS) @@ -262,7 +274,10 @@ def writeCSVMetadata(pathCSV:str, profiles:list): for p in profiles: assert isinstance(p, SpectralProfile) d = {} - d['spectra names'] = p.name().replace(',', '-') + spectrumName = p.name() + if spectrumName is None: + spectrumName = '' + d['spectra names'] = spectrumName.replace(',', '-') d[CSV_GEOMETRY_COLUMN] = p.geometry().asWkt() for name in fieldNames: v = p.attribute(name) @@ -282,6 +297,42 @@ class EnviSpectralLibraryIO(AbstractSpectralLibraryIO): REQUIRED_TAGS = ['byte order', 'data type', 'header offset', 'lines', 'samples', 'bands'] SINGLE_VALUE_TAGS = REQUIRED_TAGS + ['description', 'wavelength', 'wavelength units'] + + @staticmethod + def addImportActions(spectralLibrary: SpectralLibrary, menu: QMenu) -> list: + + def read(speclib: SpectralLibrary): + + path, filter = QFileDialog.getOpenFileName(caption='ENVI Spectral Library', + filter='All types (*.*);;Spectral Library files (*.sli)') + if os.path.isfile(path): + + sl = EnviSpectralLibraryIO.readFrom(path) + if isinstance(sl, SpectralLibrary): + speclib.startEditing() + speclib.beginEditCommand('Add ENVI Spectral Library from {}'.format(path)) + speclib.addSpeclib(sl, True) + speclib.endEditCommand() + speclib.commitChanges() + + m = menu.addAction('ENVI') + m.triggered.connect(lambda *args, sl=spectralLibrary: read(sl)) + + + + @staticmethod + def addExportActions(spectralLibrary:SpectralLibrary, menu:QMenu) -> list: + + def write(speclib: SpectralLibrary): + + path, filter = QFileDialog.getSaveFileName(caption='Write ENVI Spectral Library ', + filter=FILTER_SLI) + if isinstance(path, str) and len(path) > 0: + EnviSpectralLibraryIO.write(spectralLibrary, path) + + m = menu.addAction('ENVI') + m.triggered.connect(lambda *args, sl=spectralLibrary: write(sl)) + @staticmethod def canRead(pathESL): """ @@ -306,7 +357,7 @@ class EnviSpectralLibraryIO(AbstractSpectralLibraryIO): return 0 @staticmethod - def readFrom(path): + def readFrom(path, progressDialog:QProgressDialog=None): """ Reads an ENVI Spectral Library (ESL). :param path: path to ENVI Spectral Library @@ -339,13 +390,10 @@ class EnviSpectralLibraryIO(AbstractSpectralLibraryIO): spectraNames = md.get('spectra names', ['Spectrum {}'.format(i+1) for i in range(nSpectra)]) # thanks to Ann for https://bitbucket.org/jakimowb/qgispluginsupport/issues/3/speclib-envypy - try: - gbl = np.where(np.asarray(md.get('bbl'), dtype=int))[0] - if xValues is not None: - xValues = np.asarray(xValues, dtype=float)[gbl] - except TypeError: - gbl = range(nbands) + bbl = md.get('bbl', None) + if bbl: + bbl = np.asarray(bbl, dtype=np.byte).tolist() speclibFields = createStandardFields() @@ -356,6 +404,9 @@ class EnviSpectralLibraryIO(AbstractSpectralLibraryIO): except Exception as ex: print(str(ex), file=sys.stderr) + PROFILE2CSVLine = {} + + if CSV_METADATA is not None: CSV_DATA, CSV_FIELDS = CSV_METADATA @@ -365,6 +416,7 @@ class EnviSpectralLibraryIO(AbstractSpectralLibraryIO): speclibFields.append(csvField) CSVLine2ESLProfile = {} + # look if we can match a CSV column with names to profile names for profileNameColumnName in CSV_PROFILE_NAME_COLUMN_NAMES: if profileNameColumnName in CSV_FIELDS.names(): @@ -372,67 +424,69 @@ class EnviSpectralLibraryIO(AbstractSpectralLibraryIO): for r, row in enumerate(CSV_DATA): nameCSV = row[c] if nameCSV in spectraNames: - CSVLine2ESLProfile[r] = spectraNames.index(nameCSV) + iProfile = spectraNames.index(nameCSV) + CSVLine2ESLProfile[r] = iProfile + PROFILE2CSVLine[iProfile] = r break - #backup: match csv line with profile index - if len(CSVLine2ESLProfile) == 0: + # backup: match csv line with profile index + if len(PROFILE2CSVLine) == 0: indices = range(min(nSpectra, len(CSV_DATA))) - CSVLine2ESLProfile = dict(zip(indices, indices)) + CSVLine2ESLProfile = PROFILE2CSVLine = dict(zip(indices, indices)) + + SLIB = SpectralLibrary() + assert SLIB.startEditing() + SLIB.addMissingFields(speclibFields) + + if CSV_METADATA is not None: + sliceCSV = [] + sliceAttr = [] + for slibField in SLIB.fields(): + fieldName = slibField.name() + + iSLIB = SLIB.fields().lookupField(fieldName) + iCSV = CSV_FIELDS.lookupField(fieldName) + + if iCSV >= 0: + sliceCSV.append(iCSV) + sliceAttr.append(iSLIB) + + iCSVGeometry = CSV_FIELDS.lookupField(CSV_GEOMETRY_COLUMN) profiles = [] + import datetime + t0 = datetime.datetime.now() for i in range(nSpectra): - p = SpectralProfile(fields=speclibFields) - p.setValues(x=xValues, y=data[i, gbl].tolist(), xUnit=xUnit, yUnit=yUnit) - name = spectraNames[i] - p.setName(name) - profiles.append(p) + f = QgsFeature(SLIB.fields()) + valueDict = {'x': xValues, 'y': data[i, :].tolist(), 'xUnit': xUnit, 'yUnit': yUnit, 'bbl': bbl} + if CSV_METADATA is not None: + j = PROFILE2CSVLine.get(i) + if j: + csvLine = CSV_DATA[j] + attr = f.attributes() + for iCSV, iAttr in zip(sliceCSV, sliceAttr): + attr[iAttr] = csvLine[iCSV] + f.setAttributes(attr) - if CSV_METADATA is not None: - #find which column index from CSV table matches which QgsFeature attribute index - for fieldIndex, csvField in enumerate(CSV_FIELDS): - assert isinstance(csvField, QgsField) - fieldName = csvField.name() - #is this a geometry field? - - if fieldName == CSV_GEOMETRY_COLUMN: - # copy CSV values to profile geometry attribute - for iCSV, iProfile in CSVLine2ESLProfile.items(): - value = CSV_DATA[iCSV][fieldIndex] - if isinstance(value, str): - g = QgsGeometry.fromWkt(value) + if iCSVGeometry > 0: + wkt = csvLine[iCSVGeometry] + if isinstance(wkt, str): + g = QgsGeometry.fromWkt(wkt) if g.wkbType() == QgsWkbTypes.Point: - profile = profiles[iProfile] - assert isinstance(profile, SpectralProfile) - profile.setGeometry(g) - else: - #set normal value fields - if fieldName in CSV_PROFILE_NAME_COLUMN_NAMES: - #map CSV field "spectrum names" or "name" to speclib "name" columns - aSpeclib = speclibFields.lookupField(FIELD_NAME) - else: - aSpeclib = speclibFields.lookupField(fieldName) - - if aSpeclib < 0: - s = "" - aCSV = CSV_FIELDS.lookupField(fieldName) - - #copy CSV values to profile attribute - for iCSV, iProfile in CSVLine2ESLProfile.items(): - profile = profiles[iProfile] - assert isinstance(profile, SpectralProfile) - value = CSV_DATA[iCSV][aCSV] - if value not in EMPTY_VALUES: - assert profile.setAttribute(aSpeclib, value) - s = "" + f.setGeometry(g) + f.setAttribute(FIELD_VALUES, encodeProfileValueDict(valueDict)) + f.setAttribute(FIELD_NAME, spectraNames[i]) + + profiles.append(f) + + #print('Creation: {}'.format(datetime.datetime.now() - t0)) + t0 = datetime.datetime.now() + SLIB.addFeatures(profiles) + #print('Adding: {}'.format(datetime.datetime.now() - t0)) - SLIB = SpectralLibrary() - SLIB.startEditing() - SLIB.addMissingFields(speclibFields) - SLIB.addProfiles(profiles) assert SLIB.commitChanges() assert SLIB.featureCount() == nSpectra @@ -441,7 +495,7 @@ class EnviSpectralLibraryIO(AbstractSpectralLibraryIO): return SLIB @staticmethod - def write(speclib:SpectralLibrary, path:str): + def write(speclib:SpectralLibrary, path:str, progressDialog:QProgressDialog): """ Writes a SpectralLibrary as ENVI Spectral Library (ESL). See http://www.harrisgeospatial.com/docs/ENVIHeaderFiles.html for ESL definition @@ -461,7 +515,9 @@ class EnviSpectralLibraryIO(AbstractSpectralLibraryIO): dn = os.path.dirname(path) bn, ext = os.path.splitext(os.path.basename(path)) - assert ext in ['.sli', '.esl'], "Path needs extension .sli or .esl: {}".format(path) + if not re.search(r'\.(sli|esl)', ext, re.I): + ext = '.sli' + writtenFiles = [] diff --git a/eotimeseriesviewer/externals/qps/speclib/plotting.py b/eotimeseriesviewer/externals/qps/speclib/plotting.py index 0aac0f1077f6393ffcf814282fcc1fa2c40240b0..a05e0da5c37b68f170633da7dbbe57572726a438 100644 --- a/eotimeseriesviewer/externals/qps/speclib/plotting.py +++ b/eotimeseriesviewer/externals/qps/speclib/plotting.py @@ -27,7 +27,7 @@ * * *************************************************************************** """ -import sys, re, os, collections +import sys, re, os, collections, typing, random, copy from qgis.PyQt.QtGui import * from qgis.PyQt.QtCore import * from qgis.PyQt.QtWidgets import * @@ -37,11 +37,19 @@ from ..externals.pyqtgraph.functions import mkPen from ..externals import pyqtgraph as pg from ..externals.pyqtgraph.graphicsItems.PlotDataItem import PlotDataItem from ..utils import METRIC_EXPONENTS, convertMetricUnit -from .spectrallibraries import SpectralProfile, SpectralLibrary, MIMEDATA_SPECLIB_LINK, FIELD_VALUES +from .spectrallibraries import SpectralProfile, SpectralLibrary, MIMEDATA_SPECLIB_LINK, FIELD_VALUES, X_UNITS, speclibSettings, SpectralLibrarySettingsKey, loadSpeclibUI +from ..plotstyling.plotstyling import PlotStyleWidget, PlotStyle BAND_INDEX = 'Band Index' +def defaultCurvePlotStyle()->PlotStyle: + ps = PlotStyle() + ps.setLineColor('white') + ps.markerSymbol = None + ps.linePen.setStyle(Qt.SolidLine) + return ps + class SpectralXAxis(pg.AxisItem): def __init__(self, *args, **kwds): @@ -113,9 +121,216 @@ class SpectralLibraryPlotItem(pg.PlotItem): # self.legend.addItem(item, name=name) +class SpectralLibraryPlotColorScheme(object): + + @staticmethod + def default(): + """ + Returns the default plotStyle scheme. + :return: + :rtype: SpectralLibraryPlotColorScheme + """ + return SpectralLibraryPlotColorScheme.dark() + + @staticmethod + def fromUserSettings(): + """ + Returns the SpectralLibraryPlotColorScheme last saved in then library settings + :return: + :rtype: + """ + settings = speclibSettings() + + scheme = SpectralLibraryPlotColorScheme.default() + + if SpectralLibrarySettingsKey.DEFAULT_PROFILE_STYLE.name in settings.allKeys(): + scheme.ps = PlotStyle.fromJSON(settings.value(SpectralLibrarySettingsKey.DEFAULT_PROFILE_STYLE.name)) + if SpectralLibrarySettingsKey.CURRENT_PROFILE_STYLE.name in settings.allKeys(): + scheme.cs = PlotStyle.fromJSON(settings.value(SpectralLibrarySettingsKey.CURRENT_PROFILE_STYLE.name)) + + scheme.bg = settings.value(SpectralLibrarySettingsKey.BACKGROUND_COLOR.name, scheme.bg) + scheme.fg = settings.value(SpectralLibrarySettingsKey.FOREGROUND_COLOR.name, scheme.fg) + scheme.ic = settings.value(SpectralLibrarySettingsKey.INFO_COLOR.name, scheme.ic) + scheme.useRendererColors = settings.value(SpectralLibrarySettingsKey.USE_VECTOR_RENDER_COLORS.name, scheme.useRendererColors) in ['True', 'true', True] + + return scheme + + + @staticmethod + def dark(): + ps = defaultCurvePlotStyle() + ps.setLineColor('white') + + cs = defaultCurvePlotStyle() + cs.setLineColor('green') + + return SpectralLibraryPlotColorScheme( + name='Dark', fg=QColor('white'), bg=QColor('black'), + ic=QColor('yellow'), ps=ps, cs=cs, userRendererColors=False) + + @staticmethod + def bright(): + ps = defaultCurvePlotStyle() + ps.setLineColor('black') + + cs = defaultCurvePlotStyle() + cs.setLineColor('green') + + return SpectralLibraryPlotColorScheme( + name='Bright', fg=QColor('black'), bg=QColor('white'), + ic=QColor('red'), ps=ps, cs=cs, userRendererColors=False) + + def __init__(self, name:str='color_scheme', + fg:QColor=QColor('white'), + bg:QColor=QColor('black'), + ps:PlotStyle=PlotStyle(), + cs:PlotStyle=PlotStyle(), + ic:QColor=QColor('yellow'), + userRendererColors:bool=True): + + self.name:str + self.name = name + + self.fg : QColor + self.fg = fg + + self.bg : QColor + self.bg = bg + + self.ps:PlotStyle + self.ps = ps + self.cs: PlotStyle + self.cs = cs + self.ic:QColor + self.ic = ic + self.useRendererColors:bool + self.useRendererColors = userRendererColors + + def clone(self): + return copy.deepcopy(self) + + def __copy__(self): + return copy.copy(self) + + def saveToUserSettings(self): + """ + Saves this plotStyle scheme to the user Qt user settings + :return: + :rtype: + """ + settings = speclibSettings() + + settings.setValue(SpectralLibrarySettingsKey.DEFAULT_PROFILE_STYLE.name, self.ps.json()) + settings.setValue(SpectralLibrarySettingsKey.CURRENT_PROFILE_STYLE.name, self.cs.json()) + settings.setValue(SpectralLibrarySettingsKey.BACKGROUND_COLOR.name, self.bg) + settings.setValue(SpectralLibrarySettingsKey.FOREGROUND_COLOR.name, self.fg) + settings.setValue(SpectralLibrarySettingsKey.INFO_COLOR.name, self.ic) + settings.setValue(SpectralLibrarySettingsKey.USE_VECTOR_RENDER_COLORS.name, self.useRendererColors) + + + def __eq__(self, other): + if not isinstance(other, SpectralLibraryPlotColorScheme): + return False + else: + + return self.bg == other.bg and \ + self.fg == other.fg and \ + self.ic == other.ic and \ + self.ps == other.ps and \ + self.cs == other.cs and \ + self.useRendererColors == other.useRendererColors + + + + + +class SpectralLibraryPlotColorSchemeWidget(QWidget, loadSpeclibUI('spectrallibraryplotcolorschemewidget.ui')): + + sigColorSchemeChanged = pyqtSignal(SpectralLibraryPlotColorScheme) + + def __init__(self, *args, **kwds): + super(SpectralLibraryPlotColorSchemeWidget, self).__init__(*args, **kwds) + self.setupUi(self) + + self.mBlocked = False + + self.mLastColorScheme: SpectralLibraryPlotColorScheme + self.mLastColorScheme = None + + + self.btnColorBackground.colorChanged.connect(self.onColorSchemeChanged) + self.btnColorForeground.colorChanged.connect(self.onColorSchemeChanged) + self.btnColorInfo.colorChanged.connect(self.onColorSchemeChanged) + self.cbUseRendererColors.clicked.connect(self.onCbUseRendererColorsClicked) + + self.wDefaultProfileStyle.setPreviewVisible(False) + self.wDefaultProfileStyle.cbIsVisible.setVisible(False) + self.wDefaultProfileStyle.sigPlotStyleChanged.connect(self.onColorSchemeChanged) + self.wDefaultProfileStyle.setMinimumSize(self.wDefaultProfileStyle.sizeHint()) + self.btnReset.setDisabled(True) + self.btnReset.clicked.connect(lambda : self.setColorScheme(self.mLastColorScheme)) + self.btnColorSchemeBright.clicked.connect(lambda : self.setColorScheme(SpectralLibraryPlotColorScheme.bright())) + self.btnColorSchemeDark.clicked.connect(lambda: self.setColorScheme(SpectralLibraryPlotColorScheme.dark())) + + + + #l.setMargin(1) + #l.setSpacing(2) + #frame.setMinimumSize(l.sizeHint()) + + def onCbUseRendererColorsClicked(self, checked:bool): + self.onColorSchemeChanged() + w = self.wDefaultProfileStyle + assert isinstance(w, PlotStyleWidget) + w.btnLinePenColor.setDisabled(checked) + + + def setColorScheme(self, colorScheme:SpectralLibraryPlotColorScheme): + assert isinstance(colorScheme, SpectralLibraryPlotColorScheme) + + if self.mLastColorScheme is None: + self.mLastColorScheme = colorScheme + self.btnReset.setEnabled(True) + + + changed = colorScheme != self.colorScheme() + + self.mBlocked = True + + self.btnColorBackground.setColor(colorScheme.bg) + self.btnColorForeground.setColor(colorScheme.fg) + self.btnColorInfo.setColor(colorScheme.ic) + self.wDefaultProfileStyle.setPlotStyle(colorScheme.ps) + '' + self.cbUseRendererColors.setChecked(colorScheme.useRendererColors) + + self.mBlocked = False + if changed: + self.sigColorSchemeChanged.emit(self.colorScheme()) + + def onColorSchemeChanged(self, *args): + if not self.mBlocked: + self.sigColorSchemeChanged.emit(self.colorScheme()) + + self.btnReset.setEnabled(isinstance(self.mLastColorScheme, SpectralLibraryPlotColorScheme) and + self.colorScheme() != self.mLastColorScheme) + + def colorScheme(self)->SpectralLibraryPlotColorScheme: + cs = SpectralLibraryPlotColorScheme() + cs.bg = self.btnColorBackground.color() + cs.fg = self.btnColorForeground.color() + cs.ic = self.btnColorInfo.color() + cs.ps = self.wDefaultProfileStyle.plotStyle() + cs.cs = self.mLastColorScheme.cs + cs.useRendererColors = self.cbUseRendererColors.isChecked() + return cs + + + + class SpectralProfilePlotDataItem(PlotDataItem): """ - A pyqtgraph.PlotDataItem to plot SpectralProfiles + A pyqtgraph.PlotDataItem to plot a SpectralProfile """ def __init__(self, spectralProfile: SpectralProfile): @@ -125,15 +340,17 @@ class SpectralProfilePlotDataItem(PlotDataItem): self.mXValueConversionFunction = lambda v, *args: v self.mYValueConversionFunction = lambda v, *args: v - self.mDefaultLineWidth = self.pen().width() - self.mDefaultLineColor = None + self.mDefaultStyle = PlotStyle() + + self.mProfile:SpectralProfile self.mProfile = None self.mInitialDataX = None self.mInitialDataY = None self.mInitialUnitX = None self.mInitialUnitY = None + self.initProfile(spectralProfile) self.applyMapFunctions() @@ -245,40 +462,60 @@ class SpectralProfilePlotDataItem(PlotDataItem): """ if b: - self.setLineWidth(self.mDefaultLineWidth + 3) + self.setLineWidth(self.mDefaultStyle.lineWidth() + 3) + self.setZValue(999999) # self.setColor(Qgis.DEFAULT_HIGHLIGHT_COLOR) else: - self.setLineWidth(self.mDefaultLineWidth) - # self.setColor(self.mDefaultLineColor) + self.setLineWidth(self.mDefaultStyle.lineWidth()) + self.setZValue(1) + + def setPlotStyle(self, plotStyle:PlotStyle, updateItem=True): + """ + Applies a PlotStyle to this SpectralProfilePlotDataItem + :param plotStyle: + :type plotStyle: + :param updateItem: set True (default) to apply changes immediately. + :type updateItem: bool + """ + assert isinstance(plotStyle, PlotStyle) + plotStyle.apply(self, updateItem=updateItem) + + def plotStyle(self)->PlotStyle: + """ + Returns the SpectralProfilePlotDataItems' PlotStyle + :return: PlotStyle + :rtype: PlotStyle + """ + return PlotStyle.fromPlotDataItem(self) def setColor(self, color: QColor): """ - Sets the profile color + Sets the profile plotStyle :param color: QColor """ if not isinstance(color, QColor): color = QColor(color) - self.setPen(color) + style = self.profileStyle() + style.linePen.setColor(color) + self.setProfileStyle(style) - if not isinstance(self.mDefaultLineColor, QColor): - self.mDefaultLineColor = color - - def pen(self): + def pen(self) -> QPen: """ Returns the QPen of the profile :return: QPen """ return mkPen(self.opts['pen']) - def color(self): + def color(self) -> QColor: """ - Returns the profile color + Returns the profile plotStyle :return: QColor """ return self.pen().color() - def setLineWidth(self, width): + + def setLineWidth(self, width:int): """ Set the profile width in px :param width: int @@ -288,6 +525,14 @@ class SpectralProfilePlotDataItem(PlotDataItem): pen.setWidth(width) self.setPen(pen) + def lineWidth(self)->int: + """ + Returns the line width + :return: line width in pixel + :rtype: int + """ + return self.pen().width() + def mouseClickEvent(self, ev): if ev.button() == Qt.RightButton: if self.raiseContextMenu(ev): @@ -334,6 +579,172 @@ class SpectralProfilePlotDataItem(PlotDataItem): return self.menu +class SpectralViewBox(pg.ViewBox): + """ + Subclass of ViewBox + """ + sigXUnitChanged = pyqtSignal(str) + sigColorSchemeChanged = pyqtSignal(SpectralLibraryPlotColorScheme) + sigMaxNumberOfProfilesChanged = pyqtSignal(int) + + def __init__(self, parent=None): + """ + Constructor of the CustomViewBox + """ + super(SpectralViewBox, self).__init__(parent) + # self.menu = None # Override pyqtgraph ViewBoxMenu + # self.menu = self.getMenu() # Create the menu + # self.menu = None + + xAction = [a for a in self.menu.actions() if a.text() == 'X Axis'][0] + yAction = [a for a in self.menu.actions() if a.text() == 'Y Axis'][0] + + + self.cbXAxisUnits = QComboBox(parent) + + + # profile settings + menuProfiles = self.menu.addMenu('Profiles') + l = QGridLayout() + self.sbMaxProfiles = QSpinBox(parent) + self.sbMaxProfiles.setToolTip('Maximum number of profiles to plot.') + self.sbMaxProfiles.setRange(0, 256) + self.sbMaxProfiles.setValue(64) + self.sbMaxProfiles.valueChanged[int].connect(self.sigMaxNumberOfProfilesChanged) + l.addWidget(QLabel('Max.'), 0, 0) + l.addWidget(self.sbMaxProfiles, 0, 1) + frame = QFrame() + frame.setLayout(l) + wa = QWidgetAction(menuProfiles) + wa.setDefaultWidget(frame) + menuProfiles.addAction(wa) + + # color settings + menuColors = self.menu.addMenu('Colors') + wa = QWidgetAction(menuColors) + self.wColorScheme = SpectralLibraryPlotColorSchemeWidget(parent) + self.wColorScheme.sigColorSchemeChanged.connect(self.sigColorSchemeChanged.emit) + wa.setDefaultWidget(self.wColorScheme) + menuColors.addAction(wa) + + menuXAxis = self.menu.addMenu('X Axis') + + # define the widget to set X-Axis options + frame = QFrame() + l = QGridLayout() + frame.setLayout(l) + self.rbXManualRange = QRadioButton('Manual') + self.rbXAutoRange = QRadioButton('Auto') + self.rbXAutoRange.setChecked(True) + + l.addWidget(self.rbXManualRange, 0, 0) + l.addWidget(self.rbXAutoRange, 1, 0) + + self.mCBXAxisUnit = QComboBox() + + # Order of X units: + # 1. long names + # 2. short si names + # 3. within these groups: by exponent + items = sorted(METRIC_EXPONENTS.items(), key=lambda item: item[1]) + fullNames = [] + siNames = [] + for item in items: + if len(item[0]) > 5: + # make centimeters to Centimeters + item = (item[0].title(), item[1]) + fullNames.append(item) + else: + siNames.append(item) + + self.mCBXAxisUnit.addItem(BAND_INDEX, userData='') + for item in fullNames + siNames: + name, exponent = item + self.mCBXAxisUnit.addItem(name, userData=name) + self.mCBXAxisUnit.setCurrentIndex(0) + + self.mCBXAxisUnit.currentIndexChanged.connect( + lambda: self.sigXUnitChanged.emit(self.mCBXAxisUnit.currentText())) + + l.addWidget(QLabel('Unit'), 2, 0) + l.addWidget(self.mCBXAxisUnit, 2, 1) + + self.mXAxisUnit = 'index' + + l.setMargin(1) + l.setSpacing(1) + frame.setMinimumSize(l.sizeHint()) + wa = QWidgetAction(menuXAxis) + wa.setDefaultWidget(frame) + menuXAxis.addAction(wa) + + self.menu.insertMenu(xAction, menuXAxis) + self.menu.removeAction(xAction) + + self.mActionShowCrosshair = self.menu.addAction('Show Crosshair') + self.mActionShowCrosshair.setCheckable(True) + self.mActionShowCrosshair.setChecked(True) + self.mActionShowCursorValues = self.menu.addAction('Show Mouse values') + self.mActionShowCursorValues.setCheckable(True) + self.mActionShowCursorValues.setChecked(True) + + def raiseContextMenu(self, ev): + self.mLastColorScheme = self.colorScheme() + super(SpectralViewBox, self).raiseContextMenu(ev) + + def setColorScheme(self, colorScheme:SpectralLibraryPlotColorScheme): + assert isinstance(colorScheme, SpectralLibraryPlotColorScheme) + self.wColorScheme.setColorScheme(colorScheme) + + def colorScheme(self)->SpectralLibraryPlotColorScheme: + """ + Returns the color scheme + """ + return self.wColorScheme.colorScheme() + + def setXAxisUnit(self, unit: str): + """ + Sets the X axis unit. + :param unit: str, metric unit like `nm` or `Nanometers`. + """ + i = self.mCBXAxisUnit.findText(unit) + if i == -1: + i = 0 + if i != self.mCBXAxisUnit.currentIndex(): + self.mCBXAxisUnit.setCurrentIndex(i) + + def xAxisUnit(self) -> str: + """ + Returns unit of X-Axis values + :return: str + """ + return self.mCBXAxisUnit.currentText() + + def addItems(self, pdis: list, ignoreBounds=False): + """ + Add multiple QGraphicsItem to this view. The view will include this item when determining how to set its range + automatically unless *ignoreBounds* is True. + """ + for i, item in enumerate(pdis): + if item.zValue() < self.zValue(): + item.setZValue(self.zValue() + 1 + i) + + scene = self.scene() + if scene is not None and scene is not item.scene(): + for item in pdis: + scene.addItem(item) ## Necessary due to Qt bug: https://bugreports.qt-project.org/browse/QTBUG-18616 + item.setParentItem(self.childGroup) + if not ignoreBounds: + self.addedItems.extend(pdis) + # self.updateAutoRange() + + def updateCurrentPosition(self, x, y): + self.mCurrentPosition = (x, y) + pass + + + + class SpectralLibraryPlotWidget(pg.PlotWidget): """ A widget to PlotWidget SpectralProfiles @@ -342,14 +753,22 @@ class SpectralLibraryPlotWidget(pg.PlotWidget): def __init__(self, parent=None): super(SpectralLibraryPlotWidget, self).__init__(parent) + self.mMaxProfiles = 64 + self.mViewBox = SpectralViewBox() plotItem = SpectralLibraryPlotItem( axisItems={'bottom': SpectralXAxis(orientation='bottom')} , viewBox=self.mViewBox ) + self.mViewBox.sbMaxProfiles.setValue(self.mMaxProfiles) + self.mViewBox.sigColorSchemeChanged.connect(self.setColorScheme) + self.mViewBox.sigMaxNumberOfProfilesChanged.connect(self.setMaxProfiles) + self.mDualView = None + self.centralWidget.setParent(None) self.centralWidget = None self.setCentralWidget(plotItem) + self.plotItem:SpectralLibraryPlotItem self.plotItem = plotItem for m in ['addItem', 'removeItem', 'autoRange', 'clear', 'setXRange', 'setYRange', 'setRange', 'setAspectLocked', 'setMouseEnabled', @@ -360,19 +779,20 @@ class SpectralLibraryPlotWidget(pg.PlotWidget): self.plotItem.sigRangeChanged.connect(self.viewRangeChanged) pi = self.getPlotItem() - assert isinstance(pi, pg.PlotItem) and pi == plotItem and pi == self.plotItem + assert isinstance(pi, SpectralLibraryPlotItem) and pi == plotItem and pi == self.plotItem #pi.disableAutoRange() + self.mSpeclib:SpectralLibrary self.mSpeclib = None self.mSpeclibSignalConnections = [] - self.mUpdatesBlocked = False self.mXUnitInitialized = False self.mXUnit = BAND_INDEX self.mYUnit = None + # describe function to convert length units from unit a to unit b self.mLUT_UnitConversions = dict() returnNone = lambda v, *args: None @@ -398,15 +818,11 @@ class SpectralLibraryPlotWidget(pg.PlotWidget): self.mPlotDataItems = dict() self.setAntialiasing(True) self.setAcceptDrops(True) - self.mMaxProfiles = 256 - self.mPlotOverlayItems = [] - self.mAddedFeatureList = [] + self.mPlotOverlayItems = [] - self.mUpdateTimer = QTimer() - #self.mUpdateTimer.timeout.connect(self._addedFeatureCheck) - self.mUpdateTimer.setInterval(2000) - self.mUpdateTimer.start(2000) + self.mLastFIDs = [] + self.mNeedsPlotUpdate = False self.mCrosshairLineV = pg.InfiniteLine(angle=90, movable=False) self.mCrosshairLineH = pg.InfiniteLine(angle=0, movable=False) @@ -424,9 +840,6 @@ class SpectralLibraryPlotWidget(pg.PlotWidget): pi.addItem(self.mCrosshairLineV, ignoreBounds=True) pi.addItem(self.mCrosshairLineH, ignoreBounds=True) - self.setBackground(QColor('black')) - self.mInfoColor = None - self.setInfoColor(QColor('yellow')) self.proxy2D = pg.SignalProxy(self.scene().sigMouseMoved, rateLimit=100, slot=self.onMouseMoved2D) @@ -436,91 +849,149 @@ class SpectralLibraryPlotWidget(pg.PlotWidget): self.setYLabel('Y (Spectral Value)') self.mViewBox.sigXUnitChanged.connect(self.updateXUnit) + self.mSPECIFIC_PROFILE_STYLES = dict() + self.mDefaultColorScheme:SpectralLibraryPlotColorScheme + self.mDefaultColorScheme = SpectralLibraryPlotColorScheme.default() + self.mColorScheme:SpectralLibraryPlotColorScheme + self.mColorScheme = SpectralLibraryPlotColorScheme.fromUserSettings() + self.setColorScheme(self.mColorScheme) - def setInfoColor(self, color: QColor): - if isinstance(color, QColor): - self.mInfoColor = color - self.mInfoLabelCursor.setColor(self.mInfoColor) - self.mCrosshairLineH.pen.setColor(self.mInfoColor) - self.mCrosshairLineV.pen.setColor(self.mInfoColor) + self.mUpdateTimer = QTimer() + self.mUpdateTimeIsBlocked = False + self.mUpdateTimerInterval = 500 + self.mUpdateTimer.timeout.connect(self.onPlotUpdateTimeOut) - def infoColor(self) -> QColor: - """ - Returns the color of overlotted information - :return: QColor - """ - return QColor(self.mInfoColor) - def foregroundInfoColor(self) -> QColor: - return self.plotItem.axes['bottom']['item'].pen().color() + def viewBox(self)->SpectralViewBox: + return self.mViewBox - def setForegroundInfoColor(self, color: QColor): - if isinstance(color, QColor): - for axis in self.plotItem.axes.values(): + def setColorScheme(self, colorScheme:SpectralLibraryPlotColorScheme): + """Sets and applies the SpectralProfilePlotColorScheme""" + assert isinstance(colorScheme, SpectralLibraryPlotColorScheme) + old = self.colorScheme() + self.mColorScheme = colorScheme + + # set Background color + if old.bg != colorScheme.bg: + self.setBackground(colorScheme.bg) + # set Foreground color + if old.fg != colorScheme.fg: + for axis in self.plotItem.axes.values(): ai = axis['item'] if isinstance(ai, pg.AxisItem): - ai.setPen(QColor(color)) + ai.setPen(colorScheme.fg) - def updatesBlocked(self) -> bool: - """ - Returns whether speclib updates are shown in the plot widget or not - :return: bool - """ - return self.mUpdatesBlocked + # set info color + self.mInfoLabelCursor.setColor(colorScheme.ic) + self.mCrosshairLineH.pen.setColor(colorScheme.ic) + self.mCrosshairLineV.pen.setColor(colorScheme.ic) + + # set Info Color + if old.ic != colorScheme.ic: + self.mInfoLabelCursor.setColor(colorScheme.ic) + self.mCrosshairLineH.pen.setColor(colorScheme.ic) + self.mCrosshairLineV.pen.setColor(colorScheme.ic) - def blockUpdates(self, b: bool) -> bool: + # update profile colors + if old.ps != colorScheme.ps or old.cs != colorScheme.cs or old.useRendererColors != colorScheme.useRendererColors: + self.updateProfileStyles() + + # update viewbox context menu and + self.viewBox().setColorScheme(self.mColorScheme) + self.mColorScheme.saveToUserSettings() + + + + + def colorScheme(self)->SpectralLibraryPlotColorScheme: """ - Disconnect the plot widget from spectral library signales. - :param b: + Returns the used SpectralProfileColorScheme :return: + :rtype: """ - b0 = self.updatesBlocked() - self.mUpdatesBlocked = b + return self.mColorScheme.clone() - if b == True: - self.disconnectSpeclibSignals() - self.blockSignals(True) - #self.updatePlot() - self.setViewportUpdateMode(QGraphicsView.NoViewportUpdate) - else: - self.setViewportUpdateMode(QGraphicsView.MinimalViewportUpdate) - self.blockSignals(False) - self.connectSpeclibSignals() + def onPlotUpdateTimeOut(self, *args): + + + + try: + + if not self.mUpdateTimeIsBlocked: + self.mUpdateTimeIsBlocked = True + self.updateSpectralProfilePlotItems() + self.mUpdateTimeIsBlocked = False + else: + s ="" + except RuntimeError as ex: + print(ex, file=sys.stderr) + self.mUpdateTimeIsBlocked = False + finally: + + # adapt changes to update interval + if self.mUpdateTimer.interval() != self.mUpdateTimerInterval: + self.mUpdateTimer.setInterval(self.mUpdateTimerInterval) + self.mUpdateTimer.start() + + def leaveEvent(self, ev): + super(SpectralLibraryPlotWidget, self).leaveEvent(ev) + + # disable mouse-position related plot items + self.mCrosshairLineH.setVisible(False) + self.mCrosshairLineV.setVisible(False) + self.mInfoLabelCursor.setVisible(False) - return b0 + def enterEvent(self, ev): + super(SpectralLibraryPlotWidget, self).enterEvent(ev) + + + def foregroundInfoColor(self) -> QColor: + return self.plotItem.axes['bottom']['item'].pen().color() + + def hoverEvent(self, ev): + if ev.enter: + self.mouseHovering = True + if ev.exit: + self.mouseHovering = False def onMouseMoved2D(self, evt): pos = evt[0] ## using signal proxy turns original arguments into a tuple plotItem = self.getPlotItem() - if plotItem.sceneBoundingRect().contains(pos): + assert isinstance(plotItem, SpectralLibraryPlotItem) + if plotItem.sceneBoundingRect().contains(pos) and self.underMouse(): vb = plotItem.vb assert isinstance(vb, SpectralViewBox) mousePoint = vb.mapSceneToView(pos) x = mousePoint.x() - if x >= 0: + y = mousePoint.y() + + # todo: add infos about plot data below mouse, e.g. profile band number + rect = QRectF(pos.x() - 2, pos.y() - 2, 5, 5) + itemsBelow = plotItem.scene().items(rect) + if SpectralProfilePlotDataItem in itemsBelow: + s = "" - # todo: add infos about plot data below mouse, e.g. profile band number - rect = QRectF(pos.x() - 2, pos.y() - 2, 5, 5) - itemsBelow = plotItem.scene().items(rect) - if SpectralProfilePlotDataItem in itemsBelow: - s = "" - y = mousePoint.y() - vb.updateCurrentPosition(x, y) - self.mInfoLabelCursor.setText('x:{:0.5f}\ny:{:0.5f}'.format(x, y)) + vb.updateCurrentPosition(x, y) + self.mInfoLabelCursor.setText('x:{:0.5f}\ny:{:0.5f}'.format(x, y)) - s = self.size() - pos = QPointF(s.width(), 0) - self.mInfoLabelCursor.setVisible(vb.mActionShowCursorValues.isChecked()) - self.mInfoLabelCursor.setPos(pos) + s = self.size() + pos = QPointF(s.width(), 0) + self.mInfoLabelCursor.setVisible(vb.mActionShowCursorValues.isChecked()) + self.mInfoLabelCursor.setPos(pos) + + b = vb.mActionShowCrosshair.isChecked() + self.mCrosshairLineH.setVisible(b) + self.mCrosshairLineV.setVisible(b) + self.mCrosshairLineV.setPos(mousePoint.x()) + self.mCrosshairLineH.setPos(mousePoint.y()) + else: + self.mCrosshairLineH.setVisible(False) + self.mCrosshairLineV.setVisible(False) + self.mInfoLabelCursor.setVisible(False) - b = vb.mActionShowCrosshair.isChecked() - self.mCrosshairLineH.setVisible(b) - self.mCrosshairLineV.setVisible(b) - self.mCrosshairLineV.setPos(mousePoint.x()) - self.mCrosshairLineH.setPos(mousePoint.y()) def setPlotOverlayItems(self, items): """ @@ -558,20 +1029,82 @@ class SpectralLibraryPlotWidget(pg.PlotWidget): self.setXUnit(xUnit) self.mXUnitInitialized = True - def _spectralProfilePDIs(self) -> list: + def spectralProfilePlotDataItems(self) -> typing.List[SpectralProfilePlotDataItem]: + """ + Returns all SpectralProfilePlotDataItems + """ return [i for i in self.getPlotItem().items if isinstance(i, SpectralProfilePlotDataItem)] - def _removeSpectralProfilePDIs(self, pdis: list): + def _removeSpectralProfilePDIs(self, fidsToRemove: typing.List[int]): + """ - pi = self.getPlotItem() - assert isinstance(pi, pg.PlotItem) + :param fidsToRemove: feature ids to remove + :type fidsToRemove: + :return: + :rtype: + """ - for pdi in pdis: + def disconnect(sig, slot): + while True: + try: + r = sig.disconnect(slot) + s = "" + except: + break + + plotItem = self.getPlotItem() + assert isinstance(plotItem, pg.PlotItem) + pdisToRemove = [pdi for pdi in self.spectralProfilePlotDataItems() if pdi.id() in fidsToRemove] + for pdi in pdisToRemove: assert isinstance(pdi, SpectralProfilePlotDataItem) - pi.removeItem(pdi) - assert pdi not in pi.dataItems + pdi.setClickable(False) + disconnect(pdi, self.onProfileClicked) + plotItem.removeItem(pdi) + #QtGui.QGraphicsScene.items(self, *args) + assert pdi not in plotItem.dataItems if pdi.id() in self.mPlotDataItems.keys(): - self.mPlotDataItems.pop(pdi.id()) + self.mPlotDataItems.pop(pdi.id(), None) + self.mSPECIFIC_PROFILE_STYLES.pop(pdi.id(), None) + self.scene().update() + s = "" + + + def resetProfileStyles(self): + """ + Resets the profile colors + """ + self.mSPECIFIC_PROFILE_STYLES.clear() + + def setProfileStyle(self, style:PlotStyle, fids:typing.List[int]): + """ + Sets the specific profile style + :param style: + :type style: + :param fids: + :type fids: + :return: + :rtype: + """ + if isinstance(fids, list): + if isinstance(style, PlotStyle): + for fid in fids: + self.mSPECIFIC_PROFILE_STYLES[fid] = style + elif style is None: + # delete existing + for fid in fids: + self.mSPECIFIC_PROFILE_STYLES.pop(fid, None) + self.updateProfileStyles(fids) + + def setMaxProfiles(self, n:int): + """ + Sets the maximum number of profiles. + :param n: maximum number of profiles visualized + :type n: int + """ + assert n > 0 + + self.mMaxProfiles = n + self.mViewBox.sbMaxProfiles.setValue(self.mMaxProfiles) def setSpeclib(self, speclib: SpectralLibrary): """ @@ -579,26 +1112,44 @@ class SpectralLibraryPlotWidget(pg.PlotWidget): :param speclib: SpectralLibrary """ assert isinstance(speclib, SpectralLibrary) - - self._removeSpectralProfilePDIs(self._spectralProfilePDIs()) + self.mUpdateTimer.stop() + # remove old spectra + if isinstance(self.speclib(), SpectralLibrary): + self._removeSpectralProfilePDIs(self.speclib().allFeatureIds()) self.mSpeclib = speclib self.connectSpeclibSignals() - self.onProfilesAdded(self.speclib().allFeatureIds()) + self.mUpdateTimer.start(self.mUpdateTimerInterval) + + + def setDualView(self, dualView:QgsDualView): + assert isinstance(dualView, QgsDualView) + speclib = dualView.masterModel().layer() + assert isinstance(speclib, SpectralLibrary) + self.mDualView = dualView + if self.speclib() != speclib: + self.setSpeclib(speclib) - self.updatePlot() + + def dualView(self)->QgsDualView: + return self.mDualView def connectSpeclibSignals(self): + """ + + """ if isinstance(self.mSpeclib, SpectralLibrary): - self.mSpeclib.featureAdded.connect(self.onProfilesAdded) - self.mSpeclib.featuresDeleted.connect(self.onProfilesRemoved) + #self.mSpeclib.featureAdded.connect(self.onProfilesAdded) + #self.mSpeclib.featuresDeleted.connect(self.onProfilesRemoved) self.mSpeclib.selectionChanged.connect(self.onSelectionChanged) self.mSpeclib.committedAttributeValuesChanges.connect(self.onCommittedAttributeValuesChanges) self.mSpeclib.rendererChanged.connect(self.onRendererChanged) def disconnectSpeclibSignals(self): - + """ + Savely disconnects all signals from the linked SpectralLibrary + """ if isinstance(self.mSpeclib, SpectralLibrary): def disconnect(sig, slot): while True: @@ -613,21 +1164,12 @@ class SpectralLibraryPlotWidget(pg.PlotWidget): disconnect(self.mSpeclib.committedAttributeValuesChanges, self.onCommittedAttributeValuesChanges) disconnect(self.mSpeclib.rendererChanged, self.onRendererChanged) - - def _addedFeatureCheck(self): - if len(self.mAddedFeatureList) > 0 and not self.mUpdatesBlocked: - fids = self.mAddedFeatureList[:] - self.mAddedFeatureList.clear() - self.onProfilesAdded(fids) - - def _addAddedFeatureList(self, fid): - self.mAddedFeatureList.append(fid) - def speclib(self) -> SpectralLibrary: """ Returns the SpectralLibrary this widget is linked to. :return: SpectralLibrary """ + return self.mSpeclib def onCommittedAttributeValuesChanges(self, layerID, featureMap): """ @@ -655,42 +1197,39 @@ class SpectralLibraryPlotWidget(pg.PlotWidget): @pyqtSlot() def onRendererChanged(self): + """ + Updates all SpectralProfilePlotDataItems + """ + self.updateProfileStyles() - profiles = self.mSpeclib.profiles(fids=self.mPlotDataItems.keys()) - self.updateProfileStyles(list(profiles)) - - - def onSelectionChanged(self, selected, deselected): - for pdi in self.plotItem.items: - if isinstance(pdi, SpectralProfilePlotDataItem): - w = pdi.pen().width() - if pdi.id() in selected: - pdi.setSelected(True) - elif pdi.id() in deselected: - pdi.setSelected(False) - s = "" + def onSelectionChanged(self, selected, deselected, clearAndSelect): + self.updateSpectralProfilePlotItems() + for pdi in self.allSpectralProfilePlotDataItems(): + if pdi.id() in selected: + pdi.setSelected(True) + elif pdi.id() in deselected: + pdi.setSelected(False) + """ def syncLibrary(self): s = "" # see https://groups.google.com/forum/#!topic/pyqtgraph/kz4U6dswEKg # speed problems in case of too many line items - profiles = list(self.speclib().profiles()) + #profiles = list(self.speclib().profiles()) self.disableAutoRange() self.blockSignals(True) - self.plotItem self.setViewportUpdateMode(QGraphicsView.NoViewportUpdate) - self.updateProfileStyles(profiles) #take too much time - self.updatePlot() #take much more time + + self.updateSpectralProfilePlotItems() + self.updateProfileStyles() self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) self.blockSignals(False) self.enableAutoRange() self.viewport().update() + """ - - s = "" - def unitConversionFunction(self, unitSrc, unitDst): """ Returns a function to convert a numeric value from unitSrc to unitDst. @@ -738,16 +1277,16 @@ class SpectralLibraryPlotWidget(pg.PlotWidget): """ return self.mXUnit - def allPlotDataItems(self) -> list: + def allPlotDataItems(self) -> typing.List[PlotDataItem]: """ Returns all PlotDataItems (not only SpectralProfilePlotDataItems) :return: [list-of-PlotDataItems] """ return list(self.mPlotDataItems.values()) + self.mPlotOverlayItems - def allSpectralProfilePlotDataItems(self): + def allSpectralProfilePlotDataItems(self)->typing.List[SpectralProfilePlotDataItem]: """ - Returns alls SpectralProfilePlotDataItem, including those used as temporary overlays. + Returns all SpectralProfilePlotDataItem, including those used as temporary overlays. :return: [list-of-SpectralProfilePlotDataItem] """ return [pdi for pdi in self.allPlotDataItems() if isinstance(pdi, SpectralProfilePlotDataItem)] @@ -772,36 +1311,51 @@ class SpectralLibraryPlotWidget(pg.PlotWidget): s = "" - def updatePlot(self): - i = 0 + def updateSpectralProfilePlotItems(self): + """ + + """ pi = self.getPlotItem() assert isinstance(pi, SpectralLibraryPlotItem) - existing = list(self.mPlotDataItems.values()) + toBeVisualized = self.profileIDsToVisualize() + visualized = self.plottedProfileIDs() + toBeRemoved = [fid for fid in visualized if fid not in toBeVisualized] + toBeAdded = [fid for fid in toBeVisualized if fid not in visualized] - to_add = [i for i in existing if isinstance(i, SpectralProfilePlotDataItem) and i not in pi.dataItems] - to_remove = [pdi for pdi in pi.dataItems if - isinstance(pdi, SpectralProfilePlotDataItem) and pdi not in existing] + if len(toBeRemoved) > 0: + self._removeSpectralProfilePDIs(toBeRemoved) - self._removeSpectralProfilePDIs(to_remove) - pi.addItems(to_add) + if len(toBeAdded) > 0: + addedPDIs = [] + addedProfiles = self.speclib().profiles(toBeAdded) + + defaultPlotStyle = self.mColorScheme.ps + for profile in addedProfiles: + assert isinstance(profile, SpectralProfile) + pdi = SpectralProfilePlotDataItem(profile) + defaultPlotStyle.apply(pdi) + pdi.setClickable(True) + pdi.setVisible(True) + pdi.sigClicked.connect(self.onProfileClicked) + self.mPlotDataItems[profile.id()] = pdi + addedPDIs.append(pdi) + pi.addItems(addedPDIs) + self.updateProfileStyles(toBeAdded) + s = "" + + if len(toBeAdded) > 0 or len(toBeRemoved) > 0: + pi.update() - pi.update() - s = "" def resetSpectralProfiles(self): for pdi in self.spectralProfilePlotDataItems(): assert isinstance(pdi, SpectralProfilePlotDataItem) pdi.resetSpectralProfile() - def spectralProfilePlotDataItems(self) -> list: - """ - Returns all available SpectralProfilePlotDataItems - """ - return [i for i in self.mPlotDataItems.values() if isinstance(i, SpectralProfilePlotDataItem)] - def spectralProfilePlotDataItem(self, fid) -> SpectralProfilePlotDataItem: + def spectralProfilePlotDataItem(self, fid:typing.Union[int, QgsFeature, SpectralProfile]) -> SpectralProfilePlotDataItem: """ Returns the SpectralProfilePlotDataItem related to SpectralProfile fid :param fid: int | QgsFeature | SpectralProfile @@ -811,63 +1365,67 @@ class SpectralLibraryPlotWidget(pg.PlotWidget): fid = fid.id() return self.mPlotDataItems.get(fid) - def updateProfileStyles(self, listOfProfiles: list): + def updateProfileStyles(self, fids: typing.List[SpectralProfile]=None): """ Updates the styles for a set of SpectralProfilePlotDataItems :param listOfProfiles: [list-of-SpectralProfiles] """ + if not isinstance(self.speclib(), SpectralLibrary): + return + + cs = self.mColorScheme + xUnit = None renderContext = QgsRenderContext() - renderContext.setExtent(self.mSpeclib.extent()) + renderContext.setExtent(self.speclib().extent()) renderer = self.speclib().renderer().clone() - colors = [] - from .spectrallibraries import DEFAULT_PROFILE_COLOR - if isinstance(renderer, QgsNullSymbolRenderer): - for i in range(len(listOfProfiles)): - colors.append(DEFAULT_PROFILE_COLOR) - else: - renderer.startRender(renderContext, self.mSpeclib.fields()) - for profile in listOfProfiles: - symbol = renderer.symbolForFeature(profile, renderContext) - if not isinstance(symbol, QgsSymbol): - symbol = renderer.sourceSymbol() - assert isinstance(symbol, QgsSymbol) - color = QColor('white') + pdis = self.spectralProfilePlotDataItems() - if isinstance(symbol, (QgsMarkerSymbol, QgsLineSymbol, QgsFillSymbol)): - color = symbol.color() - colors.append(color) - renderer.stopRender(renderContext) + # update requested FIDs only + if isinstance(fids, list): + pdis = [pdi for pdi in pdis if pdi.id() in fids] - s = "" - for profile, color in zip(listOfProfiles, colors): - assert isinstance(profile, SpectralProfile) + # update X Axis unit + if not self.mXUnitInitialized: + for pdi in pdis: + profile = pdi.spectralProfile() + if profile.xUnit() in X_UNITS: + self.setXUnit(profile.xUnit()) + break - if not self.mXUnitInitialized and isinstance(profile.xUnit(), str): - self.setXUnit(profile.xUnit()) + # update line colors + if not cs.useRendererColors or isinstance(renderer, QgsNullSymbolRenderer): + for pdi in pdis: + style = self.mSPECIFIC_PROFILE_STYLES.get(pdi.id(), cs.ps) + style.apply(pdi) + else: + renderer.startRender(renderContext, self.speclib().fields()) + for pdi in pdis: + profile = pdi.spectralProfile() - id = profile.id() + style = self.mSPECIFIC_PROFILE_STYLES.get(pdi.id(), None) - pdi = self.mPlotDataItems.get(id) - if not isinstance(pdi, PlotDataItem): - pdi = SpectralProfilePlotDataItem(profile) - pdi.setClickable(True) - pdi.sigClicked.connect(self.onProfileClicked) - self.mPlotDataItems[profile.id()] = pdi - pdi.setColor(color) + if not isinstance(style, PlotStyle): + style = cs.ps.clone() + symbol = renderer.symbolForFeature(profile, renderContext) + if not isinstance(symbol, QgsSymbol): + symbol = renderer.sourceSymbol() + assert isinstance(symbol, QgsSymbol) + if isinstance(symbol, (QgsMarkerSymbol, QgsLineSymbol, QgsFillSymbol)): + style.setLineColor(symbol.color()) + style.apply(pdi) + renderer.stopRender(renderContext) if isinstance(xUnit, str): self.setXUnit(xUnit) self.mXUnitInitialized = True def onProfileClicked(self, pdi): - if self.mUpdatesBlocked: - return if isinstance(pdi, SpectralProfilePlotDataItem) and pdi in self.mPlotDataItems.values(): modifiers = QApplication.keyboardModifiers() @@ -907,234 +1465,79 @@ class SpectralLibraryPlotWidget(pg.PlotWidget): def xLabel(self) -> str: return self.getPlotItem().getAxis('bottom').label - def speclib(self) -> SpectralLibrary: - """ - :return: SpectralLibrary - """ - return self.mSpeclib - - def onProfilesAdded(self, fids): - if not isinstance(fids, list): - fids = [fids] - if len(fids) == 0: - return - profiles = self.speclib().profiles(fids=fids) - self.updateProfileStyles(list(profiles)) - self.updatePlot() - s = "" - - def onProfilesRemoved(self, fids): - if len(fids) == 0: - return - - pi = self.getPlotItem() - assert isinstance(pi, pg.PlotItem) - to_remove = [pdi for pdi in self._spectralProfilePDIs() if pdi.id() in fids] - self._removeSpectralProfilePDIs(to_remove) - - def dragEnterEvent(self, event): - assert isinstance(event, QDragEnterEvent) - if MIMEDATA_SPECLIB_LINK in event.mimeData().formats(): - event.accept() - - def dragMoveEvent(self, event): - if MIMEDATA_SPECLIB_LINK in event.mimeData().formats(): - event.accept() - - -class SpectralViewBox(pg.ViewBox): - """ - Subclass of ViewBox - """ - sigXUnitChanged = pyqtSignal(str) - - def __init__(self, parent=None): - """ - Constructor of the CustomViewBox - """ - super(SpectralViewBox, self).__init__(parent) - # self.menu = None # Override pyqtgraph ViewBoxMenu - # self.menu = self.getMenu() # Create the menu - # self.menu = None - - xAction = [a for a in self.menu.actions() if a.text() == 'X Axis'][0] - yAction = [a for a in self.menu.actions() if a.text() == 'Y Axis'][0] - - menuColors = self.menu.addMenu('Colors') - frame = QFrame() - l = QGridLayout() - frame.setLayout(l) - - self.btnColorBackground = QgsColorButton(parent) - self.btnColorForeground = QgsColorButton(parent) - self.btnColorInfo = QgsColorButton(parent) - self.btnColorSelected = QgsColorButton(parent) - self.cbXAxisUnits = QComboBox(parent) - - def onBackgroundColorChanged(color: QColor): - w = self._viewWidget() - if isinstance(w, SpectralLibraryPlotWidget): - w.setBackground(QColor(color)) - - def onForegroundColorChanged(color: QColor): - w = self._viewWidget() - if isinstance(w, SpectralLibraryPlotWidget): - w.setForegroundInfoColor(color) - # w.setForegroundBrush(color) - - def onInfoColorChanged(color: QColor): - w = self._viewWidget() - if isinstance(w, SpectralLibraryPlotWidget): - w.setInfoColor(color) - s = "" - - self.btnColorBackground.colorChanged.connect(onBackgroundColorChanged) - self.btnColorForeground.colorChanged.connect(onForegroundColorChanged) - self.btnColorInfo.colorChanged.connect(onInfoColorChanged) - - l.addWidget(QLabel('Background'), 0, 0) - l.addWidget(self.btnColorBackground, 0, 1) - - l.addWidget(QLabel('Foreground'), 1, 0) - l.addWidget(self.btnColorForeground, 1, 1) - - l.addWidget(QLabel('Crosshair info'), 2, 0) - l.addWidget(self.btnColorInfo, 2, 1) - - l.setMargin(1) - l.setSpacing(1) - frame.setMinimumSize(l.sizeHint()) - wa = QWidgetAction(menuColors) - wa.setDefaultWidget(frame) - menuColors.addAction(wa) - - menuXAxis = self.menu.addMenu('X Axis') - - # define the widget to set X-Axis options - frame = QFrame() - l = QGridLayout() - frame.setLayout(l) - self.rbXManualRange = QRadioButton('Manual') - - self.rbXAutoRange = QRadioButton('Auto') - self.rbXAutoRange.setChecked(True) - - l.addWidget(self.rbXManualRange, 0, 0) - l.addWidget(self.rbXAutoRange, 1, 0) - - self.mCBXAxisUnit = QComboBox() - - # Order of X units: - # 1. long names - # 2. short si names - # 3. within these groups: by exponent - items = sorted(METRIC_EXPONENTS.items(), key=lambda item: item[1]) - fullNames = [] - siNames = [] - for item in items: - if len(item[0]) > 5: - # make centimeters to Centimeters - item = (item[0].title(), item[1]) - fullNames.append(item) - else: - siNames.append(item) - - self.mCBXAxisUnit.addItem(BAND_INDEX, userData='') - for item in fullNames + siNames: - name, exponent = item - self.mCBXAxisUnit.addItem(name, userData=name) - self.mCBXAxisUnit.setCurrentIndex(0) - - self.mCBXAxisUnit.currentIndexChanged.connect( - lambda: self.sigXUnitChanged.emit(self.mCBXAxisUnit.currentText())) - - l.addWidget(QLabel('Unit'), 2, 0) - l.addWidget(self.mCBXAxisUnit, 2, 1) - - self.mXAxisUnit = 'index' - - l.setMargin(1) - l.setSpacing(1) - frame.setMinimumSize(l.sizeHint()) - wa = QWidgetAction(menuXAxis) - wa.setDefaultWidget(frame) - menuXAxis.addAction(wa) - - self.menu.insertMenu(xAction, menuXAxis) - self.menu.removeAction(xAction) - - self.mActionShowCrosshair = self.menu.addAction('Show Crosshair') - self.mActionShowCrosshair.setCheckable(True) - self.mActionShowCrosshair.setChecked(True) - self.mActionShowCursorValues = self.menu.addAction('Show Mouse values') - self.mActionShowCursorValues.setCheckable(True) - self.mActionShowCursorValues.setChecked(True) - - def setXAxisUnit(self, unit: str): + def plottedProfileCount(self)->int: """ - Sets the X axis unit. - :param unit: str, metric unit like `nm` or `Nanometers`. + Returns the number of plotted profiles + :return: int + :rtype: int """ - i = self.mCBXAxisUnit.findText(unit) - if i == -1: - i = 0 - if i != self.mCBXAxisUnit.currentIndex(): - self.mCBXAxisUnit.setCurrentIndex(i) + return len(self.allSpectralProfilePlotDataItems()) - def xAxisUnit(self) -> str: + def plottedProfileIDs(self)->typing.List[int]: """ - Returns unit of X-Axis values - :return: str + Returns the feature IDs of all visualized SpectralProfiles. """ - return self.mCBXAxisUnit.currentText() + return [pdi.id() for pdi in self.allSpectralProfilePlotDataItems()] - def addItems(self, pdis: list, ignoreBounds=False): + def profileIDsToVisualize(self)->typing.List[int]: """ - Add multiple QGraphicsItem to this view. The view will include this item when determining how to set its range - automatically unless *ignoreBounds* is True. + Returns the list of profile/feature ids to be visualized. + The maximum number is determined by self.mMaxProfiles + Order of returned fids is equal to its importance. 1st postion = most important """ - for i, item in enumerate(pdis): - if item.zValue() < self.zValue(): - item.setZValue(self.zValue() + 1 + i) + nMax = len(self.speclib()) - scene = self.scene() - if scene is not None and scene is not item.scene(): - for item in pdis: - scene.addItem(item) ## Necessary due to Qt bug: https://bugreports.qt-project.org/browse/QTBUG-18616 - item.setParentItem(self.childGroup) - if not ignoreBounds: - self.addedItems.extend(pdis) - # self.updateAutoRange() - - def updateContextMenu(self): + allIDs = self.speclib().allFeatureIds() + if nMax <= self.mMaxProfiles: + return allIDs - w = self._viewWidget() - if isinstance(w, SpectralLibraryPlotWidget): - # get background color - bg = w.backgroundBrush().color() - self.btnColorBackground.setColor(bg) + # Order: + # 1. visible in table + # 2. selected + # 3. others - # get foreground color - self.btnColorForeground.setColor(w.foregroundInfoColor()) - # get info color - self.btnColorInfo.setColor(w.infoColor()) - def raiseContextMenu(self, ev): + vis = self.plottedProfileIDs() + dualView = self.dualView() - pt = self.mapDeviceToView(ev.pos()) + priority1 = [] + if isinstance(dualView, QgsDualView): + tv = dualView.tableView() + assert isinstance(tv, QTableView) + rowHeight = tv.rowViewportPosition(1) - tv.rowViewportPosition(0) + for y in range(0, tv.viewport().height(), rowHeight): + idx = dualView.tableView().indexAt(QPoint(0, y)) + if idx.isValid(): + fid = tv.model().data(idx, role=Qt.UserRole) + priority1.append(fid) + priority2 = self.dualView().masterModel().layer().selectedFeatureIds() + priority3 = dualView.filteredFeatures() + else: + priority2 = self.speclib().selectedFeatureIds() + priority3 = self.speclib().allFeatureIds() - xRange, yRange = self.viewRange() + #featurePool = priority3 + featurePool = priority1+priority2 + toVisualize = sorted(featurePool, key=lambda fid : (fid not in priority1, fid not in priority2, fid)) - menu = self.getMenu(ev) + if len(toVisualize) >= self.mMaxProfiles: + return sorted(toVisualize[0:self.mMaxProfiles]) + else: + toVisualize = sorted(toVisualize) + nMissing = min(self.mMaxProfiles - len(toVisualize), len(priority3)) + if nMissing > 0: + toVisualize += sorted(priority3[0:nMissing]) + return toVisualize - self.scene().addParentContextMenus(self, menu, ev) - self.updateContextMenu() - menu.exec_(ev.screenPos().toPoint()) - def updateCurrentPosition(self, x, y): - self.mCurrentPosition = (x, y) - pass + def dragEnterEvent(self, event): + assert isinstance(event, QDragEnterEvent) + if MIMEDATA_SPECLIB_LINK in event.mimeData().formats(): + event.accept() + def dragMoveEvent(self, event): + if MIMEDATA_SPECLIB_LINK in event.mimeData().formats(): + event.accept() diff --git a/eotimeseriesviewer/externals/qps/speclib/specchio.py b/eotimeseriesviewer/externals/qps/speclib/specchio.py new file mode 100644 index 0000000000000000000000000000000000000000..d2e82266fecafd2a480a71d30765cd17c2fa35bd --- /dev/null +++ b/eotimeseriesviewer/externals/qps/speclib/specchio.py @@ -0,0 +1,227 @@ + +import os, sys, re, pathlib, json, collections +import csv as pycsv +from .spectrallibraries import * + + +class SPECCHIOSpectralLibraryIO(AbstractSpectralLibraryIO): + """ + I/O Interface for the SPECCHIO spectral library . + See https://ecosis.org for details. + """ + @staticmethod + def canRead(path)->bool: + """ + Returns true if it can read the source defined by path + :param path: source uri + :return: True, if source is readable. + """ + + with open(path, 'r', encoding='utf-8') as f: + for line in f: + if re.search(r'^\d+(\.\d+)?.+', line): + return True + + return False + + @staticmethod + def readFrom(path, wlu='nm', delimiter=',', progressDialog:QProgressDialog=None)->SpectralLibrary: + """ + Returns the SpectralLibrary read from "path" + :param path: source of SpectralLibrary + :return: SpectralLibrary + """ + + sl = SpectralLibrary() + sl.startEditing() + bn = os.path.basename(path) + delimiter = ',' + with open(path, 'r', encoding='utf-8') as f: + lines = f.readlines() + DATA = collections.OrderedDict() + readMetaData = True + regNumber = re.compile(r'^\d+(\.\d+)?$') + nProfiles = 0 + for i, line in enumerate(lines): + + assert isinstance(line, str) + line = line.strip() + if len(line) == 0: + continue + + values = line.split(delimiter) + if len(values) < 2: + continue + + try: + mdKey = values.pop(0).strip() + assert isinstance(mdKey, str) + if len(values) == 0: + continue + + t = findTypeFromString(values[0]) + values = [t(v) for v in values if len(v) > 0] + if len(values) > 0: + DATA[mdKey] = values + else: + s = "" + except Exception as ex: + print(ex, file=sys.stderr) + print('Line {}:{}'.format(i+1, line), file=sys.stderr) + + numericValueKeys = [] + metadataKeys = [] + for k in DATA.keys(): + if regNumber.search(k): + numericValueKeys.append(k) + else: + metadataKeys.append(k) + + numericValueKeys = sorted(numericValueKeys) + xValues = [float(v) for v in numericValueKeys] + nProfiles = len(DATA[numericValueKeys[0]]) + + sl.beginEditCommand('Set metadata columns') + for k in metadataKeys: + if k in sl.fieldNames(): + continue + + k2 = k.replace(' ', '_') + qgsField = createQgsField(k, DATA[k][0]) + assert sl.addAttribute(qgsField) + + + sl.endEditCommand() + sl.commitChanges() + sl.startEditing() + profiles = [] + for i in range(nProfiles): + profile = SpectralProfile(fields=sl.fields()) + # add profile name + if FIELD_NAME in metadataKeys: + profile.setName(DATA[FIELD_NAME][i]) + else: + profile.setName('{}:{}'.format(bn, i+1)) + + # add profile values + yValues = [float(DATA[k][i]) for k in numericValueKeys] + profile.setValues(x=xValues, y=yValues, xUnit=wlu) + + # add profile metadata + for k in metadataKeys: + mdValues = DATA[k] + if len(mdValues) > i: + profile.setAttribute(k, mdValues[i]) + + profiles.append(profile) + + sl.addProfiles(profiles, addMissingFields=True) + sl.commitChanges() + return sl + + @staticmethod + def write(speclib:SpectralLibrary, path:str, progressDialog:QProgressDialog=None, delimiter:str=',')->list: + """ + Writes the SpectralLibrary to path and returns a list of written files that can be used to open the spectral library with readFrom(...) + :param speclib: SpectralLibrary + :param path: str, path to library source + :return: [str-list-of-written-files] + """ + """ + Writes the SpectralLibrary to path and returns a list of written files that can be used to open the spectral library with readFrom + """ + assert isinstance(speclib, SpectralLibrary) + basePath, ext = os.path.splitext(path) + s = "" + + writtenFiles = [] + fieldNames = [n for n in speclib.fields().names() if n not in [FIELD_VALUES, FIELD_FID]] + groups = speclib.groupBySpectralProperties() + for i, grp in enumerate(groups.keys()): + # in-memory text buffer + stream = io.StringIO() + xValues, xUnit, yUnit = grp + profiles = groups[grp] + if i == 0: + path = basePath + ext + else: + path = basePath + '{}{}'.format(i + 1, ext) + + # write metadata + for fn in speclib.fields().names(): + assert isinstance(fn, str) + if fn in [FIELD_FID, FIELD_VALUES]: + continue + line = [fn] + for p in profiles: + assert isinstance(p, SpectralProfile) + line.append(str(p.attribute(fn))) + stream.write(delimiter.join(line) + '\n') + # + line = ['wavelength unit'] + for p in profiles: + line.append(str(p.xUnit())) + stream.write(delimiter.join(line) + '\n') + + # write values + for i, xValue in enumerate(xValues): + line = [str(xValue)] + for p in profiles: + assert isinstance(p, SpectralProfile) + yValue = p.values()['y'][i] + line.append(str(yValue)) + stream.write(delimiter.join(line) + '\n') + + lines = stream.getvalue().replace('\r', '') + + with open(path, 'w', encoding='utf-8') as f: + f.write(lines) + writtenFiles.append(path) + + return writtenFiles + + @staticmethod + def score(uri:str)->int: + """ + Returns a score value for the give uri. E.g. 0 for unlikely/unknown, 20 for yes, probalby thats the file format the reader can read. + + :param uri: str + :return: int + """ + return 0 + + + @staticmethod + def addExportActions(spectralLibrary:SpectralLibrary, menu:QMenu) -> list: + + def write(speclib: SpectralLibrary): + + path, filter = QFileDialog.getSaveFileName(caption='Write SPECCHIO CSV Spectral Library ', + filter='Textfile (*.csv)') + if isinstance(path, str) and len(path) > 0: + SPECCHIOSpectralLibraryIO.write(spectralLibrary, path) + + m = menu.addAction('SPECCHIO') + m.setToolTip('Exports the profiles into the SPECCIO text file format.') + m.triggered.connect(lambda *args, sl=spectralLibrary: write(sl)) + + @staticmethod + def addImportActions(spectralLibrary: SpectralLibrary, menu: QMenu) -> list: + + def read(speclib: SpectralLibrary): + + path, filter = QFileDialog.getOpenFileName(caption='Read SPECCHIO CSV File', + filter='All type (*.*);;Text files (*.txt);; CSV (*.csv)') + if os.path.isfile(path): + + sl = SPECCHIOSpectralLibraryIO.readFrom(path) + if isinstance(sl, SpectralLibrary): + speclib.startEditing() + speclib.beginEditCommand('Add profiles from {}'.format(path)) + speclib.addSpeclib(sl, True) + speclib.endEditCommand() + speclib.commitChanges() + + m = menu.addAction('SPECCHIO') + m.setToolTip('Adds profiles stored in an SPECCHIO csv text file.') + m.triggered.connect(lambda *args, sl=spectralLibrary: read(sl)) \ No newline at end of file diff --git a/eotimeseriesviewer/externals/qps/speclib/speclibcsvexportdialog.ui b/eotimeseriesviewer/externals/qps/speclib/speclibcsvexportdialog.ui new file mode 100644 index 0000000000000000000000000000000000000000..efb0b15ee880b3b62e3f1ecc90651c3d5208e05b --- /dev/null +++ b/eotimeseriesviewer/externals/qps/speclib/speclibcsvexportdialog.ui @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Dialog</class> + <widget class="QDialog" name="Dialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="geometry"> + <rect> + <x>30</x> + <y>240</y> + <width>341</width> + <height>32</height> + </rect> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + <widget class="QWidget" name="fileWidget" native="true"> + <property name="geometry"> + <rect> + <x>100</x> + <y>30</y> + <width>221</width> + <height>31</height> + </rect> + </property> + </widget> + <widget class="QLabel" name="label"> + <property name="geometry"> + <rect> + <x>20</x> + <y>40</y> + <width>47</width> + <height>13</height> + </rect> + </property> + <property name="text"> + <string>File Name</string> + </property> + </widget> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>Dialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>Dialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/eotimeseriesviewer/externals/qps/speclib/spectrallibraries.py b/eotimeseriesviewer/externals/qps/speclib/spectrallibraries.py index 70c112fa8ba95d83cb8ed9ed7d7ebbd008977ad9..08ec9c61a1968de6a12cca5129ea29a2789e410d 100644 --- a/eotimeseriesviewer/externals/qps/speclib/spectrallibraries.py +++ b/eotimeseriesviewer/externals/qps/speclib/spectrallibraries.py @@ -1,3 +1,4 @@ + # -*- coding: utf-8 -*- # noinspection PyPep8Naming """ @@ -30,15 +31,17 @@ """ #see http://python-future.org/str_literals.html for str issue discussion -import json, enum, tempfile, pickle, collections +import json, enum, tempfile, pickle, collections, typing, inspect from osgeo import ogr, osr from qgis.utils import iface from qgis.gui import Targets, QgsMapLayerAction +from qgis.core import QgsField, QgsVectorLayer, QgsRasterLayer, QgsVectorFileWriter from ..externals.pyqtgraph import PlotItem from ..externals import pyqtgraph as pg from ..models import Option, OptionListModel from .. utils import * -from ..speclib import speclibSettings +from ..speclib import speclibSettings, SpectralLibrarySettingsKey +from ..plotstyling.plotstyling import PlotStyleWidget, PlotStyle # get to now how we can import this module MODULE_IMPORT_PATH = None @@ -60,15 +63,31 @@ SPECLIB_CRS = QgsCoordinateReferenceSystem('EPSG:{}'.format(SPECLIB_EPSG_CODE)) SPECLIB_CLIPBOARD = weakref.WeakValueDictionary() -COLOR_CURRENT_SPECTRA = QColor('green') -COLOR_SELECTED_SPECTRA = QColor('yellow') -COLOR_BACKGROUND = QColor('black') -CURRENT_PROFILE_COLOR = QColor('green') -DEFAULT_PROFILE_COLOR = QColor('white') + +OGR_EXTENSION2DRIVER = dict() +OGR_EXTENSION2DRIVER[''] = [] #list all drivers without specific extension + +for i in range(ogr.GetDriverCount()): + drv = ogr.GetDriver(i) + extensions = drv.GetMetadataItem(gdal.DMD_EXTENSIONS) + if isinstance(extensions, str): + extensions = extensions.split(',') + for ext in extensions: + if ext not in OGR_EXTENSION2DRIVER.keys(): + OGR_EXTENSION2DRIVER[ext] = [] + OGR_EXTENSION2DRIVER[ext].append(drv.GetName()) + else: + OGR_EXTENSION2DRIVER[''].append(drv.GetName()) +OGR_EXTENSION2DRIVER[None] = OGR_EXTENSION2DRIVER[''] DEBUG = False +class SerializationMode(enum.Enum): + + JSON = 1 + PICKLE = 2 +SERIALIZATION = SerializationMode.PICKLE def log(msg:str): if DEBUG: @@ -97,7 +116,7 @@ PICKLE_PROTOCOL = pickle.HIGHEST_PROTOCOL # DEFAULT_SPECTRUM_STYLE.linePen.setColor(Qt.white) EMPTY_VALUES = [None, NULL, QVariant(), '', 'None'] -EMPTY_PROFILE_VALUES = {'x': None, 'y': None, 'xUnit': None, 'yUnit': None} +EMPTY_PROFILE_VALUES = {'x': None, 'y': None, 'xUnit': None, 'yUnit': None, 'bbl': None} FIELD_VALUES = 'values' FIELD_NAME = 'name' @@ -209,7 +228,7 @@ def findTypeFromString(value:str): continue return t - #every values can be converted into a string + # every values can be converted into a string return str def setComboboxValue(cb: QComboBox, text: str): @@ -272,27 +291,39 @@ def encodeProfileValueDict(d:dict)->str: d2 = {} for k in EMPTY_PROFILE_VALUES.keys(): v = d.get(k) - # save keys with information only if v is not None: d2[k] = v - return json.dumps(d2, sort_keys=True, separators=(',', ':')) + if SERIALIZATION == SerializationMode.JSON: + return json.dumps(d2, sort_keys=True, separators=(',', ':')) + elif SERIALIZATION == SerializationMode.PICKLE: + return QByteArray(pickle.dumps(d2)) + else: + raise NotImplementedError() + -def decodeProfileValueDict(jsonDump:str): +def decodeProfileValueDict(dump): """ - Converts a json string into a SpectralProfile value dictionary - :param jsonDump: str + Converts a json / pickle dump into a SpectralProfile value dictionary + :param dump: str :return: dict """ d = EMPTY_PROFILE_VALUES.copy() - if isinstance(jsonDump, str): - d2 = json.loads(jsonDump) + + if dump not in EMPTY_VALUES: + d2 = None + if SERIALIZATION == SerializationMode.JSON: + d2 = json.loads(dump) + elif SERIALIZATION == SerializationMode.PICKLE: + d2 = pickle.loads(dump) + else: + raise NotImplementedError() d.update(d2) return d def qgsFieldAttributes2List(attributes)->list: - """Returns a list of attibutes with None instead of NULL or QVariatn(""" + """Returns a list of attributes with None instead of NULL or QVariant.NULL""" r = QVariant(None) return [None if v == r else v for v in attributes] @@ -338,11 +369,10 @@ def ogrStandardFields()->list: fields = [ ogr.FieldDefn(FIELD_FID, ogr.OFTInteger), ogr.FieldDefn(FIELD_NAME, ogr.OFTString), - #ogr.FieldDefn('x_unit', ogr.OFTString), - #ogr.FieldDefn('y_unit', ogr.OFTString), ogr.FieldDefn('source', ogr.OFTString), - ogr.FieldDefn(FIELD_VALUES, ogr.OFTString), - #ogr.FieldDefn(FIELD_STYLE, ogr.OFTString), + ogr.FieldDefn(FIELD_VALUES, ogr.OFTString) \ + if SERIALIZATION == SerializationMode.JSON else \ + ogr.FieldDefn(FIELD_VALUES, ogr.OFTBinary), ] return fields @@ -360,6 +390,8 @@ def createStandardFields(): a, b = QVariant.Int, 'int' elif ogrType in [ogr.OFTReal]: a, b = QVariant.Double, 'double' + elif ogrType in [ogr.OFTBinary]: + a, b = QVariant.ByteArray, 'Binary' else: raise NotImplementedError() @@ -391,6 +423,29 @@ class SpectralProfile(QgsFeature): crs = SPECLIB_CRS + @staticmethod + def profileName(basename:str, pxPosition:QPoint=None, geoPosition:QgsPointXY=None, index:int=None): + """ + Unified method to generate the name of a single profile + :param basename: base name + :param pxPosition: optional, pixel position in source image + :param geoPosition: optional, pixel position in geo-coordinates + :param index: index, e.g. n'th-1 profile that was sampled from a data set + :return: name + """ + + + name = basename + + if isinstance(index, int): + name += str(index) + + if isinstance(pxPosition, QPoint): + name += '({}:{})'.format(pxPosition.x(), pxPosition.y()) + elif isinstance(geoPosition, QgsPoint): + name += '({}:{})'.format(geoPosition.x(), geoPosition.y()) + return name.replace(' ', ':') + @staticmethod def fromMapCanvas(mapCanvas, position)->list: """ @@ -434,7 +489,7 @@ class SpectralProfile(QgsFeature): return None profile = SpectralProfile() - profile.setName('{} {}'.format(layer.name(), position)) + profile.setName(SpectralProfile.profileName(layer.name(), geoPosition=position)) profile.setValues(x=wl, y=y, xUnit=wlu) @@ -509,7 +564,7 @@ class SpectralProfile(QgsFeature): wl, wlu = parseWavelength(ds) profile = SpectralProfile(fields=fields) - profile.setName('{} {},{}'.format(baseName, px.x(), px.y())) + profile.setName(SpectralProfile.profileName(baseName, pxPosition=px)) profile.setValues(x=wl, y=y, xUnit=wlu) profile.setCoordinates(geoCoordinate) profile.setSource('{}'.format(ds.GetDescription())) @@ -553,16 +608,20 @@ class SpectralProfile(QgsFeature): - def fieldNames(self): + def fieldNames(self)->typing.List[str]: + """ + Returns all field names + :return: + """ return self.fields().names() def setName(self, name:str): if name != self.name(): - self.setAttribute('name', name) + self.setAttribute(FIELD_NAME, name) #self.sigNameChanged.emit(name) - def name(self): - return self.metadata('name') + def name(self)->str: + return self.metadata(FIELD_NAME) def setSource(self, uri: str): self.setAttribute('source', uri) @@ -580,28 +639,6 @@ class SpectralProfile(QgsFeature): def geoCoordinate(self): return self.geometry() - #def style(self)->PlotStyle: - # """ - # Returns this features's PlotStyle - # :return: PlotStyle - # """ - # styleJson = self.metadata(FIELD_STYLE) - # try: - # style = PlotStyle.fromJSON(styleJson) - # except Exception as ex: - # style = DEFAULT_SPECTRUM_STYLE - # return style - - #def setStyle(self, style:PlotStyle): - # """ - # Sets a Spectral Profiles's plot style - # :param style: PLotStyle - # """ - # if isinstance(style, PlotStyle): - # self.setMetadata(FIELD_STYLE, style.json()) - # else: - # self.setMetadata(FIELD_STYLE, None) - def updateMetadata(self, metaData): if isinstance(metaData, dict): for key, value in metaData.items(): @@ -657,10 +694,18 @@ class SpectralProfile(QgsFeature): v = None return default if v is None else v + def nb(self)->int: + """ + Returns the number of profile bands / profile values + :return: int + :rtype: + """ + return len(self.yValues()) + def values(self)->dict: """ Returns a dictionary with 'x', 'y', 'xUnit' and 'yUnit' values. - :return: {'x':list,'y':list,'xUnit':str,'yUnit':str} + :return: {'x':list,'y':list,'xUnit':str,'yUnit':str, 'bbl':list} """ if self.mValueCache is None: jsonStr = self.attribute(self.fields().indexFromName(FIELD_VALUES)) @@ -671,7 +716,7 @@ class SpectralProfile(QgsFeature): return self.mValueCache - def setValues(self, x=None, y=None, xUnit=None, yUnit=None): + def setValues(self, x=None, y=None, xUnit=None, yUnit=None, bbl=None): d = self.values().copy() @@ -681,16 +726,22 @@ class SpectralProfile(QgsFeature): if isinstance(y, np.ndarray): y = y.tolist() + if isinstance(bbl, np.ndarray): + bbl = bbl.astype(bool).tolist() + if isinstance(x, list): d['x'] = x if isinstance(y, list): d['y'] = y - # ensure x/y are list or None + if isinstance(bbl, list): + d['bbl'] = bbl + + # ensure x/y/bbl are list or None assert d['x'] is None or isinstance(d['x'], list) assert d['y'] is None or isinstance(d['y'], list) - + assert d['bbl'] is None or isinstance(d['bbl'], list) # ensure same length if isinstance(d['x'], list): @@ -699,6 +750,11 @@ class SpectralProfile(QgsFeature): assert len(d['x']) == len(d['y']), \ 'x and y need to have the same number of values ({} != {})'.format(len(d['x']), len(d['y'])) + if isinstance(d['bbl'], list): + assert isinstance(d['y'], list), 'y values need to be specified' + assert len(d['bbl']) == len(d['y']), \ + 'y and bbl need to have the same number of values ({} != {})'.format(len(d['y']), len(d['bbl'])) + if isinstance(xUnit, str): d['xUnit'] = xUnit if isinstance(yUnit, str): @@ -735,6 +791,16 @@ class SpectralProfile(QgsFeature): else: return y + def bbl(self)->list: + """ + Returns the BadBandList. + :return: + :rtype: + """ + bbl = self.values().get('bbl') + if not isinstance(bbl, list): + bbl = np.ones(self.nb(), dtype=np.byte).tolist() + return bbl def setXUnit(self, unit : str): d = self.values() @@ -796,10 +862,10 @@ class SpectralProfile(QgsFeature): pi = SpectralProfilePlotDataItem(self) pi.setClickable(True) pw = pg.plot( title=self.name()) - pw.plotItem().addItem(pi) + pw.getPlotItem().addItem(pi) pi.setColor('green') - pg.QAPP.exec_() + #pg.QAPP.exec_() def __reduce_ex__(self, protocol): @@ -849,6 +915,8 @@ class SpectralProfile(QgsFeature): return False if self.yValues() != other.yValues(): return False + if self.xUnit() != other.xUnit(): + return False else: i2 = names2.index(n) if self.attribute(i1) != other.attribute(i2): @@ -969,7 +1037,9 @@ class SpectralLibrary(QgsVectorLayer): def readFromVector(vector_qgs_layer:QgsVectorLayer=None, raster_qgs_layer:QgsRasterLayer=None, progressDialog:QProgressDialog=None, - all_touched=False): + nameField=None, + all_touched=False, + returnProfileList=False): """ Reads SpectraProfiles from a raster source, based on the locations specified in a vector data set. Opens a Select Polygon Layer dialog to select the correct polygon and returns a Spectral Library with @@ -978,26 +1048,23 @@ class SpectralLibrary(QgsVectorLayer): :param vector_qgs_layer: QgsVectorLayer | str :param raster_qgs_layer: QgsRasterLayer | str :param progressDialog: QProgressDialog (optional) + :param nameField: str | int | QgsField that is used to generate individual profile names. :param all_touched: bool, False (default) = extract only pixel entirely covered with a geometry True = extract all pixels touched by a geometry - :return: Spectral Library + :param returnProfileList: bool, False (default) = return a SpectralLibrary + True = return a [list-of-SpectralProfiles] and skip the SpectralLibrary generations. + :return: Spectral Library | [list-of-profiles] """ - # for debugging only - # layers = [l for l in canvas.layers()] - # vector_qgs_layer = layers[0] - # raster_qgs_layer = layers[1] - # assert isinstance(vector_qgs_layer, QgsVectorLayer) - # assert isinstance(raster_qgs_layer, QgsRasterLayer) - # the SpectralLibrary to be returned spectral_library = SpectralLibrary() - if isinstance(vector_qgs_layer, str): - vector_qgs_layer = QgsVectorLayer(vector_qgs_layer) - - if isinstance(raster_qgs_layer, str): - raster_qgs_layer = QgsRasterLayer(raster_qgs_layer) + # homogenize source file formats + try: + vector_qgs_layer = qgsVectorLayer(vector_qgs_layer) + raster_qgs_layer = qgsRasterLayer(raster_qgs_layer) + except Exception as ex: + print(ex, file=sys.stderr) # get QgsLayers of vector and raster from ..utils import SelectMapLayersDialog @@ -1014,7 +1081,6 @@ class SpectralLibrary(QgsVectorLayer): return # get the shapefile fields and check the minimum requirements - # standard_fields = qgsStandardFields() # field in the vector source vector_fields = vector_qgs_layer.fields() @@ -1024,84 +1090,94 @@ class SpectralLibrary(QgsVectorLayer): # fields we need to copy values from the vector source to each SpectralProfile fields_to_copy = [] - for field in vector_fields: if speclib_fields.indexOf(field.name()) == -1: speclib_fields.append(QgsField(field)) fields_to_copy.append(field.name()) + options = QgsVectorFileWriter.SaveVectorOptions() + options.driverName = 'GPKG' + # set spatial filter in destination CRS + options.filterExtent = SpatialExtent.fromLayer(raster_qgs_layer) + #options.filterExtent = SpatialExtent.fromLayer(raster_qgs_layer).toCrs(vector_qgs_layer.crs()) + options.destCRS = raster_qgs_layer.crs() + tmpPath = '/vsimem/tmp_transform{}.gpkg'.format(vector_qgs_layer.name()) + ct = QgsCoordinateTransform() + ct.setSourceCrs(vector_qgs_layer.crs()) + ct.setDestinationCrs(raster_qgs_layer.crs()) + options.ct = ct + error = QgsVectorFileWriter.writeAsVectorFormat(layer=vector_qgs_layer, + fileName=tmpPath, + options=options) + vector_qgs_layer.disconnect() + del vector_qgs_layer + + # make the internal FID a normal attribute which gdal can rasterize + tmp_qgs_layer = QgsVectorLayer(tmpPath) + if tmp_qgs_layer.featureCount() == 0: + info = 'No intersection between\nraster {} and vector {}'.format(raster_qgs_layer.source(), vector_qgs_layer.source()) + print(info) + if isinstance(progressDialog, QProgressDialog): + progressDialog.setLabelText('No intersection between raster and vector source') + progressDialog.setValue(progressDialog.maximum()) + return spectral_library + assert isinstance(tmp_qgs_layer, QgsVectorLayer) + assert tmp_qgs_layer.isValid() + assert tmp_qgs_layer.startEditing() + fidName = 'tmpFID' + i = 1 + while fidName in tmp_qgs_layer.fields().names(): + fidName = 'tmpFID{}'.format(i) + i += 1 + tmp_qgs_layer.addAttribute(QgsField(fidName, QVariant.Int, 'int')) + assert tmp_qgs_layer.commitChanges() + assert fidName in tmp_qgs_layer.fields().names() + + # let the tmpFID be fid + 1, so that there is not negative or zero tmpFID + # this will be the value to be burned into the gdal raster + assert tmp_qgs_layer.startEditing() + + i = tmp_qgs_layer.fields().lookupField(fidName) + for fid in [fid for fid in tmp_qgs_layer.allFeatureIds() if fid >= 0]: + tmp_qgs_layer.changeAttributeValue(fid, i, fid+1) + assert tmp_qgs_layer.commitChanges() # get ORG/GDAL dataset and OGR/GDAL layer of vector and raster - drv = ogr.GetDriverByName('Memory') - assert isinstance(drv, ogr.Driver) - # load vector data into memory - vector_dataset = drv.CopyDataSource(ogrDataSource(vector_qgs_layer), '') - assert isinstance(vector_dataset, ogr.DataSource) + tmp_ogrDataSource = ogr.Open(tmpPath) + assert isinstance(tmp_ogrDataSource, ogr.DataSource) + assert tmp_ogrDataSource.GetLayerCount() == 1 + tmp_ogrLayer = tmp_ogrDataSource.GetLayer(0) + assert isinstance(tmp_ogrLayer, ogr.Layer) + raster_dataset = gdal.Open(raster_qgs_layer.source()) - bn = os.path.basename(raster_dataset.GetDescription()) - assert isinstance(raster_dataset, gdal.Dataset) + badBandList = parseBadBandList(raster_dataset) wl, wlu = parseWavelength(raster_dataset) + bn = os.path.basename(raster_dataset.GetDescription()) + assert isinstance(raster_dataset, gdal.Dataset) - iLayer = 0 - layerNames = [vector_dataset.GetLayer(i).GetName() for i in range(vector_dataset.GetLayerCount())] - match = re.search('layername=([^|,;]+)', vector_qgs_layer.source()) - if match: - layerName = match.group(1) - if layerName in layerNames: - iLayer = layerNames.index(layerName) - - vector_layer = vector_dataset.GetLayer(iLayer) - assert isinstance(vector_layer, ogr.Layer) - ldefn = vector_layer.GetLayerDefn() - assert isinstance(ldefn, ogr.FeatureDefn) - fieldNames = [ldefn.GetFieldDefn(i).GetName() for i in range(ldefn.GetFieldCount())] - - # make the internal FID a normal attribute which we can rasterize - fidName = 'FID' - iProfile = 0 - while fidName in fieldNames: - fidName = 'tmpFID{}'.format(iProfile) - vector_layer.CreateField(ogr.FieldDefn(fidName, ogr.OFTInteger64)) - # transform geometries to raster SRS - srsVector = vector_layer.GetSpatialRef() - srsRaster = osr.SpatialReference() - srsRaster.ImportFromWkt(raster_dataset.GetProjection()) - - trans = osr.CoordinateTransformation(srsVector, srsRaster) - assert isinstance(trans, osr.CoordinateTransformation) - for feature in vector_layer: - assert isinstance(feature, ogr.Feature) - feature.SetField(fidName, feature.GetFID()+1) # let the smalled fid be 1 - - # assert geom.Transform(trans) == ogr.OGRERR_NONE - # wow! gdal.Rasterize considers SRS differences - # geom = feature.GetGeometryRef() - # feature.SetGeometry(geom) - vector_layer.SetFeature(feature) - - vector_layer.ResetReading() - - # vector to array to x and y values - driver = gdal.GetDriverByName('MEM') - mem = driver.Create('', raster_dataset.RasterXSize, raster_dataset.RasterYSize, 1, gdal.GDT_UInt32) - assert isinstance(mem, gdal.Dataset) - band = mem.GetRasterBand(1) + # create in-memory raster to burn the tempFID into + memDrv = gdal.GetDriverByName('MEM') + memRaster = memDrv.Create('', raster_dataset.RasterXSize, raster_dataset.RasterYSize, 1, gdal.GDT_UInt32) + assert isinstance(memRaster, gdal.Dataset) + band = memRaster.GetRasterBand(1) assert isinstance(band, gdal.Band) band.Fill(0) - mem.SetGeoTransform(raster_dataset.GetGeoTransform()) - mem.SetProjection(raster_dataset.GetProjection()) - mem.FlushCache() + memRaster.SetGeoTransform(raster_dataset.GetGeoTransform()) + memRaster.SetProjection(raster_dataset.GetProjection()) + memRaster.FlushCache() all_touched = 'TRUE' if all_touched else 'FALSE' - gdal.RasterizeLayer(mem, [1], vector_layer, + gdal.RasterizeLayer(memRaster, [1], tmp_ogrLayer, options=['ALL_TOUCHED={}'.format(all_touched), 'ATTRIBUTE={}'.format(fidName)]) - memory_array = mem.ReadAsArray() - y, x = np.where(memory_array > 0) + # read the burned tmpFIDs + tmpFIDArray = memRaster.ReadAsArray() + y, x = np.where(tmpFIDArray > 0) n_profiles = len(y) - memory_array = memory_array - 1 # decrease FID by 1 (we already got the right positions - + # decrease tmpFID array by 1 (we already got the right positions) + # it now contains the real FID values + tmpFIDArray = tmpFIDArray - 1 if n_profiles == 0: # no profiles to extract. Return an empty speclib @@ -1113,44 +1189,46 @@ class SpectralLibrary(QgsVectorLayer): progressDialog.setRange(0, raster_dataset.RasterCount + n_profiles + 1) progressDialog.setValue(0) - progressDialog.setLabelText('Read profiles...') - - fids = memory_array[y, x] - unique_fids = list(np.unique(fids)) - driver = None + progressDialog.setLabelText('Read {} profiles...'.format(n_profiles)) - percentage_to_extract = float(len(y)) / (mem.RasterXSize * mem.RasterYSize) + fids = tmpFIDArray[y, x] + unique_fids = np.unique(fids).tolist() + fids = fids.tolist() - del vector_layer, memory_array, mem, driver + del tmp_ogrLayer, tmpFIDArray, memRaster, memDrv # save all profiles in a spectral library profiles = [] - # transform x/y pixel position into speclib crs + # transform x/y pixel position into the speclib crs rasterCRS = raster_qgs_layer.crs() rasterGT = raster_dataset.GetGeoTransform() + rasterSRS = osr.SpatialReference() - speclibSRS = osr.SpatialReference() rasterSRS.ImportFromWkt(rasterCRS.toWkt()) + speclibSRS = osr.SpatialReference() speclibSRS.ImportFromWkt(spectral_library.crs().toWkt()) - transCRS = QgsCoordinateTransform() - transCRS.setSourceCrs(rasterCRS) - transCRS.setDestinationCrs(spectral_library.crs()) + # GDAL 3.0 considers authority specific axis order + # see https://github.com/OSGeo/gdal/issues/1974 + # https://gdal.org/tutorials/osr_api_tut.html + rasterSRS.SetAxisMappingStrategy(osr.OAMS_TRADITIONAL_GIS_ORDER) + speclibSRS.SetAxisMappingStrategy(osr.OAMS_TRADITIONAL_GIS_ORDER) # transform many coordinates fast! transSRS = osr.CoordinateTransformation(rasterSRS, speclibSRS) # geo-coordinate in raster CRS - if True: # pixel center - x2, y2 = x+0.5, y+0.5 + # move to pixel center + if True: + x2, y2 = x + 0.5, y + 0.5 else: x2, y2 = x, y - # transSRS.TransformPoints(np.ones((n_points, 2))) geo_coordinates = np.empty((n_profiles, 2), dtype=np.float) geo_coordinates[:, 0] = rasterGT[0] + x2 * rasterGT[1] + y2 * rasterGT[2] geo_coordinates[:, 1] = rasterGT[3] + x2 * rasterGT[4] + y2 * rasterGT[5] + geo_coordinates = transSRS.TransformPoints(geo_coordinates) attr_idx_profile = [] @@ -1164,7 +1242,7 @@ class SpectralLibrary(QgsVectorLayer): # store relevant features in memory for faster accesss features = {} featureAttributes = {} - for f in vector_qgs_layer.getFeatures(unique_fids): + for f in tmp_qgs_layer.getFeatures(unique_fids): assert isinstance(f, QgsFeature) features[f.id()] = f @@ -1178,8 +1256,6 @@ class SpectralLibrary(QgsVectorLayer): profileData = None - # should we consider a band-band-list? - for b in range(raster_dataset.RasterCount): if isinstance(progressDialog, QProgressDialog): if progressDialog.wasCanceled(): @@ -1199,14 +1275,34 @@ class SpectralLibrary(QgsVectorLayer): del raster_dataset, bandData - - - # 2. profiles with source vector metadata - - wasPointGeometry = vector_qgs_layer.wkbType() in [QgsWkbTypes.Point, QgsWkbTypes.PointGeometry] + # 2. write profiles + some meta data from the source vector file + wasPointGeometry = tmp_qgs_layer.wkbType() in [QgsWkbTypes.Point, QgsWkbTypes.PointGeometry] if wasPointGeometry: pass + # which field is to choose to describe the spectrum name? + nameFieldIndex = None + if isinstance(nameField, int): + nameFieldIndex = nameField + elif isinstance(nameField, QgsField): + nameFieldIndex = vector_fields.lookupField(nameField.name()) + elif isinstance(nameField, str): + nameFieldIndex = vector_fields.lookupField(nameField) + + if nameFieldIndex is None: + for i, field in enumerate(vector_fields): + assert isinstance(field, QgsField) + if field.type() == QVariant.String and re.search(field.name(), 'Name', re.I): + nameFieldIndex = vector_fields.lookupField(field.name()) + break + if nameFieldIndex is None: + for i, field in enumerate(vector_fields): + assert isinstance(field, QgsField) + if re.search(field.name(), '^(fid|id)$', re.I): + nameFieldIndex = vector_fields.lookupField(field.name()) + break + + profileNameCounts = dict() # dictionary to store spectra names for iProfile, fid, in enumerate(fids): if isinstance(progressDialog, QProgressDialog): @@ -1218,39 +1314,65 @@ class SpectralLibrary(QgsVectorLayer): assert isinstance(feature, QgsFeature) profile = SpectralProfile(fields=speclib_fields) - # 2.1 set profile id + name + # 2.1 set profile id profile.setId(iProfile) - px_x, px_y = x[iProfile], y[iProfile] - profile.setName('{} {},{}'.format(bn, px_x, px_y)) - # 2.2 set geometry + # 2.2. set profile name + if nameFieldIndex is None: + px_x, px_y = x[iProfile], y[iProfile] + profileName = '{} {},{}'.format(bn, px_x, px_y) + else: + profileName = str(feature.attribute(nameFieldIndex)) + + n = profileNameCounts.get(profileName) + # n = 1 -> Foobar + # n > 1 -> Foobar (n) + if n is None: + profileNameCounts[profileName] = 1 + else: + profileNameCounts[profileName] = n + 1 + profileName = profileName + ' ({})'.format(n + 1) + profile.setName(profileName) + + # 2.3 set geometry g = geo_coordinates[iProfile] profile.setGeometry(QgsPoint(g[0], g[1])) - # 2.3 copy vector feature attribute + # 2.4 copy vector feature attribute for idx_p, idx_f in zip(attr_idx_profile, attr_idx_feature): profile.setAttribute(idx_p, feature.attribute(idx_f)) - # 2.4 set the profile values - profile.setValues(x=wl, y=profileData[:, iProfile], xUnit=wlu) + # 2.5 set the profile values + profile.setValues(x=wl, y=profileData[:, iProfile], xUnit=wlu, bbl=badBandList) profiles.append(profile) + if returnProfileList: + return profiles if isinstance(progressDialog, QProgressDialog): progressDialog.setLabelText('Create speclib...') - spectral_library.startEditing() + assert spectral_library.startEditing() spectral_library.addMissingFields(vector_fields) + assert spectral_library.commitChanges() + assert spectral_library.startEditing() # spectral_library.addProfiles(profiles) if not spectral_library.addFeatures(profiles, QgsFeatureSink.FastInsert): s = "" - spectral_library.commitChanges() + + if not spectral_library.commitChanges(): + s = "" if isinstance(progressDialog, QProgressDialog): # print('{} {} {}'.format(progressDialog.minimum(), progressDialog.maximum(), progressDialog.value())) progressDialog.setValue(progressDialog.value()+1) + tmp_qgs_layer.disconnect() + del tmp_qgs_layer + gdal.Unlink(tmpPath) + + return spectral_library @@ -1263,7 +1385,7 @@ class SpectralLibrary(QgsVectorLayer): :param mode: :return: """ - + warnings.warn(DeprecationWarning(r'Use readFromVector instead')) assert mode in ['CENTROIDS', 'AVERAGES', 'PIXELS'] if isinstance(rasterSource, str): @@ -1586,7 +1708,7 @@ class SpectralLibrary(QgsVectorLayer): @staticmethod - def readFrom(uri): + def readFrom(uri, progressDialog:QProgressDialog=None): """ Reads a Spectral Library from the source specified in "uri" (path, url, ...) :param uri: path or uri of the source from which to read SpectralProfiles and return them in a SpectralLibrary @@ -1603,12 +1725,13 @@ class SpectralLibrary(QgsVectorLayer): readers = AbstractSpectralLibraryIO.__subclasses__() for cls in sorted(readers, key=lambda r:r.score(uri)): - if cls.canRead(uri): - return cls.readFrom(uri) + try: + if cls.canRead(uri): + return cls.readFrom(uri, progressDialog=progressDialog) + except Exception as ex: + s = "" return None - - sigNameChanged = pyqtSignal(str) __refs__ = [] @@ -1691,8 +1814,9 @@ class SpectralLibrary(QgsVectorLayer): """ Initializes the default QgsFeatureRenderer """ - self.renderer().symbol().setColor(DEFAULT_PROFILE_COLOR) - s = "" + color = speclibSettings().value('DEFAULT_PROFILE_COLOR', QColor('green')) + self.renderer().symbol().setColor(color) + def initTableConfig(self): """ @@ -1816,7 +1940,7 @@ class SpectralLibrary(QgsVectorLayer): # self.dataProvider().addAttributes(missingFields) - def addSpeclib(self, speclib, addMissingFields=True): + def addSpeclib(self, speclib, addMissingFields=True, progressDialog:QProgressDialog=None): """ Adds another SpectraLibrary :param speclib: SpectralLibrary @@ -1824,69 +1948,85 @@ class SpectralLibrary(QgsVectorLayer): """ assert isinstance(speclib, SpectralLibrary) - self.addProfiles(speclib.profiles(), addMissingFields=addMissingFields) + self.addProfiles(speclib, addMissingFields=addMissingFields, progressDialog=progressDialog) s = "" - def addProfiles(self, profiles, addMissingFields:bool=None): - - if addMissingFields is None: - addMissingFields = isinstance(profiles, SpectralLibrary) + def addProfiles(self, profiles:typing.Union[typing.List[SpectralProfile], QgsVectorLayer], + addMissingFields:bool=None, + progressDialog:QProgressDialog=None): if isinstance(profiles, SpectralProfile): profiles = [profiles] - elif isinstance(profiles, SpectralLibrary): - profiles = profiles.profiles() - profiles = list(profiles) + if addMissingFields is None: + addMissingFields = isinstance(profiles, SpectralLibrary) if len(profiles) == 0: return assert self.isEditable(), 'SpectralLibrary "{}" is not editable. call startEditing() first'.format(self.name()) - - profiles2 = [] - fieldLookup={} - def createCopy(srcFeature:QgsFeature)->QgsFeature: - p2 = QgsFeature(self.fields()) - srcAttributes = srcFeature.attributes() - p2.setGeometry(srcFeature.geometry()) - for i1, i2 in fieldLookup.items(): - v = srcAttributes[i1] - p2.setAttribute(i2, None if v == QVariant(None) else v) - return p2 - - pRef = profiles[0] - if addMissingFields: - self.addMissingFields(pRef.fields()) + if isinstance(progressDialog, QProgressDialog): + progressDialog.setLabelText('Add {} profiles'.format(len(profiles))) + progressDialog.setValue(0) + progressDialog.setRange(0, len(profiles)) iSrcList = [] iDstList = [] - for i1, srcName in enumerate(pRef.fields().names()): - if srcName == FIELD_FID: - continue - i2 = self.fields().lookupField(srcName) - if i2 >= 0: - iSrcList.append(i1) - iDstList.append(i2) - fieldLookup[i1] = i2 - elif addMissingFields: - raise Exception('Missing field: "{}"'.format(srcName)) - # create new features + copy geometry + bufferLength = 500 + profileBuffer = [] + + nAdded = 0 + + def flushBuffer(): + nonlocal self, nAdded, profileBuffer, progressDialog + if not self.addFeatures(profileBuffer): + self.raiseError() + nAdded += len(profileBuffer) + #print('added {}'.format(len(profileBuffer))) + profileBuffer.clear() + + if isinstance(progressDialog, QProgressDialog): + progressDialog.setValue(nAdded) - for pSrc in profiles: + QApplication.processEvents() + + + + for i, pSrc in enumerate(profiles): + #print(i) + if i == 0: + if addMissingFields: + self.addMissingFields(pSrc.fields()) + + for iSrc, srcName in enumerate(pSrc.fields().names()): + if srcName == FIELD_FID: + continue + iDst = self.fields().lookupField(srcName) + if iDst >= 0: + iSrcList.append(iSrc) + iDstList.append(iDst) + elif addMissingFields: + raise Exception('Missing field: "{}"'.format(srcName)) + + # create new feature + copy geometry pDst = QgsFeature(self.fields()) pDst.setGeometry(pSrc.geometry()) - profiles2.append(pDst) - for iSrc, iDst in zip(iSrcList,iDstList): - for pSrc, pDst in zip(profiles, profiles2): + # copy attributes + for iSrc, iDst in zip(iSrcList, iDstList): pDst.setAttribute(iDst, pSrc.attribute(iSrc)) - if not self.addFeatures(profiles2): - self.raiseError() - s = "" + profileBuffer.append(pDst) + + if len(profileBuffer) >= bufferLength: + flushBuffer() + + + flushBuffer() + + #s = "" def speclibFromFeatureIDs(self, fids): if isinstance(fids, int): @@ -1937,13 +2077,13 @@ class SpectralLibrary(QgsVectorLayer): for fid in fids: assert isinstance(fid, int) featureRequest.setFilterFids(fids) - # features = [f for f in self.features() if f.id() in fids] + # features = [f for f in self.features() if f.id() in fidsToRemove] return self.getFeatures(featureRequest) - def profiles(self, fids=None): + def profiles(self, fids=None)->typing.Generator[SpectralProfile, None, None]: """ - Like features(fids=None), but converts each returned QgsFeature into a SpectralProfile + Like features(fidsToRemove=None), but converts each returned QgsFeature into a SpectralProfile :param fids: optional, [int-list-of-feature-ids] to return :return: generator of [List-of-SpectralProfiles] """ @@ -2118,9 +2258,9 @@ class SpectralLibrary(QgsVectorLayer): self.commitChanges() - def __len__(self): + def __len__(self)->int: cnt = self.featureCount() - #can be -1 if the number of features is unknown + # can be -1 if the number of features is unknown return max(cnt, 0) def __iter__(self): @@ -2161,10 +2301,9 @@ class SpectralLibrary(QgsVectorLayer): class AbstractSpectralLibraryIO(object): """ Abstract class interface to define I/O operations for spectral libraries - Overwrite the canRead and read From routines. """ @staticmethod - def canRead(path): + def canRead(path:str)->bool: """ Returns true if it can read the source defined by path :param path: source uri @@ -2173,18 +2312,24 @@ class AbstractSpectralLibraryIO(object): return False @staticmethod - def readFrom(path): + def readFrom(path:str, progressDialog:QProgressDialog=None)->SpectralLibrary: """ Returns the SpectralLibrary read from "path" - :param path: source of SpectralLibrary + :param path: source of Spectral Library + :param progressDialog: QProgressDialog, which well-behave implementations can use to show the import progress. :return: SpectralLibrary """ return None @staticmethod - def write(speclib, path): - """Writes the SpectralLibrary to path and returns a list of written files that can be used to open - the spectral library with readFrom""" + def write(speclib:SpectralLibrary, path:str, progressDialog:QProgressDialog)->typing.List[str]: + """ + Writes the SpectralLibrary. + :param speclib: SpectralLibrary to write + :param path: file path to write the SpectralLibrary to + :param progressDialog: QProgressDialog, which well-behave implementations can use to show the writting progress. + :return: a list of paths that can be used to re-open all written profiles + """ assert isinstance(speclib, SpectralLibrary) return [] @@ -2199,6 +2344,31 @@ class AbstractSpectralLibraryIO(object): """ return 0 + @staticmethod + def filterString()->str: + """ + Returns a Qt file filter string + :return: + :rtype: + """ + + return None + + @staticmethod + def addImportActions(spectralLibrary:SpectralLibrary, menu:QMenu): + """ + Returns a list of QActions or QMenus that can be called to read/import SpectralProfiles from a certain file format into a SpectralLibrary + :param spectralLibrary: SpectralLibrary to import SpectralProfiles to + :return: [list-of-QAction-or-QMenus] + """ + + @staticmethod + def addExportActions(spectralLibrary:SpectralLibrary, menu:QMenu): + """ + Returns a list of QActions or QMenus that can be called to write/export SpectralProfiles into certain file format + :param spectralLibrary: SpectralLibrary to export SpectralProfiles from + :return: [list-of-QAction-or-QMenus] + """ class AddAttributeDialog(QDialog): @@ -2359,13 +2529,13 @@ class SpectralProfileMapTool(QgsMapToolEmitPoint): self.marker = QgsVertexMarker(self.mCanvas) self.rubberband = QgsRubberBand(self.mCanvas, QgsWkbTypes.PolygonGeometry) - color = QColor('red') + plotStyle = QColor('red') self.rubberband.setLineStyle(Qt.SolidLine) - self.rubberband.setColor(color) + self.rubberband.setColor(plotStyle) self.rubberband.setWidth(2) - self.marker.setColor(color) + self.marker.setColor(plotStyle) self.marker.setPenWidth(3) self.marker.setIconSize(5) self.marker.setIconType(QgsVertexMarker.ICON_CROSS) # or ICON_CROSS, ICON_X @@ -2375,9 +2545,9 @@ class SpectralProfileMapTool(QgsMapToolEmitPoint): self.marker.setCenter(geoPoint) #self.marker.show() - def setStyle(self, color=None, brushStyle=None, fillColor=None, lineStyle=None): - if color: - self.rubberband.setColor(color) + def setStyle(self, plotStyle=None, brushStyle=None, fillColor=None, lineStyle=None): + if plotStyle: + self.rubberband.setColor(plotStyle) if brushStyle: self.rubberband.setBrushStyle(brushStyle) if fillColor: @@ -2962,14 +3132,6 @@ def registerSpectralProfileEditorWidget(): reg.registerWidget(EDITOR_WIDGET_REGISTRY_KEY, SPECTRAL_PROFILE_EDITOR_WIDGET_FACTORY) -def registerAbstractLibraryIOs(): - try: - import asd - except: - s = "" - - - from ..utils import SelectMapLayersDialog class SpectralProfileImportPointsDialog(SelectMapLayersDialog): @@ -3076,15 +3238,32 @@ class SpectralLibraryWidget(QMainWindow, loadSpeclibUI('spectrallibrarywidget.ui # set spacer into menu # empty = QWidget(self.mToolbar) #empty.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) - from .plotting import SpectralLibraryPlotWidget + from ..speclib.plotting import SpectralLibraryPlotWidget assert isinstance(self.mPlotWidget, SpectralLibraryPlotWidget) - #self.mToolbar.insertWidget(self.actionReload, empty) - - self.mColorCurrentSpectra = COLOR_SELECTED_SPECTRA - self.mColorSelectedSpectra = COLOR_SELECTED_SPECTRA self.m_plot_max = 500 + from .envi import EnviSpectralLibraryIO + from .csvdata import CSVSpectralLibraryIO + from .asd import ASDSpectralLibraryIO + from .ecosis import EcoSISSpectralLibraryIO + from .specchio import SPECCHIOSpectralLibraryIO + from .artmo import ARTMOSpectralLibraryIO + from .vectorsources import VectorSourceSpectralLibraryIO + + self.mSpeclibIOInterfaces = [ + EnviSpectralLibraryIO(), + CSVSpectralLibraryIO(), + ARTMOSpectralLibraryIO(), + ASDSpectralLibraryIO(), + EcoSISSpectralLibraryIO(), + SPECCHIOSpectralLibraryIO(), + VectorSourceSpectralLibraryIO(), + ] + + self.mSpeclibIOInterfaces = sorted(self.mSpeclibIOInterfaces, key=lambda c: c.__class__.__name__) + + self.mSelectionModel = None if not isinstance(speclib, SpectralLibrary): @@ -3093,19 +3272,12 @@ class SpectralLibraryWidget(QMainWindow, loadSpeclibUI('spectrallibrarywidget.ui assert isinstance(speclib, SpectralLibrary) self.mSpeclib = speclib - MAP_LAYER_STORES[0].addMapLayer(speclib) + QPS_MAPLAYER_STORE.addMapLayer(speclib) self.mSpeclib.editingStarted.connect(self.onIsEditableChanged) self.mSpeclib.editingStopped.connect(self.onIsEditableChanged) self.mSpeclib.selectionChanged.connect(self.onSelectionChanged) - - - from .plotting import SpectralLibraryPlotWidget - assert isinstance(self.mPlotWidget, SpectralLibraryPlotWidget) - self.mPlotWidget.setSpeclib(self.mSpeclib) - - - self.mPlotWidget.backgroundBrush().setColor(COLOR_BACKGROUND) + self.mSpeclib.nameChanged.connect(lambda *args, sl=self.mSpeclib: self.setWindowTitle(sl.name())) if isinstance(mapCanvas, QgsMapCanvas): self.mCanvas = mapCanvas @@ -3121,12 +3293,14 @@ class SpectralLibraryWidget(QMainWindow, loadSpeclibUI('spectrallibrarywidget.ui self.mDualView.init(self.mSpeclib, self.mCanvas) self.mDualView.setView(QgsDualView.AttributeTable) self.mDualView.setAttributeTableConfig(self.mSpeclib.attributeTableConfig()) + self.mDualView.showContextMenuExternally.connect(self.onShowContextMenuExternally) + self.mDualView.tableView().willShowContextMenu.connect(self.onWillShowContextMenu) + from .plotting import SpectralLibraryPlotWidget + assert isinstance(self.mPlotWidget, SpectralLibraryPlotWidget) + self.mPlotWidget.setDualView(self.mDualView) + self.mPlotWidget.mUpdateTimer.timeout.connect(self.updateStatusBar) - self.mTableView = self.mDualView.tableView() - assert isinstance(self.mTableView, QgsAttributeTableView) - self.mTableView.willShowContextMenu.connect(self.onWillShowContextMenu) - - # change selected row color: keep color also when attribtue table looses focus + # change selected row plotStyle: keep plotStyle also when the attribute table looses focus pal = self.mDualView.tableView().palette() cSelected = pal.color(QPalette.Active, QPalette.Highlight) @@ -3160,6 +3334,29 @@ class SpectralLibraryWidget(QMainWindow, loadSpeclibUI('spectrallibrarywidget.ui self.spectraLibrary = self.speclib self.clearTable = self.clearSpectralLibrary + def applyAllPlotUpdates(self): + """ + Forces the plot widget to update + :return: + :rtype: + """ + self.plotWidget().onPlotUpdateTimeOut() + + def updateStatusBar(self): + + assert isinstance(self.mStatusBar, QStatusBar) + slib = self.speclib() + nFeatures = slib.featureCount() + nSelected = slib.selectedFeatureCount() + nVisible = self.plotWidget().plottedProfileCount() + msg = "{}/{}/{}".format(nFeatures, nSelected, nVisible) + self.mStatusBar.showMessage(msg) + + def onShowContextMenuExternally(self, menu:QgsActionMenu, fid): + s = "" + + + def onImportFromVectorSource(self): d = SpectralProfileImportPointsDialog() @@ -3201,6 +3398,47 @@ class SpectralLibraryWidget(QMainWindow, loadSpeclibUI('spectrallibrarywidget.ui menu.addAction(self.actionCopySelectedRows) menu.addAction(self.actionPasteFeatures) + menu.addSeparator() + + selectedFIDs = self.mDualView.tableView().selectedFeaturesIds() + n = len(selectedFIDs) + menuProfileStyle = menu.addMenu('Profile Style') + wa = QWidgetAction(menuProfileStyle) + + btnResetProfileStyles = QPushButton('Reset') + + plotStyle = self.plotWidget().colorScheme().ps + if n == 0: + btnResetProfileStyles.setText('Reset All') + btnResetProfileStyles.clicked.connect(self.plotWidget().resetProfileStyles) + btnResetProfileStyles.setToolTip('Resets all profile styles') + else: + from .plotting import SpectralProfilePlotDataItem + for fid in selectedFIDs: + spi = self.plotWidget().spectralProfilePlotDataItem(fid) + if isinstance(spi, SpectralProfilePlotDataItem): + plotStyle = PlotStyle.fromPlotDataItem(spi) + + btnResetProfileStyles.setText('Reset Selected') + btnResetProfileStyles.clicked.connect(lambda *args, fids=selectedFIDs: self.plotWidget().setProfileStyle(None, fids)) + + psw = PlotStyleWidget(plotStyle=plotStyle) + psw.setPreviewVisible(False) + psw.cbIsVisible.setVisible(False) + psw.sigPlotStyleChanged.connect(lambda style, fids=selectedFIDs : self.plotWidget().setProfileStyle(style, fids)) + + frame = QFrame() + l = QVBoxLayout() + l.addWidget(btnResetProfileStyles) + l.addWidget(psw) + + frame.setLayout(l) + wa.setDefaultWidget(frame) + menuProfileStyle.addAction(wa) + + self.mDualView.tableView().currentIndex() + + def clearSpectralLibrary(self): """ Removes all SpectralProfiles and additional fields @@ -3267,7 +3505,7 @@ class SpectralLibraryWidget(QMainWindow, loadSpeclibUI('spectrallibrarywidget.ui - def initActions(self): + def initActions(self): self.actionSelectProfilesFromMap.triggered.connect(self.sigLoadFromMapRequest.emit) @@ -3287,13 +3525,15 @@ class SpectralLibraryWidget(QMainWindow, loadSpeclibUI('spectrallibrarywidget.ui if b else self.setCurrentProfilesMode(SpectralLibraryWidget.CurrentProfilesMode.normal) ) - self.actionImportSpeclib.triggered.connect(lambda: self.importSpeclib()) + self.actionImportSpeclib.triggered.connect(self.onImportSpeclib) + self.actionImportSpeclib.setMenu(self.importSpeclibMenu()) self.actionImportVectorSource.triggered.connect(self.onImportFromVectorSource) self.actionAddProfiles.triggered.connect(self.addCurrentSpectraToSpeclib) self.actionReloadProfiles.triggered.connect(self.onReloadProfiles) + m = QMenu() - m.addAction(self.actionImportSpeclib) + #m.addAction(self.actionImportSpeclib) m.addAction(self.actionImportVectorSource) m.addAction(self.optionAddCurrentProfilesAutomatically) m.addSeparator() @@ -3301,9 +3541,10 @@ class SpectralLibraryWidget(QMainWindow, loadSpeclibUI('spectrallibrarywidget.ui self.actionAddProfiles.setMenu(m) - self.actionSaveSpeclib.triggered.connect(self.onExportSpectra) - - self.actionReload.triggered.connect(lambda : self.mPlotWidget.updatePlot()) + self.actionExportSpeclib.triggered.connect(self.onExportSpectra) + self.actionExportSpeclib.setMenu(self.exportSpeclibMenu()) + self.actionSaveSpeclib = self.actionExportSpeclib # backward compatibility + self.actionReload.triggered.connect(lambda : self.mPlotWidget.updateSpectralProfilePlotItems()) self.actionToggleEditing.toggled.connect(self.onToggleEditing) self.actionSaveEdits.triggered.connect(self.onSaveEdits) self.actionDeleteSelected.triggered.connect(lambda : deleteSelected(self.speclib())) @@ -3335,6 +3576,27 @@ class SpectralLibraryWidget(QMainWindow, loadSpeclibUI('spectrallibrarywidget.ui self.onIsEditableChanged() + def importSpeclibMenu(self)->QMenu: + """ + :return: QMenu with QActions and submenus to import SpectralProfiles + """ + m = QMenu() + for iface in self.mSpeclibIOInterfaces: + assert isinstance(iface, AbstractSpectralLibraryIO), iface + iface.addImportActions(self.speclib(), m) + return m + + def exportSpeclibMenu(self)->QMenu: + """ + :return: QMenu with QActions and submenus to export SpectralProfiles + """ + m = QMenu() + for iface in self.mSpeclibIOInterfaces: + assert isinstance(iface, AbstractSpectralLibraryIO) + iface.addExportActions(self.speclib(), m) + return m + + def showProperties(self, *args): from ..layerproperties import showLayerPropertiesDialog @@ -3343,16 +3605,13 @@ class SpectralLibraryWidget(QMainWindow, loadSpeclibUI('spectrallibrarywidget.ui s = "" - def importSpeclib(self, path=None): + def onImportSpeclib(self): """ Imports a SpectralLibrary :param path: str """ - slib = None - if path is None: - slib = SpectralLibrary.readFromSourceDialog(self) - else: - slib = SpectralLibrary.readFrom(path) + + slib = SpectralLibrary.readFromSourceDialog(self) if isinstance(slib, SpectralLibrary) and len(slib) > 0: self.addSpeclib(slib) @@ -3387,6 +3646,11 @@ class SpectralLibraryWidget(QMainWindow, loadSpeclibUI('spectrallibrarywidget.ui self.actionDeleteSelected.setEnabled(self.mSpeclib.isEditable() and hasSelected) self.actionReloadProfiles.setEnabled(self.mSpeclib.isEditable() and hasSelected) + self.actionPanMapToSelectedRows.setEnabled(hasSelected) + self.actionRemoveSelection.setEnabled(hasSelected) + self.actionZoomMapToSelectedRows.setEnabled(hasSelected) + + def onIsEditableChanged(self, *args): speclib = self.speclib() @@ -3432,7 +3696,7 @@ class SpectralLibraryWidget(QMainWindow, loadSpeclibUI('spectrallibrarywidget.ui def onReloadProfiles(self): cnt = self.speclib().selectedFeatureCount() - if cnt > 0 and self.speclib().isEditable(): + if cnt > 0 and self.speclib().isEditable(): # ask for profile source raster from ..utils import SelectMapLayersDialog @@ -3572,13 +3836,13 @@ class SpectralLibraryWidget(QMainWindow, loadSpeclibUI('spectrallibrarywidget.ui if isinstance(iface, QgisInterface): iface.copySelectionToClipboard(self.mSpeclib) - def onAttributesChanged(self): - self.btnRemoveAttribute.setEnabled(len(self.mSpeclib.metadataAttributes()) > 0) + #def onAttributesChanged(self): + # self.btnRemoveAttribute.setEnabled(len(self.mSpeclib.metadataAttributes()) > 0) - def addAttribute(self, name): - name = str(name) - if len(name) > 0 and name not in self.mSpeclib.metadataAttributes(): - self.mModel.addAttribute(name) + #def addAttribute(self, name): + # name = str(name) + # if len(name) > 0 and name not in self.mSpeclib.metadataAttributes(): + # self.mModel.addAttribute(name) def plotWidget(self): """ @@ -3612,40 +3876,32 @@ class SpectralLibraryWidget(QMainWindow, loadSpeclibUI('spectrallibrarywidget.ui progressDialog = QProgressDialog(self) - progressDialog.setRange(0,1) - info = 'Add {} profiles...'.format(len(speclib)) - progressDialog.setLabelText(info) - progressDialog.setValue(0) + progressDialog.setWindowTitle('Add Profiles') + progressDialog.show() + info = 'Add {} profiles...'.format(len(speclib)) wasEditable = sl.isEditable() - plotWasBlocked = self.mPlotWidget.blockUpdates(True) + try: sl.startEditing() sl.beginEditCommand(info) - sl.addSpeclib(speclib) + sl.addSpeclib(speclib, progressDialog=progressDialog) sl.endEditCommand() if not wasEditable: sl.commitChanges() - except: - s = "" + except Exception as ex: + print(ex, file=sys.stderr) pass - progressDialog.setValue(1) - self.mPlotWidget.blockUpdates(plotWasBlocked) - self.mPlotWidget.syncLibrary() - s = "" - #def onReset(*args): - # self.mProgressBar.setValue(0) - # self.mInfoLabel.setText('') - # self.mPlotWidget.blockUpdates(False) - # self.mPlotWidget.syncLibrary() + progressDialog.hide() + progressDialog.close() + QApplication.processEvents() - #QTimer.singleShot(500, onReset) def addCurrentSpectraToSpeclib(self, *args): """ - Adds all current spectral profiles to the "persistant" SpectralLibrary + Adds all current spectral profiles to the "persistent" SpectralLibrary """ profiles = self.currentSpectra() @@ -3695,9 +3951,10 @@ class SpectralLibraryWidget(QMainWindow, loadSpeclibUI('spectrallibrarywidget.ui newCurrent = collections.OrderedDict() + colorScheme = self.plotWidget().colorScheme() for i, p in enumerate(profiles): pdi = SpectralProfilePlotDataItem(p) - pdi.setColor(CURRENT_PROFILE_COLOR) + pdi.setPlotStyle(colorScheme.cs) newCurrent[p] = pdi self.mCurrentProfiles.update(newCurrent) @@ -3755,4 +4012,7 @@ class SpectralLibraryPanel(QgsDockWidget): self.SLW.setCurrentProfilesMode(mode) -registerAbstractLibraryIOs() \ No newline at end of file +class SpectralLibraryLayerStyleWidget(QgsMapLayerConfigWidget): + + pass + diff --git a/eotimeseriesviewer/externals/qps/speclib/spectrallibraryplotcolorschemewidget.ui b/eotimeseriesviewer/externals/qps/speclib/spectrallibraryplotcolorschemewidget.ui new file mode 100644 index 0000000000000000000000000000000000000000..511189320a5287f2337e7ad5957648cac11619f3 --- /dev/null +++ b/eotimeseriesviewer/externals/qps/speclib/spectrallibraryplotcolorschemewidget.ui @@ -0,0 +1,190 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Form</class> + <widget class="QWidget" name="Form"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>262</width> + <height>247</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout" rowstretch="0,1,1,1,0,0,0" columnstretch="0,2"> + <property name="leftMargin"> + <number>2</number> + </property> + <property name="topMargin"> + <number>2</number> + </property> + <property name="rightMargin"> + <number>2</number> + </property> + <property name="bottomMargin"> + <number>2</number> + </property> + <property name="spacing"> + <number>2</number> + </property> + <item row="3" column="1"> + <widget class="QgsColorButton" name="btnColorInfo"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QgsColorButton" name="btnColorForeground"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Crosshair</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QgsColorButton" name="btnColorBackground"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>...</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Foreground</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Background</string> + </property> + </widget> + </item> + <item row="0" column="0" colspan="2"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QToolButton" name="btnColorSchemeDark"> + <property name="styleSheet"> + <string notr="true">background-color:"black"; +color:"white"</string> + </property> + <property name="text"> + <string>Dark</string> + </property> + <property name="autoRaise"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="btnColorSchemeBright"> + <property name="styleSheet"> + <string notr="true">background-color:"white"; +color:"black"</string> + </property> + <property name="text"> + <string>Bright</string> + </property> + <property name="autoRaise"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QToolButton" name="btnReset"> + <property name="text"> + <string>Reset</string> + </property> + <property name="autoRaise"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item row="4" column="0" colspan="2"> + <widget class="QCheckBox" name="cbUseRendererColors"> + <property name="text"> + <string>Vector point colors as line colors</string> + </property> + </widget> + </item> + <item row="5" column="0" colspan="2"> + <widget class="PlotStyleWidget" name="wDefaultProfileStyle" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>1</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>50</width> + <height>50</height> + </size> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>QgsColorButton</class> + <extends>QToolButton</extends> + <header>qgscolorbutton.h</header> + </customwidget> + <customwidget> + <class>PlotStyleWidget</class> + <extends>QWidget</extends> + <header>qps.plotstyling.plotstyling</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources> + <include location="../../../../QGIS/images/images.qrc"/> + <include location="../qpsresources.qrc"/> + </resources> + <connections/> +</ui> diff --git a/eotimeseriesviewer/externals/qps/speclib/spectrallibrarywidget.ui b/eotimeseriesviewer/externals/qps/speclib/spectrallibrarywidget.ui index 60b7126473bd8023cb63c62248b34c7abd4f7e40..918178cfa6ea29f754aa52d468c009377effaca3 100644 --- a/eotimeseriesviewer/externals/qps/speclib/spectrallibrarywidget.ui +++ b/eotimeseriesviewer/externals/qps/speclib/spectrallibrarywidget.ui @@ -60,23 +60,6 @@ <property name="acceptDrops"> <bool>true</bool> </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <property name="spacing"> - <number>2</number> - </property> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - </layout> </widget> </widget> </item> @@ -94,7 +77,8 @@ </attribute> <addaction name="actionSelectProfilesFromMap"/> <addaction name="actionAddProfiles"/> - <addaction name="actionSaveSpeclib"/> + <addaction name="actionImportSpeclib"/> + <addaction name="actionExportSpeclib"/> </widget> <widget class="QToolBar" name="toolBarFeatureSelections"> <property name="windowTitle"> @@ -135,7 +119,7 @@ </widget> <widget class="QStatusBar" name="mStatusBar"> <property name="sizeGripEnabled"> - <bool>false</bool> + <bool>true</bool> </property> </widget> <action name="actionImportSpeclib"> @@ -150,7 +134,7 @@ <string>Import Spectral Library</string> </property> </action> - <action name="actionSaveSpeclib"> + <action name="actionExportSpeclib"> <property name="icon"> <iconset resource="../qpsresources.qrc"> <normaloff>:/qps/ui/icons/speclib_save.svg</normaloff>:/qps/ui/icons/speclib_save.svg</iconset> diff --git a/eotimeseriesviewer/externals/qps/speclib/vectorsources.py b/eotimeseriesviewer/externals/qps/speclib/vectorsources.py new file mode 100644 index 0000000000000000000000000000000000000000..6840921f1946a6de7c47caa5a96b0ad542dc9f55 --- /dev/null +++ b/eotimeseriesviewer/externals/qps/speclib/vectorsources.py @@ -0,0 +1,168 @@ + +import os, sys, re, pathlib, json, io, re, linecache +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.PyQt.QtWidgets import * +from qgis.core import * + + +from .spectrallibraries import SpectralProfile, SpectralLibrary, AbstractSpectralLibraryIO, FIELD_FID, FIELD_VALUES, FIELD_NAME, findTypeFromString, createQgsField, OGR_EXTENSION2DRIVER + +class VectorSourceSpectralLibraryIO(AbstractSpectralLibraryIO): + """ + I/O Interface for the EcoSIS spectral library format. + See https://ecosis.org for details. + """ + @staticmethod + def canRead(path:str): + """ + Returns true if it can read the source defined by path + :param path: source uri + :return: True, if source is readable. + """ + try: + + lyr = QgsVectorLayer(path) + assert isinstance(lyr, QgsVectorLayer) + assert lyr.isValid() + fieldNames = lyr.fields().names() + for fn in [FIELD_NAME, FIELD_VALUES]: + assert fn in fieldNames + typeName = lyr.fields().at(lyr.fields().lookupField(FIELD_NAME)).typeName() + assert re.search('(string|varchar|char|json)', typeName, re.I) + return True + except: + return False + return False + + + @staticmethod + def readFrom(path, progressDialog:QProgressDialog=None, addAttributes:bool = True)->SpectralLibrary: + """ + Returns the SpectralLibrary read from "path" + :param path: source of SpectralLibrary + :return: SpectralLibrary + """ + + lyr = QgsVectorLayer(path) + assert isinstance(lyr, QgsVectorLayer) + + + speclib = SpectralLibrary() + assert isinstance(speclib, SpectralLibrary) + + speclib.setName(lyr.name()) + + + assert speclib.startEditing() + + if addAttributes: + speclib.addMissingFields(lyr.fields()) + assert speclib.commitChanges() + assert speclib.startEditing() + + + profiles = [] + for feature in lyr.getFeatures(): + profile = SpectralProfile(fields=speclib.fields()) + for name in speclib.fieldNames(): + profile.setAttribute(name, feature.attribute(name)) + profiles.append(profile) + + speclib.addProfiles(profiles, addMissingFields=False) + + assert speclib.commitChanges() + return speclib + + @staticmethod + def write(speclib:SpectralLibrary, path:str, progressDialog:QProgressDialog=None, options:QgsVectorFileWriter.SaveVectorOptions=None): + """ + Writes the SpectralLibrary to path and returns a list of written files that can be used to open the spectral library with readFrom + """ + assert isinstance(speclib, SpectralLibrary) + basePath, ext = os.path.splitext(path) + + + + if not isinstance(options, QgsVectorFileWriter.SaveVectorOptions): + driverName = OGR_EXTENSION2DRIVER.get(ext, 'GPKG') + options = QgsVectorFileWriter.SaveVectorOptions() + options.fileEncoding = 'utf-8' + options.driverName = driverName + + if driverName == 'GPKG' and not ext == '.gpkg': + path += '.gpkg' + + if options.layerName in [None, '']: + options.layerName = speclib.name() + + errors = QgsVectorFileWriter.writeAsVectorFormat(layer=speclib, + fileName=path, + options=options) + writtenFiles = [] + if os.path.exists(path): + writtenFiles.append(path) + return writtenFiles + + @staticmethod + def score(uri:str)->int: + """ + Returns a score value for the give uri. E.g. 0 for unlikely/unknown, 20 for yes, probably thats the file format the reader can read. + + :param uri: str + :return: int + """ + return 0 + + @staticmethod + def addImportActions(spectralLibrary: SpectralLibrary, menu: QMenu) -> list: + + def read(speclib: SpectralLibrary): + + path, filter = QFileDialog.getOpenFileName(caption='Vector File', + filter='All type (*.*)') + if os.path.isfile(path) and VectorSourceSpectralLibraryIO.canRead(path): + sl = VectorSourceSpectralLibraryIO.readFrom(path) + if isinstance(sl, SpectralLibrary): + speclib.startEditing() + speclib.beginEditCommand('Add Spectral Library profiles from {}'.format(path)) + speclib.addSpeclib(sl, True) + speclib.endEditCommand() + speclib.commitChanges() + + m = menu.addAction('Vector Layer') + m.setToolTip('Adds profiles from another vector source\'s "{}" and "{}" attributes.'.format(FIELD_VALUES, FIELD_NAME)) + m.triggered.connect(lambda *args, sl=spectralLibrary: read(sl)) + + + @staticmethod + def addExportActions(spectralLibrary:SpectralLibrary, menu:QMenu) -> list: + + def write(speclib: SpectralLibrary): + # https://gdal.org/drivers/vector/index.html + LUT_Files = {'Geopackage (*.gpkg)': 'GPKG', + 'ESRI Shapefile (*.shp)' : 'ESRI Shapefile', + 'Keyhole Markup Language (*.kml)': 'KML', + 'Comma Separated Value (*.csv)': 'CSV'} + + path, filter = QFileDialog.getSaveFileName(caption='Write to Vector Layer', + filter=';;'.join(LUT_Files.keys())) + if isinstance(path, str) and len(path) > 0: + options = QgsVectorFileWriter.SaveVectorOptions() + options.fileEncoding = 'UTF-8' + + ogrType = LUT_Files.get(filter) + if isinstance(ogrType, str): + options.driverName = ogrType + if ogrType == 'GPKG': + pass + elif ogrType == 'ESRI Shapefile': + pass + elif ogrType == 'KML': + pass + elif ogrType == 'CSV': + pass + sl = VectorSourceSpectralLibraryIO.write(spectralLibrary, path, options=options) + + m = menu.addAction('Vector Source') + m.triggered.connect(lambda *args, sl=spectralLibrary: write(sl)) \ No newline at end of file diff --git a/eotimeseriesviewer/externals/qps/testing.py b/eotimeseriesviewer/externals/qps/testing.py index 19f0dc7dee6034eceba926b188228ffa17c25eda..c8c0ce6967530597cd6c947eb3fe4f0a14e40a28 100644 --- a/eotimeseriesviewer/externals/qps/testing.py +++ b/eotimeseriesviewer/externals/qps/testing.py @@ -1,4 +1,4 @@ -import os, sys, re, io, importlib, uuid, warnings, pathlib, time, site +import os, sys, re, io, importlib, uuid, warnings, pathlib, time, site, mock, inspect, types import sip from qgis.core import * from qgis.gui import * @@ -163,6 +163,7 @@ def initQgisApplication(*args, qgisResourceDir: str = None, loadPythonRunner = False if isinstance(QgsApplication.instance(), QgsApplication): + print('Found existing QgsApplication.instance()') return QgsApplication.instance() else: @@ -209,12 +210,14 @@ def initQgisApplication(*args, qgisResourceDir: str = None, if isinstance(resourceDir, str) and os.path.exists(resourceDir): qgisResourceDir = resourceDir + # try to find a directory "qgisresources" that contains python modules mit a qInitResources method if isinstance(qgisResourceDir, str) and os.path.isdir(qgisResourceDir): modules = [m for m in os.listdir(qgisResourceDir) if re.search(r'[^_].*\.py', m)] modules = [m[0:-3] for m in modules] for m in modules: mod = importlib.import_module('qgisresources.{}'.format(m)) if "qInitResources" in dir(mod): + print('Loads Qt resources from {}'.format(m)) mod.qInitResources() # initiate a PythonRunner instance if None exists @@ -255,7 +258,7 @@ def initQgisApplication(*args, qgisResourceDir: str = None, providers = QgsProviderRegistry.instance().providerList() potentialProviders = ['DB2', 'WFS', 'arcgisfeatureserver', 'arcgismapserver', 'delimitedtext', 'gdal', - 'geonode', 'gpx', 'mdal', 'memory', 'mesh_memory', 'mssql', 'ogr', 'oracle', 'ows', + 'geonode', 'gpx', 'mdal', 'memory', 'mesh_memory', 'mssql', 'ogr', 'ows', 'postgres', 'spatialite', 'virtual', 'wcs', 'wms'] missing = [p for p in potentialProviders if p not in providers] @@ -267,16 +270,19 @@ def initQgisApplication(*args, qgisResourceDir: str = None, class QgisMockup(QgisInterface): """ - A "fake" QGIS Desktop instance that should provide all the inferfaces a plugin developer might need (and nothing more) + A "fake" QGIS Desktop instance that should provide all the interfaces a plugin developer might need (and nothing more) """ - - def pluginManagerInterface(self) -> QgsPluginManagerInterface: - return self.mPluginManager - def __init__(self, *args): - # QgisInterface.__init__(self) super(QgisMockup, self).__init__() + #mock.MagicMock.__init__(self, spec=QgisInterface, name='QgisMockup') + + + #super(QgisMockup, self).__init__(spec=QgisInterface, name='QgisMockup') + #mock.MagicMock.__init__(self, spec=QgisInterface) + #QgisInterface.__init__(self) + + self.mCanvas = QgsMapCanvas() self.mCanvas.blockSignals(False) self.mCanvas.setCanvasColor(Qt.black) @@ -313,8 +319,30 @@ class QgisMockup(QgisInterface): self.mClipBoard = QgsClipboardMockup() + # mock other functions + excluded = QObject.__dict__.keys() + + self._mock = mock.Mock(spec=QgisInterface) + for n in self._mock._mock_methods: + assert isinstance(n, str) + if not n.startswith('_') and n not in excluded: + try: + inspect.getfullargspec(getattr(self, n)) + except: + setattr(self, n, getattr(self._mock, n)) + + + + def pluginManagerInterface(self) -> QgsPluginManagerInterface: + return self.mPluginManager + def activeLayer(self): - return None + return self.mapCanvas().currentLayer() + + def setActiveLayer(self, mapLayer:QgsMapLayer): + if mapLayer in self.mapCanvas().layers(): + self.mapCanvas().setCurrentLayer(mapLayer) + def cutSelectionToClipboard(self, mapLayer: QgsMapLayer): if isinstance(mapLayer, QgsVectorLayer): @@ -543,11 +571,22 @@ class TestObjects(): Create an in-memory ogr.DataSource :return: ogr.DataSource """ + ogr.UseExceptions() assert wkb in [ogr.wkbPoint, ogr.wkbPolygon, ogr.wkbLineString] + # find the QGIS world_map.shp pkgPath = QgsApplication.instance().pkgDataPath() - pathSrc = os.path.join(pkgPath, *['resources', 'data', 'world_map.shp']) - assert os.path.isfile(pathSrc), 'Unable to find QGIS "world_map.shp"' + pathSrc = None + potentialPathes = [ + os.path.join(os.path.dirname(__file__), 'testpolygons.geojson'), + os.path.join(pkgPath, *['resources', 'data', 'world_map.shp']), + ] + for p in potentialPathes: + if os.path.isfile(p): + pathSrc = p + break + + assert os.path.isfile(pathSrc), 'Unable to find QGIS "world_map.shp". QGIS Pkg path = {}'.format(pkgPath) dsSrc = ogr.Open(pathSrc) assert isinstance(dsSrc, ogr.DataSource) @@ -560,19 +599,19 @@ class TestObjects(): srs = lyrSrc.GetSpatialRef() assert isinstance(srs, osr.SpatialReference) - drv = dsSrc.GetDriver() + drv = ogr.GetDriverByName('ESRI Shapefile') assert isinstance(drv, ogr.Driver) # set temp path if wkb == ogr.wkbPolygon: lname = 'polygons' - pathDst = '/vsimem/tmp' + str(uuid.uuid4()) + '.world_map.polygons.shp' + pathDst = '/vsimem/tmp' + str(uuid.uuid4()) + '.test.polygons.shp' elif wkb == ogr.wkbPoint: lname = 'points' - pathDst = '/vsimem/tmp' + str(uuid.uuid4()) + '.world_map.centroids.shp' + pathDst = '/vsimem/tmp' + str(uuid.uuid4()) + '.test.centroids.shp' elif wkb == ogr.wkbLineString: lname = 'lines' - pathDst = '/vsimem/tmp' + str(uuid.uuid4()) + '.world_map.line.shp' + pathDst = '/vsimem/tmp' + str(uuid.uuid4()) + '.test.line.shp' else: raise NotImplementedError() @@ -612,7 +651,7 @@ class TestObjects(): for i in range(ldef.GetFieldCount()): fDst.SetField(i, fSrc.GetField(i)) - lyrDst.CreateFeature(fDst) + assert lyrDst.CreateFeature(fDst) == ogr.OGRERR_NONE assert isinstance(dsDst, ogr.DataSource) dsDst.FlushCache() diff --git a/eotimeseriesviewer/externals/qps/testpolygons.geojson b/eotimeseriesviewer/externals/qps/testpolygons.geojson new file mode 100644 index 0000000000000000000000000000000000000000..730b78f3025804487da43d503ccb21c127dee3d0 --- /dev/null +++ b/eotimeseriesviewer/externals/qps/testpolygons.geojson @@ -0,0 +1,158 @@ +{ +"type": "FeatureCollection", +"name": "testpolygons", +"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, +"features": [ +{ "type": "Feature", "properties": { "fid": 1, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297082085241408, 52.501857636615817 ], [ 13.297091916472423, 52.501855399052978 ], [ 13.297111122367605, 52.501862812730671 ], [ 13.297123131476843, 52.501854659724032 ], [ 13.297125401290463, 52.501846365631216 ], [ 13.297114173278231, 52.5018343095376 ], [ 13.297108374751774, 52.501833036535857 ], [ 13.297091162945314, 52.501824462922258 ], [ 13.297095243067778, 52.501819763501132 ], [ 13.297089629821974, 52.501813735014331 ], [ 13.297060362969674, 52.501814503275988 ], [ 13.297044410083084, 52.501823789944318 ], [ 13.297036159427256, 52.501835565657998 ], [ 13.297031757660644, 52.501848586312406 ], [ 13.297050870199671, 52.501858376832537 ], [ 13.297058387126265, 52.50186562226132 ], [ 13.297073833116501, 52.501869412310882 ], [ 13.297082085241408, 52.501857636615817 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 2, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298304702572262, 52.501824082425173 ], [ 13.298300435722354, 52.501822824320577 ], [ 13.29825068351229, 52.501822331659241 ], [ 13.298246260722161, 52.501823241788593 ], [ 13.298235832622826, 52.501840933950739 ], [ 13.298229437127349, 52.501855115506416 ], [ 13.298227029792844, 52.501866975354623 ], [ 13.298242293678461, 52.501875520781752 ], [ 13.298277036215257, 52.501884347291082 ], [ 13.298290811110379, 52.501880976817539 ], [ 13.298302820043775, 52.501872822790673 ], [ 13.2983165015893, 52.501871830945682 ], [ 13.298330506067664, 52.501862516082035 ], [ 13.298317282167101, 52.501851620933977 ], [ 13.298305961987545, 52.501841942704885 ], [ 13.298304702572262, 52.501824082425173 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 3, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.30011789363734, 52.50296980947023 ], [ 13.300084958990217, 52.502964578098634 ], [ 13.300072812496468, 52.502976298040394 ], [ 13.300056905682681, 52.502984396237082 ], [ 13.300036966322143, 52.50299600419978 ], [ 13.300034284195048, 52.503014996454972 ], [ 13.300035681956849, 52.503029290085308 ], [ 13.300052663936688, 52.503043808544668 ], [ 13.300069509850308, 52.50306189185936 ], [ 13.300096460828359, 52.50307060586924 ], [ 13.30011548431251, 52.503082773679566 ], [ 13.300140626181049, 52.503087893074024 ], [ 13.300181535806409, 52.503088480864932 ], [ 13.300201245858641, 52.503082816369421 ], [ 13.300224989912431, 52.503073642117229 ], [ 13.300229253296955, 52.50306418707504 ], [ 13.300225815517718, 52.503052243211627 ], [ 13.300234250508318, 52.503035711781585 ], [ 13.300248253454688, 52.503026397569819 ], [ 13.300250568580179, 52.503016915434721 ], [ 13.300233540676105, 52.503003585881267 ], [ 13.300210392892453, 52.502997306518672 ], [ 13.300204961040695, 52.502986523541374 ], [ 13.300199113699573, 52.502982156116161 ], [ 13.300193594321467, 52.502978033468104 ], [ 13.300176367183729, 52.502977200623455 ], [ 13.300158573834583, 52.502976340759389 ], [ 13.30011789363734, 52.50296980947023 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 4, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299496909753373, 52.502948989912838 ], [ 13.299477750305265, 52.502940388658601 ], [ 13.299461569568436, 52.502955619162442 ], [ 13.299435923101921, 52.502963576379294 ], [ 13.29942544783469, 52.502982457501119 ], [ 13.29944034521821, 52.503000513797666 ], [ 13.299424392425534, 52.50300979988711 ], [ 13.299437525163722, 52.503023072669706 ], [ 13.299464752798588, 52.503024653578208 ], [ 13.299486089904134, 52.503027339348705 ], [ 13.299507748229658, 52.503021702972347 ], [ 13.299518085773174, 52.503006389374463 ], [ 13.299518957607523, 52.502983801598894 ], [ 13.299515841242265, 52.502963535573194 ], [ 13.299496909753373, 52.502948989912838 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 5, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300367728312782, 52.503571915856064 ], [ 13.300336400444165, 52.503564208085116 ], [ 13.300337501155242, 52.503565390058228 ], [ 13.300367728312782, 52.503571915856064 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 6, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300203039686897, 52.503531395592923 ], [ 13.300139368274001, 52.503515728892388 ], [ 13.300139487031698, 52.50351582051082 ], [ 13.300143339562354, 52.503523468940621 ], [ 13.300192831547587, 52.503534154884534 ], [ 13.300199237270173, 52.503533331614861 ], [ 13.300203039686897, 52.503531395592923 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 7, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297801449808738, 52.502914829827432 ], [ 13.297799960907115, 52.502902913929717 ], [ 13.297784099678516, 52.502909822042447 ], [ 13.297772595611489, 52.502904899268792 ], [ 13.297753298754193, 52.502899863469182 ], [ 13.29774122380152, 52.502909220414637 ], [ 13.297735213697502, 52.502913876802737 ], [ 13.297722784590649, 52.502921112119047 ], [ 13.297721256199086, 52.502922001836495 ], [ 13.297713286114993, 52.502929260850792 ], [ 13.297878326254931, 52.502974029025758 ], [ 13.29787178375668, 52.502961041497464 ], [ 13.297868302310833, 52.502956918389067 ], [ 13.297860599721353, 52.502947796574958 ], [ 13.297853036548389, 52.502941740072316 ], [ 13.297848740251519, 52.50293899168458 ], [ 13.297835917505157, 52.502930788831748 ], [ 13.297820654811188, 52.502922243372403 ], [ 13.297801449808738, 52.502914829827432 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 8, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299268435465493, 52.502919537657483 ], [ 13.299259291015593, 52.502903943115442 ], [ 13.299241988052058, 52.502897747585216 ], [ 13.299228121151176, 52.502903495023133 ], [ 13.29922761633488, 52.502916571776225 ], [ 13.299210129821578, 52.502915130852429 ], [ 13.299196815071014, 52.502906613576044 ], [ 13.299184437403193, 52.502924277790669 ], [ 13.299197386457887, 52.502942306108856 ], [ 13.299187370061349, 52.502949298434892 ], [ 13.299214368160538, 52.502956823787535 ], [ 13.299228004164208, 52.502957019818787 ], [ 13.299229355767178, 52.502972502333954 ], [ 13.299251105887386, 52.502964488249724 ], [ 13.299255415329212, 52.502953845265317 ], [ 13.299271458452752, 52.502942181422618 ], [ 13.299272055045272, 52.502926726914389 ], [ 13.299268435465493, 52.502919537657483 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 9, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300322648459646, 52.503478334186376 ], [ 13.300340364568573, 52.503473830557795 ], [ 13.300363696014211, 52.50347535618198 ], [ 13.300420365382793, 52.503486183804988 ], [ 13.300461966681835, 52.503416945815175 ], [ 13.30043635043085, 52.503410978997806 ], [ 13.300414554469276, 52.503420181278763 ], [ 13.300333558684903, 52.503397606912046 ], [ 13.300340137171508, 52.503378670628877 ], [ 13.300407862218799, 52.503391538050352 ], [ 13.300437658314099, 52.503326546058133 ], [ 13.300333147868345, 52.50330720159554 ], [ 13.300354004541257, 52.503271817820327 ], [ 13.300285762863499, 52.503256153871064 ], [ 13.300281302407939, 52.503260176292422 ], [ 13.300279808096045, 52.503269502084805 ], [ 13.300277536846481, 52.503272118264007 ], [ 13.300277236330677, 52.503279907526682 ], [ 13.300267034854, 52.503284214307293 ], [ 13.300263488433291, 52.503288299311713 ], [ 13.300241921841749, 52.503291557171316 ], [ 13.300207086020672, 52.503285109882789 ], [ 13.300205159301134, 52.503273488035965 ], [ 13.300199678661006, 52.503269276932187 ], [ 13.3001939257241, 52.503266815201663 ], [ 13.300174821722688, 52.503258641042365 ], [ 13.30016917792415, 52.503257207674814 ], [ 13.300167346822031, 52.503255442465743 ], [ 13.30016516248722, 52.503254507464703 ], [ 13.300143871099179, 52.503250632937601 ], [ 13.30012978277972, 52.503251656015436 ], [ 13.300114434843467, 52.503267186291801 ], [ 13.300095503026551, 52.503252640725172 ], [ 13.300105930935546, 52.503238914425197 ], [ 13.300103511759183, 52.503235779496215 ], [ 13.300109314838263, 52.503234459349834 ], [ 13.300145010137447, 52.503187468973394 ], [ 13.300163347203778, 52.503191423333313 ], [ 13.30016325756813, 52.503191378887628 ], [ 13.300154351627119, 52.503186815563041 ], [ 13.300140989301433, 52.50317948634919 ], [ 13.300127949519938, 52.503163835908154 ], [ 13.300115047328957, 52.503144619730527 ], [ 13.300111838935299, 52.503126731474445 ], [ 13.300124168947953, 52.503110256017521 ], [ 13.300147821290274, 52.503103459537016 ], [ 13.300161137758318, 52.503111976729315 ], [ 13.300179083051395, 52.50310152963231 ], [ 13.300218411425007, 52.503092578394885 ], [ 13.300232048950374, 52.503092774331911 ], [ 13.300259230792907, 52.503095543936155 ], [ 13.30026475289937, 52.503103950032425 ], [ 13.300285311892228, 52.503126844809913 ], [ 13.300284990820517, 52.503135166956156 ], [ 13.300284394613405, 52.503150620573351 ], [ 13.300283752503598, 52.503167263967114 ], [ 13.300277219859513, 52.503185012268524 ], [ 13.300280474191734, 52.503201711642468 ], [ 13.300268748790081, 52.503214126380456 ], [ 13.300276801218585, 52.503215874873916 ], [ 13.300280398871129, 52.503216648555579 ], [ 13.300339077537721, 52.503100033116141 ], [ 13.30033715362787, 52.503096663443678 ], [ 13.300306535448623, 52.503081950017233 ], [ 13.300299201213145, 52.503069950176389 ], [ 13.300332640456993, 52.503062104728905 ], [ 13.300346826853746, 52.503048034993071 ], [ 13.300362871233821, 52.503036370122892 ], [ 13.30036527659278, 52.503024510208043 ], [ 13.30036550588332, 52.503018566716079 ], [ 13.300378620013797, 52.503021444387876 ], [ 13.300378701424503, 52.503021281917427 ], [ 13.300394959839803, 52.50301426587577 ], [ 13.300007723172229, 52.502929202793943 ], [ 13.299721672529332, 52.50286636149842 ], [ 13.299353627343386, 52.502785507525125 ], [ 13.298995256896209, 52.502706773163759 ], [ 13.298636050152107, 52.502627865637535 ], [ 13.298281359511597, 52.502549939075223 ], [ 13.297917229390796, 52.502469936229744 ], [ 13.297556234233978, 52.502390624734332 ], [ 13.297198311381784, 52.502311985733733 ], [ 13.29726565624031, 52.502197306427327 ], [ 13.297361150311481, 52.502217791626904 ], [ 13.297365297588037, 52.502212536606969 ], [ 13.297406112584852, 52.502221232278146 ], [ 13.297409050398517, 52.502216315902892 ], [ 13.297452427392662, 52.502225054722608 ], [ 13.29744947160925, 52.502230474348302 ], [ 13.297491050530574, 52.502239342825483 ], [ 13.29749700132929, 52.502247124041808 ], [ 13.297522071869377, 52.502252162949915 ], [ 13.297543847949536, 52.50224667878112 ], [ 13.297586017619645, 52.502178336482629 ], [ 13.297588541630287, 52.502110575452292 ], [ 13.297574745438462, 52.502098441088485 ], [ 13.297544780729794, 52.502097971229006 ], [ 13.297536502057918, 52.50210437342384 ], [ 13.297488206643054, 52.502103386210848 ], [ 13.297488437512495, 52.502109955817005 ], [ 13.297445561759574, 52.502109180548665 ], [ 13.297445080209915, 52.502102502137618 ], [ 13.297399377388366, 52.502101566584599 ], [ 13.297399797387142, 52.502094701542944 ], [ 13.297299400581686, 52.502093955254715 ], [ 13.297297556271626, 52.502135120161604 ], [ 13.297310054005161, 52.502130908723984 ], [ 13.29733542324743, 52.502130084304738 ], [ 13.297356623420709, 52.502136337105405 ], [ 13.297378143736704, 52.502134267745994 ], [ 13.297383481432673, 52.502147428579512 ], [ 13.297378851486792, 52.502166393650157 ], [ 13.297376443904822, 52.502178253478384 ], [ 13.297370048082296, 52.502192434984096 ], [ 13.297352332198168, 52.502196938170542 ], [ 13.297321254756232, 52.502194111827414 ], [ 13.297307710894422, 52.502191537825688 ], [ 13.297294257454693, 52.502186586048467 ], [ 13.297271018635486, 52.502182683869833 ], [ 13.297271615913159, 52.502167229375644 ], [ 13.297276017677797, 52.502154207814023 ], [ 13.297286354309653, 52.502138894392708 ], [ 13.297292247885519, 52.502136908539029 ], [ 13.297130109722676, 52.502135325632523 ], [ 13.297130540579346, 52.502111026782714 ], [ 13.2970583252705, 52.502110799214691 ], [ 13.29705964697945, 52.502085828956687 ], [ 13.297020521011607, 52.502085585842558 ], [ 13.297021713456596, 52.502069754193023 ], [ 13.297022122726352, 52.502064273645054 ], [ 13.296920850309574, 52.502062473248102 ], [ 13.296920435570081, 52.502066722819748 ], [ 13.296875115688696, 52.502065868102655 ], [ 13.296875530168196, 52.502061663483488 ], [ 13.296764317734166, 52.502059691983135 ], [ 13.296764625581462, 52.502063772132992 ], [ 13.296725871979923, 52.502063044260993 ], [ 13.29672569681709, 52.50205900018787 ], [ 13.296645568568685, 52.502057581295041 ], [ 13.296636626085288, 52.502073794974706 ], [ 13.296655310029513, 52.50207817027232 ], [ 13.296642672724557, 52.502100052764298 ], [ 13.296646289015678, 52.50210083581716 ], [ 13.296630853793987, 52.50212671957501 ], [ 13.296627590109047, 52.502126152892124 ], [ 13.296596601692183, 52.502179788875125 ], [ 13.296450508254486, 52.502147685197535 ], [ 13.296548084496953, 52.501982874106361 ], [ 13.297027663568374, 52.501990299060303 ], [ 13.297303850998823, 52.50199458186831 ], [ 13.297687707321044, 52.502000532022635 ], [ 13.298071771403178, 52.502006483919175 ], [ 13.298454087749036, 52.502012408531783 ], [ 13.298833060263393, 52.502018279329903 ], [ 13.299219197970963, 52.502024260013208 ], [ 13.299601174587623, 52.502030175132532 ], [ 13.299921958137718, 52.502035141343576 ], [ 13.300360432799872, 52.502041920730491 ], [ 13.30035928141019, 52.502025821610133 ], [ 13.300358886746581, 52.50202608387827 ], [ 13.30033931478216, 52.502028181770292 ], [ 13.300319834545235, 52.502027901902551 ], [ 13.300280874072286, 52.502027342157412 ], [ 13.300267603769877, 52.502017636101414 ], [ 13.300252845439804, 52.501996013292519 ], [ 13.300241477455238, 52.501987524102042 ], [ 13.300233181750293, 52.502000488923585 ], [ 13.300217825729995, 52.501994320607693 ], [ 13.300194816079488, 52.50198447461085 ], [ 13.300197589834895, 52.501963103692646 ], [ 13.300223281448226, 52.501953957425087 ], [ 13.300215672247225, 52.501949089949434 ], [ 13.300201715474644, 52.501957216182198 ], [ 13.30019211224012, 52.501953509592312 ], [ 13.300184825557508, 52.501940320886966 ], [ 13.300181708948406, 52.501920054873402 ], [ 13.300159913679398, 52.501929257115449 ], [ 13.300144603528683, 52.501921900810721 ], [ 13.300135185256917, 52.501913438724657 ], [ 13.30012214433477, 52.501897789160388 ], [ 13.300120792442199, 52.50188230665033 ], [ 13.300134979990105, 52.501868236955737 ], [ 13.300165965697593, 52.501873439417281 ], [ 13.300183222492386, 52.50188082458795 ], [ 13.300180723945084, 52.501895062236748 ], [ 13.300192044553944, 52.501904740289923 ], [ 13.300217185762557, 52.501909859663478 ], [ 13.300215881176046, 52.501893188297153 ], [ 13.300220005368727, 52.501887299866127 ], [ 13.30024936393, 52.501884153964859 ], [ 13.300260777755881, 52.501891454276262 ], [ 13.300289997283796, 52.501891874079952 ], [ 13.300274688596344, 52.501884517813068 ], [ 13.300284704611116, 52.50187752449164 ], [ 13.300306087080786, 52.501879021232178 ], [ 13.300310258597285, 52.501871943940735 ], [ 13.300319676894309, 52.501880406013115 ], [ 13.300346720440219, 52.501886741327802 ], [ 13.300349385745676, 52.501887695823669 ], [ 13.30034618559675, 52.501843049844503 ], [ 13.298300435722354, 52.501822824320577 ], [ 13.298304702572262, 52.501824082425173 ], [ 13.298305961987545, 52.501841942704885 ], [ 13.298317282167101, 52.501851620933977 ], [ 13.298330506067664, 52.501862516082035 ], [ 13.2983165015893, 52.501871830945682 ], [ 13.298302820043775, 52.501872822790673 ], [ 13.298290811110379, 52.501880976817539 ], [ 13.298277036215257, 52.501884347291082 ], [ 13.298242293678461, 52.501875520781752 ], [ 13.298227029792844, 52.501866975354623 ], [ 13.298229437127349, 52.501855115506416 ], [ 13.298235832622826, 52.501840933950739 ], [ 13.298246260722161, 52.501823241788593 ], [ 13.29825068351229, 52.501822331659241 ], [ 13.29743704397443, 52.501814276504007 ], [ 13.297438465929279, 52.501815187098003 ], [ 13.297441627210452, 52.501834264296839 ], [ 13.297452671546331, 52.501851075866341 ], [ 13.297465298027753, 52.501877425605784 ], [ 13.297464838648498, 52.50188931347266 ], [ 13.297470223721561, 52.501901286345998 ], [ 13.297483308129738, 52.501915748195472 ], [ 13.297476682710419, 52.501935873192295 ], [ 13.297459104765515, 52.501936809766306 ], [ 13.297441343051405, 52.501942501843779 ], [ 13.297431234725661, 52.501951871774878 ], [ 13.297411754551828, 52.501951591428686 ], [ 13.2973902343267, 52.501953660794996 ], [ 13.29735131992318, 52.501951911209474 ], [ 13.297344032622705, 52.50193872231516 ], [ 13.297328768892342, 52.501930176773548 ], [ 13.297315179165347, 52.501928791649689 ], [ 13.29730761623993, 52.501922735113325 ], [ 13.297310023824863, 52.501910875286072 ], [ 13.297316189931204, 52.501902637266433 ], [ 13.297334456969747, 52.501883869363787 ], [ 13.297371424623281, 52.501885590938095 ], [ 13.297358292844068, 52.501872317931841 ], [ 13.297340806811899, 52.501870876735502 ], [ 13.297330928206764, 52.501874302275731 ], [ 13.297315344390455, 52.50187407799028 ], [ 13.297306018533847, 52.501863238829309 ], [ 13.297296874992764, 52.501847644140661 ], [ 13.297297472254149, 52.501832189645391 ], [ 13.297325020646893, 52.501825449803107 ], [ 13.297346494867586, 52.501824569324597 ], [ 13.297357998606712, 52.501829492137723 ], [ 13.297366342536304, 52.501815338669033 ], [ 13.297369737639228, 52.50181360996762 ], [ 13.296853966483106, 52.501808501196777 ], [ 13.296865421073189, 52.50181526565067 ], [ 13.296867001606602, 52.501824803807402 ], [ 13.296866450151898, 52.501839069423049 ], [ 13.296848595014461, 52.501847139142519 ], [ 13.296828701301019, 52.50185755768765 ], [ 13.296805232936981, 52.501859598901277 ], [ 13.296785660890892, 52.501861696204664 ], [ 13.296754170114626, 52.501869568701196 ], [ 13.29674688447929, 52.501856379792791 ], [ 13.296729534886559, 52.501851371856795 ], [ 13.296720393025458, 52.501835777146511 ], [ 13.296705129453141, 52.501827231525581 ], [ 13.296707537194175, 52.501815371711743 ], [ 13.296723719919243, 52.501800141607475 ], [ 13.296735821003768, 52.501789610888267 ], [ 13.296765409564136, 52.501780520573874 ], [ 13.296788500233259, 52.501784399141805 ], [ 13.29670838482547, 52.501708954895442 ], [ 13.296528153917547, 52.501982543971565 ], [ 13.296426686459958, 52.502160196013769 ], [ 13.296370418791428, 52.502280410217644 ], [ 13.296371716986899, 52.502280748999169 ], [ 13.296382992349171, 52.502291616307012 ], [ 13.296396949888431, 52.502283491430717 ], [ 13.296412671753464, 52.502280149210819 ], [ 13.29641189032492, 52.50230035830247 ], [ 13.296413240922607, 52.50231584084289 ], [ 13.296399053521485, 52.50232991010003 ], [ 13.296393071067362, 52.502333392566115 ], [ 13.296373268934198, 52.502341434178135 ], [ 13.296357455135016, 52.502347153245452 ], [ 13.296350030154077, 52.502337530920038 ], [ 13.296345918565404, 52.502332753121813 ], [ 13.296330914055686, 52.502364828100518 ], [ 13.296246096747051, 52.502468990892758 ], [ 13.29627726769368, 52.502469440687726 ], [ 13.296281945594428, 52.502449286810908 ], [ 13.296286163749599, 52.502441021690871 ], [ 13.296286899300439, 52.502422000578129 ], [ 13.296296960531159, 52.502413819603071 ], [ 13.296322837126963, 52.502399917790775 ], [ 13.296325612754558, 52.502378547879644 ], [ 13.296362672720043, 52.502377892020668 ], [ 13.296390024235373, 52.502377589966251 ], [ 13.296390089097013, 52.502377589102025 ], [ 13.296448577182723, 52.502390208011022 ], [ 13.296762254293377, 52.502457863404167 ], [ 13.296752854843126, 52.502455140409594 ], [ 13.296747286020024, 52.502447923908349 ], [ 13.296724093111415, 52.502442831847077 ], [ 13.296716807394267, 52.502429642937336 ], [ 13.296705485951637, 52.502419964536507 ], [ 13.296721621469368, 52.502405923290695 ], [ 13.29672987382636, 52.502394146724271 ], [ 13.296738264029637, 52.502378804427778 ], [ 13.296744568159877, 52.502367000710677 ], [ 13.296767852953256, 52.502369715015583 ], [ 13.296777592393003, 52.502369855232295 ], [ 13.2967857987329, 52.502359268436301 ], [ 13.296792332649147, 52.502341520336259 ], [ 13.296821506463319, 52.502343129874937 ], [ 13.296856342497064, 52.502349578171987 ], [ 13.296871696686933, 52.502355746899681 ], [ 13.296883155989237, 52.502361859555229 ], [ 13.296908343048923, 52.502365789855801 ], [ 13.296903711401274, 52.502384754884076 ], [ 13.296903068032483, 52.502401398250825 ], [ 13.29690251657798, 52.502415663865371 ], [ 13.296902286790662, 52.502421608245832 ], [ 13.296909298325783, 52.502441929523776 ], [ 13.296916492188481, 52.502457496174351 ], [ 13.296894879778979, 52.502461943200309 ], [ 13.296879203842076, 52.502464096607852 ], [ 13.296855827074006, 52.502463760076473 ], [ 13.296834260614959, 52.502467018215249 ], [ 13.296827003258372, 52.502471846316567 ], [ 13.297103425457369, 52.502531466684623 ], [ 13.297065618216461, 52.502598871784777 ], [ 13.297024663192014, 52.50259947089809 ], [ 13.297017427805418, 52.502595459165505 ], [ 13.297020179261439, 52.502613357121326 ], [ 13.29703349497084, 52.502621874660001 ], [ 13.297038878573398, 52.50263384663149 ], [ 13.297054280313837, 52.502638826481359 ], [ 13.297070094164976, 52.502633106422685 ], [ 13.297091706607153, 52.502628660264008 ], [ 13.297109055108514, 52.502633667229382 ], [ 13.297100758360768, 52.50264663182103 ], [ 13.297096494349875, 52.502656086745723 ], [ 13.297101237663517, 52.502667204323807 ], [ 13.297108378641774, 52.502662528280389 ], [ 13.297116998492616, 52.502641242474802 ], [ 13.297115601687999, 52.502626948817223 ], [ 13.29710042967212, 52.502616025493069 ], [ 13.297087115458654, 52.50260750708442 ], [ 13.29713047361164, 52.502537300192003 ], [ 13.297452877950789, 52.502606828147584 ], [ 13.29749072899123, 52.502614987514761 ], [ 13.297534396914777, 52.502624405728263 ], [ 13.297540663963588, 52.502625758279294 ], [ 13.297530466273709, 52.502609069496849 ], [ 13.297533379188515, 52.502584132025262 ], [ 13.29754357804921, 52.502572384313353 ], [ 13.297547979742147, 52.502559363640749 ], [ 13.297562166855061, 52.502545294243902 ], [ 13.297576217612994, 52.502534791495862 ], [ 13.297580619292305, 52.502521770821836 ], [ 13.297575371900814, 52.50250623222297 ], [ 13.297540305966423, 52.502505727619592 ], [ 13.297530244942802, 52.502513908702042 ], [ 13.29750909197748, 52.502506467075925 ], [ 13.297507465342498, 52.502498117803839 ], [ 13.297494057728619, 52.502491977172191 ], [ 13.297482324127744, 52.502492997854759 ], [ 13.297462797771733, 52.502493906391422 ], [ 13.297449160451311, 52.502493710136825 ], [ 13.297429771924511, 52.502491052038778 ], [ 13.297428283135236, 52.502479136136536 ], [ 13.297431150119603, 52.502455388442414 ], [ 13.297453038095219, 52.502443808958034 ], [ 13.297459250194317, 52.50243438205564 ], [ 13.297434108986876, 52.502429262995221 ], [ 13.297430809842519, 52.502413752426932 ], [ 13.297442728667397, 52.502407976264749 ], [ 13.297472315996885, 52.502398886654142 ], [ 13.297477701165809, 52.502410858628103 ], [ 13.297469403096848, 52.502423823226167 ], [ 13.297500526652248, 52.502425460655338 ], [ 13.297502796392118, 52.50241716745429 ], [ 13.297529977667631, 52.502419936782296 ], [ 13.297529518262108, 52.502431825547099 ], [ 13.297541435606648, 52.502426049353737 ], [ 13.297555486330758, 52.502415546607665 ], [ 13.297574874832488, 52.502418204684929 ], [ 13.297594217432541, 52.502422050736939 ], [ 13.297611383596299, 52.502431814054468 ], [ 13.297614956962708, 52.502440191337875 ], [ 13.297620709645754, 52.502442653192034 ], [ 13.297641954475084, 52.502447717045122 ], [ 13.297657126569801, 52.502458640300787 ], [ 13.297646696606288, 52.502476332384653 ], [ 13.297621233854642, 52.502479533702164 ], [ 13.297611171344693, 52.50248771566914 ], [ 13.297608719339649, 52.502500764399571 ], [ 13.297609885136051, 52.502521002414291 ], [ 13.297629550788217, 52.502516527245085 ], [ 13.297662713007501, 52.502515814891382 ], [ 13.297668236052916, 52.502524220227365 ], [ 13.29766773074595, 52.502537297869345 ], [ 13.297678914621445, 52.502550542809573 ], [ 13.297690097031062, 52.502563787727567 ], [ 13.297691539957128, 52.502576892503527 ], [ 13.297668117182326, 52.502577745008672 ], [ 13.297665985275966, 52.50258247158326 ], [ 13.297675220934007, 52.502595689389487 ], [ 13.297688259674455, 52.502611340093523 ], [ 13.29767425632765, 52.502620653999401 ], [ 13.297668136156132, 52.502627702261286 ], [ 13.297652919536443, 52.502617967904563 ], [ 13.297635339849634, 52.502618904481174 ], [ 13.297628945515497, 52.502633086021213 ], [ 13.297624681653623, 52.502642540067299 ], [ 13.297624878654307, 52.502643923952427 ], [ 13.297627116871205, 52.502644415610355 ], [ 13.297789327581443, 52.502679405720343 ], [ 13.297760957743066, 52.50272306705935 ], [ 13.297727261738949, 52.502788002289805 ], [ 13.297699391244198, 52.502803064355696 ], [ 13.297664004981042, 52.502810881943645 ], [ 13.297686446190351, 52.502764815587639 ], [ 13.297435262157903, 52.502707675438984 ], [ 13.297416121908769, 52.502749031104102 ], [ 13.297381383808954, 52.502739428468139 ], [ 13.297379277940205, 52.50275293533236 ], [ 13.297357206041729, 52.502769269407644 ], [ 13.297346959631616, 52.502782206855997 ], [ 13.297319226853169, 52.502793702202382 ], [ 13.297317632137176, 52.502794630521407 ], [ 13.297317273075342, 52.502795190901104 ], [ 13.297506779447504, 52.50284430291876 ], [ 13.297573156820615, 52.502862683075833 ], [ 13.297581823333013, 52.502858144043977 ], [ 13.29758966209217, 52.502857067305015 ], [ 13.297606826984353, 52.502866829704168 ], [ 13.297624085199516, 52.502874215267802 ], [ 13.297626606606833, 52.502877482991551 ], [ 13.29774122380152, 52.502909220414637 ], [ 13.297753298754193, 52.502899863469182 ], [ 13.297772595611489, 52.502904899268792 ], [ 13.297784099678516, 52.502909822042447 ], [ 13.297799960907115, 52.502902913929717 ], [ 13.297801449808738, 52.502914829827432 ], [ 13.297820654811188, 52.502922243372403 ], [ 13.297835917505157, 52.502930788831748 ], [ 13.297848740251519, 52.50293899168458 ], [ 13.297973486531472, 52.502973533314787 ], [ 13.29813001468858, 52.503011185312445 ], [ 13.298166510853891, 52.502945457665874 ], [ 13.297906428081555, 52.502888300214003 ], [ 13.297978823667604, 52.502765702692116 ], [ 13.297902582082855, 52.502891630072611 ], [ 13.297889128362755, 52.502886678361236 ], [ 13.297863803151897, 52.502886313997699 ], [ 13.297840609879618, 52.502881222152617 ], [ 13.297817600312227, 52.502871375695442 ], [ 13.29781638700865, 52.502852326538395 ], [ 13.297820698256846, 52.50284168363013 ], [ 13.297884525303438, 52.502854496422941 ], [ 13.297941898875646, 52.502731617159448 ], [ 13.297883686928461, 52.502724832870193 ], [ 13.297897934417945, 52.502702830456123 ], [ 13.29800261507798, 52.502725409179668 ], [ 13.298003145486831, 52.502725523805736 ], [ 13.298536596754278, 52.502840570064784 ], [ 13.298541566464644, 52.502841638663519 ], [ 13.298536713731806, 52.502838838241473 ], [ 13.298517554522157, 52.502830236833546 ], [ 13.29850641627316, 52.502815802194867 ], [ 13.298494958063317, 52.502809690613923 ], [ 13.298487808028758, 52.502792935156812 ], [ 13.298500002276301, 52.502780026524796 ], [ 13.29850621269442, 52.502770600443867 ], [ 13.298525924285167, 52.50276493624898 ], [ 13.29853027832249, 52.50275310439379 ], [ 13.29854632303401, 52.502741440671194 ], [ 13.29855438984479, 52.502734419461518 ], [ 13.298555170337792, 52.502714209450275 ], [ 13.298549694485303, 52.502704615298491 ], [ 13.29856369767648, 52.502695301287744 ], [ 13.298560489942259, 52.502677412993961 ], [ 13.298565029085797, 52.50266082565004 ], [ 13.29860041651194, 52.502653008709807 ], [ 13.298608529209483, 52.502644798619038 ], [ 13.298616092437362, 52.502650855074101 ], [ 13.298653059320422, 52.502652576232393 ], [ 13.298672357683481, 52.502657611007898 ], [ 13.298691562724558, 52.50266502441211 ], [ 13.298712853697046, 52.502668899197516 ], [ 13.298716520582341, 52.502674898716116 ], [ 13.298739576126678, 52.502683557021086 ], [ 13.298768659941288, 52.502687542958917 ], [ 13.298766160885835, 52.502701780571677 ], [ 13.298777160032488, 52.502719780898438 ], [ 13.298768863756171, 52.502732745610956 ], [ 13.298756990921284, 52.502737333029977 ], [ 13.298756531876212, 52.502749220903091 ], [ 13.298757929216638, 52.502763514545549 ], [ 13.298736500468779, 52.502763206399365 ], [ 13.298697446086004, 52.502765023860618 ], [ 13.298695132113977, 52.502774505984128 ], [ 13.29870264799367, 52.502781751289774 ], [ 13.298718463160109, 52.502776031030699 ], [ 13.298735765966162, 52.502782227535192 ], [ 13.298741335158155, 52.502789443945581 ], [ 13.298719309614688, 52.502804590300485 ], [ 13.298693525342214, 52.502816114886208 ], [ 13.298675440616309, 52.502830128366512 ], [ 13.298669184314983, 52.502840743333543 ], [ 13.298657036010253, 52.502852463106059 ], [ 13.298619885312034, 52.502855497455961 ], [ 13.298602312060508, 52.502854739425842 ], [ 13.298620965754722, 52.502858766913484 ], [ 13.298954472906155, 52.502932094552094 ], [ 13.298953278307765, 52.502934337766369 ], [ 13.299002678304745, 52.502945168549985 ], [ 13.298991494257868, 52.502964008873661 ], [ 13.298942092735471, 52.502953178962741 ], [ 13.298869460791005, 52.503076273569462 ], [ 13.29891915505412, 52.503087194933741 ], [ 13.298907970899567, 52.503106036147095 ], [ 13.298858276618182, 52.503095114778006 ], [ 13.298831580669235, 52.503140243524669 ], [ 13.298219224799769, 52.503005634365771 ], [ 13.298205318352515, 52.503029298803341 ], [ 13.298467881686213, 52.503092455136489 ], [ 13.298555194399452, 52.503114329513018 ], [ 13.298555898179233, 52.50311361764021 ], [ 13.298585119964198, 52.503114037892821 ], [ 13.298593280102297, 52.503104639847251 ], [ 13.298600750142084, 52.503113073137683 ], [ 13.298618008622697, 52.50312045855825 ], [ 13.298642873423383, 52.503132710611993 ], [ 13.298654700466047, 52.50312931208272 ], [ 13.298662952414579, 52.503117536278204 ], [ 13.298682387205814, 52.503119005299766 ], [ 13.298699690140277, 52.503125201810342 ], [ 13.298712639087279, 52.503143229281221 ], [ 13.298707567524724, 52.503152504513039 ], [ 13.298800314841808, 52.503175739904002 ], [ 13.298995012436151, 52.503221108291932 ], [ 13.299016515337867, 52.503218967351096 ], [ 13.29903817381628, 52.503213331062177 ], [ 13.299071108517758, 52.503218562725465 ], [ 13.299101957341021, 52.503227332103421 ], [ 13.299103307433196, 52.503242814597883 ], [ 13.299102826353641, 52.503246230635348 ], [ 13.299499353695619, 52.50333862767426 ], [ 13.299501176513742, 52.503337743964238 ], [ 13.29952686907636, 52.503328597857958 ], [ 13.299548527545854, 52.503322961475597 ], [ 13.299573898916673, 52.503322136603131 ], [ 13.299595557377151, 52.503316500212094 ], [ 13.299636283662359, 52.503321843703588 ], [ 13.299657437383985, 52.503329284954788 ], [ 13.29966481736979, 52.503340095956148 ], [ 13.29967387028247, 52.503358068222965 ], [ 13.299677260576896, 52.503371200957218 ], [ 13.299676920902051, 52.503380002066315 ], [ 13.299859355484537, 52.503422511594799 ], [ 13.299877937928366, 52.503424041898079 ], [ 13.299901315242494, 52.503424377836737 ], [ 13.299930399621582, 52.503428364392079 ], [ 13.299957168794208, 52.503441833068436 ], [ 13.299956367295161, 52.503445115930262 ], [ 13.299973275832574, 52.503449055186131 ], [ 13.299990377528767, 52.503439932088803 ], [ 13.300000439822934, 52.503431749918811 ], [ 13.30000410691094, 52.503437750297408 ], [ 13.300032732596231, 52.503453624707213 ], [ 13.300061540325522, 52.50346474357773 ], [ 13.300063305064013, 52.503469527082878 ], [ 13.300098234226903, 52.503473596680003 ], [ 13.300105843674238, 52.503478464164118 ], [ 13.300107235655732, 52.503480268919759 ], [ 13.300280085848797, 52.503520543330744 ], [ 13.30031063797842, 52.503486488394401 ], [ 13.300322648459646, 52.503478334186376 ] ], [ [ 13.300156715960265, 52.503434319084967 ], [ 13.300179796292106, 52.50339183006767 ], [ 13.299111531443387, 52.503142151251218 ], [ 13.29909461467466, 52.503176402819733 ], [ 13.299083156289239, 52.50317029129463 ], [ 13.299073783861967, 52.503160640247962 ], [ 13.299074426428586, 52.50314399686458 ], [ 13.299090974530799, 52.50311925629611 ], [ 13.299105438172401, 52.503098054368735 ], [ 13.299117263698482, 52.503094655771626 ], [ 13.299130945642576, 52.503093662937069 ], [ 13.299142589045539, 52.503095019867921 ], [ 13.299121869153087, 52.503126837689699 ], [ 13.300186006476713, 52.503382402998561 ], [ 13.300212753861276, 52.50334591434715 ], [ 13.300231821891762, 52.503356893240415 ], [ 13.300231179757319, 52.503373536633177 ], [ 13.300226871979623, 52.503384179675159 ], [ 13.300208420539608, 52.503407704414208 ], [ 13.300201933697501, 52.503424263832258 ], [ 13.300180093283187, 52.503434654968579 ], [ 13.300156715960265, 52.503434319084967 ] ], [ [ 13.29951180906164, 52.503124116381855 ], [ 13.299519820819727, 52.503108899726733 ], [ 13.299504141945443, 52.503109408947999 ], [ 13.299502245116942, 52.503102819898395 ], [ 13.299497110426005, 52.503095297775516 ], [ 13.299484208565829, 52.503076080632759 ], [ 13.299479487396781, 52.503059677540435 ], [ 13.299142777300958, 52.502989160056572 ], [ 13.29912178210331, 52.503028110244763 ], [ 13.299065699911921, 52.503016599011282 ], [ 13.299045326682823, 52.502988948539205 ], [ 13.29907367540309, 52.502956415018822 ], [ 13.299493752797648, 52.503047003577677 ], [ 13.299506612074726, 52.503049764393992 ], [ 13.299512583369259, 52.503047941388687 ], [ 13.299513042215331, 52.503036053511522 ], [ 13.299525601862806, 52.503013633730447 ], [ 13.299545357815676, 52.503006781365578 ], [ 13.299587666441802, 52.503021663017236 ], [ 13.29960655063358, 52.503037397519861 ], [ 13.299629560694587, 52.503047243633013 ], [ 13.299634854757624, 52.50306159326842 ], [ 13.299634304174287, 52.503075858901568 ], [ 13.299632691299175, 52.503076953327984 ], [ 13.299630412629483, 52.503078501232537 ], [ 13.299627593700899, 52.503085037787713 ], [ 13.299976412266465, 52.503162578278406 ], [ 13.299982876392415, 52.503160545643048 ], [ 13.299983881729995, 52.503159387635506 ], [ 13.299985584455635, 52.50315672822672 ], [ 13.299987374289158, 52.503153453272652 ], [ 13.299987646316145, 52.503153505734069 ], [ 13.299992179241157, 52.503146421958903 ], [ 13.299990643864623, 52.503135694961983 ], [ 13.299975425079468, 52.50312596087776 ], [ 13.299971163141629, 52.50313541503273 ], [ 13.299949732726891, 52.503135107083565 ], [ 13.29995789258003, 52.503125708941468 ], [ 13.299960207773591, 52.503116226812779 ], [ 13.299946571715479, 52.503116030865527 ], [ 13.299927273037127, 52.503110995396646 ], [ 13.299892345685143, 52.503106924873926 ], [ 13.299891085601857, 52.50308906460905 ], [ 13.299893584319117, 52.503074826970497 ], [ 13.299915196705781, 52.503070380295739 ], [ 13.299917557817199, 52.503059708391447 ], [ 13.299914395351815, 52.503040632151354 ], [ 13.299901080460591, 52.50303211405425 ], [ 13.299862163514391, 52.503030365264848 ], [ 13.299844631054045, 52.503030113312121 ], [ 13.299831453783279, 52.503018029472599 ], [ 13.299816556232546, 52.502999973223965 ], [ 13.299803195457761, 52.502992644892508 ], [ 13.299785569779104, 52.50299477066546 ], [ 13.299776472483673, 52.502977987305322 ], [ 13.299787175530154, 52.502953161741111 ], [ 13.299795610630143, 52.502936631241944 ], [ 13.299809431623823, 52.50293207171616 ], [ 13.299838789472155, 52.502928925003665 ], [ 13.29985863856886, 52.502919694851194 ], [ 13.299876943091334, 52.502911965610238 ], [ 13.299880434428911, 52.502910491772205 ], [ 13.299897966841987, 52.502910743719255 ], [ 13.299919395674497, 52.502911051655715 ], [ 13.29994630212685, 52.502920954597954 ], [ 13.299963559325091, 52.502928338903956 ], [ 13.299970862773989, 52.50293242785748 ], [ 13.299978823880972, 52.502936885010158 ], [ 13.299988058871033, 52.502950101721758 ], [ 13.30000531608772, 52.502957486021771 ], [ 13.300007126675625, 52.502961080650053 ], [ 13.300004673876989, 52.50297412941373 ], [ 13.29999447557233, 52.502985877341779 ], [ 13.300003756417139, 52.50299790607292 ], [ 13.300005291792827, 52.503008633069925 ], [ 13.30000659779561, 52.503025304456301 ], [ 13.299988790119356, 52.503032184893591 ], [ 13.29995956836739, 52.503031764988037 ], [ 13.29994203590557, 52.503031513049699 ], [ 13.299941714770139, 52.503039835194592 ], [ 13.299966810752096, 52.503046142604887 ], [ 13.299989958490738, 52.503052422913669 ], [ 13.3000247940954, 52.503058870264475 ], [ 13.300034076436019, 52.503070899014489 ], [ 13.300057224232184, 52.50307717841163 ], [ 13.300064787734168, 52.503083234775993 ], [ 13.300045169412302, 52.503086521492833 ], [ 13.30005241036107, 52.503100899083471 ], [ 13.300065635063111, 52.503111794042162 ], [ 13.300082800610122, 52.503121556087144 ], [ 13.300090180632571, 52.50313236796157 ], [ 13.300109661355418, 52.503132647871226 ], [ 13.300109202668688, 52.503144535751865 ], [ 13.300079843256224, 52.503147681610514 ], [ 13.300071866882824, 52.503152325148214 ], [ 13.300061806139315, 52.50316050734645 ], [ 13.3000545753459, 52.503167937182532 ], [ 13.300045856405251, 52.503176894820477 ], [ 13.300045231297029, 52.503180226971807 ], [ 13.30003642296581, 52.503191348407874 ], [ 13.300035196334608, 52.503193430229757 ], [ 13.300034543982052, 52.503193420856043 ], [ 13.300021028402577, 52.503201289965531 ], [ 13.300007620220097, 52.503195149614669 ], [ 13.29998832297265, 52.503190114176618 ], [ 13.299986874620533, 52.503189847005387 ], [ 13.299974123022666, 52.503211213003077 ], [ 13.29965847415373, 52.503141051789363 ], [ 13.299669797251454, 52.503160558217076 ], [ 13.299670965478288, 52.503180796238652 ], [ 13.299651255350412, 52.503186459746146 ], [ 13.299629596961168, 52.50319209614814 ], [ 13.299603766841997, 52.503204808910588 ], [ 13.299586463746348, 52.503198612531037 ], [ 13.299566981527066, 52.50319833251384 ], [ 13.299553161897341, 52.503202892031361 ], [ 13.299541105464129, 52.503212235038596 ], [ 13.299517865931598, 52.503208332397101 ], [ 13.299518233037215, 52.503198821376706 ], [ 13.299505422880801, 52.503177227379787 ], [ 13.299498318292734, 52.503159283103471 ], [ 13.299502948914778, 52.503140317967201 ], [ 13.299516787852165, 52.503124378558105 ], [ 13.29951180906164, 52.503124116381855 ] ], [ [ 13.300052663936688, 52.503043808544668 ], [ 13.300035681956849, 52.503029290085308 ], [ 13.300034284195048, 52.503014996454972 ], [ 13.300036966322143, 52.50299600419978 ], [ 13.300056905682681, 52.502984396237082 ], [ 13.300072812496468, 52.502976298040394 ], [ 13.300084958990217, 52.502964578098634 ], [ 13.30011789363734, 52.50296980947023 ], [ 13.300158573834583, 52.502976340759389 ], [ 13.300176367183729, 52.502977200623455 ], [ 13.300193594321467, 52.502978033468104 ], [ 13.300199113699573, 52.502982156116161 ], [ 13.300204961040695, 52.502986523541374 ], [ 13.300210392892453, 52.502997306518672 ], [ 13.300233540676105, 52.503003585881267 ], [ 13.300250568580179, 52.503016915434721 ], [ 13.300248253454688, 52.503026397569819 ], [ 13.300234250508318, 52.503035711781585 ], [ 13.300225815517718, 52.503052243211627 ], [ 13.300229253296955, 52.50306418707504 ], [ 13.300224989912431, 52.503073642117229 ], [ 13.300201245858641, 52.503082816369421 ], [ 13.300181535806409, 52.503088480864932 ], [ 13.300140626181049, 52.503087893074024 ], [ 13.30011548431251, 52.503082773679566 ], [ 13.300096460828359, 52.50307060586924 ], [ 13.300069509850308, 52.50306189185936 ], [ 13.300052663936688, 52.503043808544668 ] ], [ [ 13.299719267813908, 52.502945049481511 ], [ 13.299712919980811, 52.502958042260985 ], [ 13.299693437866603, 52.502957762265503 ], [ 13.299679710092541, 52.502959944043326 ], [ 13.299669418465179, 52.502974069676917 ], [ 13.299640611155093, 52.502962950713588 ], [ 13.299621358464519, 52.502956726317841 ], [ 13.299596400241334, 52.502946852202122 ], [ 13.299589297123688, 52.502928907951748 ], [ 13.299585905413995, 52.502915775193344 ], [ 13.299579167886478, 52.502888320798611 ], [ 13.299595258293859, 52.502875467155832 ], [ 13.29962053616379, 52.502877020005322 ], [ 13.299657275335838, 52.502884685261265 ], [ 13.299705977595167, 52.502885385215833 ], [ 13.299739003899345, 52.502888238936571 ], [ 13.299740491852997, 52.502900154793195 ], [ 13.299739895425326, 52.502915609305248 ], [ 13.299745005983057, 52.502934714447207 ], [ 13.299719267813908, 52.502945049481511 ] ], [ [ 13.299049861597364, 52.502875953754227 ], [ 13.299037667482422, 52.502888862443882 ], [ 13.299027927916546, 52.502888722412258 ], [ 13.299006359927798, 52.502891980925163 ], [ 13.298990224949579, 52.502906022486172 ], [ 13.298968612519747, 52.502910469890537 ], [ 13.298953349644387, 52.502901923676902 ], [ 13.298930202136694, 52.502895643167051 ], [ 13.29892681062627, 52.50288251039138 ], [ 13.298925275517163, 52.502871783382425 ], [ 13.298933665054188, 52.50285644092574 ], [ 13.29895332925482, 52.502851965515667 ], [ 13.298984589375092, 52.502850036802947 ], [ 13.299007782803056, 52.502855127526132 ], [ 13.299034413642454, 52.502872163039051 ], [ 13.299049861597364, 52.502875953754227 ] ], [ [ 13.299169475091261, 52.502857452198192 ], [ 13.299146327540324, 52.502851172629128 ], [ 13.299132919587462, 52.502845032180709 ], [ 13.299117886159372, 52.502830542499815 ], [ 13.299100307886439, 52.502831479314835 ], [ 13.29908708350812, 52.502820584250422 ], [ 13.299101363489877, 52.502804136932589 ], [ 13.299098017833563, 52.502789815283869 ], [ 13.299088965268298, 52.502771842974326 ], [ 13.299102694483809, 52.502769661285917 ], [ 13.299108996569188, 52.502757857417471 ], [ 13.299136453515647, 52.50275349401312 ], [ 13.299154741953638, 52.502754617398828 ], [ 13.299161732769834, 52.502755046981086 ], [ 13.299162747269374, 52.502756361695241 ], [ 13.299171014845861, 52.502767074898252 ], [ 13.299188501299097, 52.502768515827348 ], [ 13.299207935956117, 52.502769984761528 ], [ 13.299229135236109, 52.502776237209652 ], [ 13.299232572724639, 52.502788181100115 ], [ 13.299263191809526, 52.502802894821606 ], [ 13.299256750602149, 52.502818265309735 ], [ 13.299254297590524, 52.502831314056351 ], [ 13.29924015681434, 52.502844194781837 ], [ 13.299214556235331, 52.502850963972236 ], [ 13.299198650819426, 52.502859061175279 ], [ 13.299182881580505, 52.502863592621352 ], [ 13.299169475091261, 52.502857452198192 ] ], [ [ 13.298980949426086, 52.502792889375698 ], [ 13.298966898880828, 52.502803393190895 ], [ 13.298955211130368, 52.502803225141818 ], [ 13.298918242634498, 52.502801504055689 ], [ 13.298893284694156, 52.502791628894805 ], [ 13.298875752337569, 52.502791376799301 ], [ 13.298862483594556, 52.502781670608726 ], [ 13.298859183922842, 52.502766160076675 ], [ 13.298845867812849, 52.502757642740363 ], [ 13.298846372739526, 52.502744565989509 ], [ 13.298856389176906, 52.502737572793571 ], [ 13.298876191011281, 52.502729531662752 ], [ 13.298890195649594, 52.502720216735433 ], [ 13.298919553382172, 52.502717071148943 ], [ 13.298941073915556, 52.502715001507212 ], [ 13.298966307237571, 52.502717743393987 ], [ 13.298979669261241, 52.502725071839563 ], [ 13.299000684915589, 52.502736078938888 ], [ 13.299013678269585, 52.502752918378135 ], [ 13.298997726916978, 52.502762205330257 ], [ 13.298980148669163, 52.502763142127648 ], [ 13.298993096156037, 52.502781169547738 ], [ 13.298980949426086, 52.502792889375698 ] ], [ [ 13.298870432945471, 52.502828174169906 ], [ 13.29887604803552, 52.502834202595515 ], [ 13.298893580409114, 52.502834454691111 ], [ 13.298899103729237, 52.502842859971544 ], [ 13.29889655880153, 52.50285828646421 ], [ 13.298896053887674, 52.502871363215164 ], [ 13.298876389672767, 52.502875838615566 ], [ 13.298856817264907, 52.502877936258265 ], [ 13.298825739238067, 52.50287511030573 ], [ 13.298810522309665, 52.502865375196777 ], [ 13.298799246199735, 52.502854508114972 ], [ 13.298797894750708, 52.502839025595897 ], [ 13.29882553536749, 52.502829907654352 ], [ 13.298845245472522, 52.502824243385007 ], [ 13.298866582440935, 52.502826929266192 ], [ 13.298870432945471, 52.502828174169906 ] ], [ [ 13.299056551419921, 52.50290459703249 ], [ 13.299075003219404, 52.502881073373231 ], [ 13.299094667417265, 52.502876597939697 ], [ 13.299121894947627, 52.502878178926231 ], [ 13.299148847129009, 52.502886892272194 ], [ 13.299158035961048, 52.502901297923941 ], [ 13.299153680568999, 52.502913130680724 ], [ 13.29915526163246, 52.502922668809695 ], [ 13.299141166695584, 52.502934360645305 ], [ 13.299115657846386, 52.50293875205891 ], [ 13.299100073639234, 52.502938528005338 ], [ 13.299076880191203, 52.502933436401236 ], [ 13.299065328632901, 52.502929701709611 ], [ 13.299054052443458, 52.50291883465156 ], [ 13.299056551419921, 52.50290459703249 ] ], [ [ 13.299184437403193, 52.502924277790669 ], [ 13.299196815071014, 52.502906613576044 ], [ 13.299210129821578, 52.502915130852429 ], [ 13.29922761633488, 52.502916571776225 ], [ 13.299228121151176, 52.502903495023133 ], [ 13.299241988052058, 52.502897747585216 ], [ 13.299259291015593, 52.502903943115442 ], [ 13.299268435465493, 52.502919537657483 ], [ 13.299272055045272, 52.502926726914389 ], [ 13.299271458452752, 52.502942181422618 ], [ 13.299255415329212, 52.502953845265317 ], [ 13.299251105887386, 52.502964488249724 ], [ 13.299229355767178, 52.502972502333954 ], [ 13.299228004164208, 52.502957019818787 ], [ 13.299214368160538, 52.502956823787535 ], [ 13.299187370061349, 52.502949298434892 ], [ 13.299197386457887, 52.502942306108856 ], [ 13.299184437403193, 52.502924277790669 ] ], [ [ 13.299461569568436, 52.502955619162442 ], [ 13.299477750305265, 52.502940388658601 ], [ 13.299496909753373, 52.502948989912838 ], [ 13.299515841242265, 52.502963535573194 ], [ 13.299518957607523, 52.502983801598894 ], [ 13.299518085773174, 52.503006389374463 ], [ 13.299507748229658, 52.503021702972347 ], [ 13.299486089904134, 52.503027339348705 ], [ 13.299464752798588, 52.503024653578208 ], [ 13.299437525163722, 52.503023072669706 ], [ 13.299424392425534, 52.50300979988711 ], [ 13.29944034521821, 52.503000513797666 ], [ 13.29942544783469, 52.502982457501119 ], [ 13.299435923101921, 52.502963576379294 ], [ 13.299461569568436, 52.502955619162442 ] ], [ [ 13.299664558548953, 52.502998978310721 ], [ 13.299684407713391, 52.502989748188213 ], [ 13.299699579031504, 52.503000671165658 ], [ 13.299707509533036, 52.502997216530773 ], [ 13.299705974230243, 52.502986489530521 ], [ 13.299706111878548, 52.502982922897388 ], [ 13.299729351310177, 52.502986825501203 ], [ 13.299762192694306, 52.502994434705855 ], [ 13.29976971029496, 52.503001679067822 ], [ 13.299769343280909, 52.503011189191142 ], [ 13.299790588638697, 52.503016252662334 ], [ 13.299798197973749, 52.503021120165627 ], [ 13.299807663799474, 52.503028392523191 ], [ 13.299795882750821, 52.503030602291027 ], [ 13.29979143578954, 52.503044811929264 ], [ 13.299789212290431, 52.503051917197588 ], [ 13.299798632240764, 52.503060378433673 ], [ 13.299778968029331, 52.503064853984029 ], [ 13.299765146990577, 52.503069413505784 ], [ 13.299733931153472, 52.503070153524817 ], [ 13.299723686837202, 52.503083091204893 ], [ 13.299706246122959, 52.503080461476053 ], [ 13.299677253761764, 52.503074097112084 ], [ 13.299672097334886, 52.503056180845455 ], [ 13.299657017773278, 52.503042880109142 ], [ 13.299645649639281, 52.503034390861529 ], [ 13.299655620134105, 52.503028586475374 ], [ 13.299658118923475, 52.503014348842306 ], [ 13.299664558548953, 52.502998978310721 ] ], [ [ 13.298492031981219, 52.502683564762407 ], [ 13.298478350151978, 52.502684557524098 ], [ 13.298455340540912, 52.502674711190259 ], [ 13.298453254595145, 52.502678249801271 ], [ 13.29845238225889, 52.502700836666961 ], [ 13.298457490761644, 52.50271994183904 ], [ 13.298456893887924, 52.502735396341322 ], [ 13.298462049784797, 52.502753312657298 ], [ 13.298449857006371, 52.502766221306658 ], [ 13.298441696902231, 52.502775619343097 ], [ 13.298435301330192, 52.502789800907202 ], [ 13.298407706542852, 52.50279773077883 ], [ 13.298382473212584, 52.502794988772841 ], [ 13.298368975020599, 52.502791226013869 ], [ 13.298359325844688, 52.502788708152373 ], [ 13.298326301185668, 52.502785854068961 ], [ 13.298310991052105, 52.502778497527999 ], [ 13.298309411683405, 52.502768958511645 ], [ 13.298295820157588, 52.502767574375653 ], [ 13.2982863102258, 52.502761489898809 ], [ 13.298282735234023, 52.502753112613654 ], [ 13.298271275633946, 52.502747000091574 ], [ 13.298271918494045, 52.502730356713933 ], [ 13.298274371789873, 52.502717307989911 ], [ 13.298297978311169, 52.502711699857727 ], [ 13.298296672961984, 52.502695028457183 ], [ 13.298281548020027, 52.502682916426245 ], [ 13.298287759943038, 52.502673490378115 ], [ 13.298311228679504, 52.502671448873713 ], [ 13.298320922274122, 52.502672777840814 ], [ 13.298338454578481, 52.502673030018116 ], [ 13.298338638255034, 52.502668274510434 ], [ 13.298356217948964, 52.502667337829337 ], [ 13.298342947885903, 52.502657631560382 ], [ 13.298339602486694, 52.502643309892107 ], [ 13.298342285351888, 52.502624316781564 ], [ 13.298350445448319, 52.502614918751661 ], [ 13.298358605541239, 52.502605520721183 ], [ 13.298374556969124, 52.502596233855684 ], [ 13.29838448157599, 52.502591619352465 ], [ 13.298407858430439, 52.50259195558241 ], [ 13.298411295682529, 52.502603899495092 ], [ 13.29841899515414, 52.50260638931038 ], [ 13.298430638405138, 52.502607746310126 ], [ 13.2984385215439, 52.502605480616147 ], [ 13.298447987054994, 52.502612753080392 ], [ 13.298453556144011, 52.502619970402556 ], [ 13.298465381589759, 52.502616571869702 ], [ 13.298475443937958, 52.502608390727744 ], [ 13.298490477078749, 52.502622880488403 ], [ 13.298519882193995, 52.502618545246236 ], [ 13.298519560829495, 52.502626866486942 ], [ 13.29852879678889, 52.50264008333005 ], [ 13.298516510760733, 52.502655369719825 ], [ 13.298500283886767, 52.502671788968044 ], [ 13.298492031981219, 52.502683564762407 ] ], [ [ 13.298238625072498, 52.502633531577445 ], [ 13.298213024502438, 52.502640299652413 ], [ 13.29819368178538, 52.502636452801852 ], [ 13.29817619394781, 52.502635011705188 ], [ 13.298175642900773, 52.502649277328601 ], [ 13.298188775119879, 52.502662551145086 ], [ 13.298194296811372, 52.50267095643612 ], [ 13.298194067196174, 52.502676900820376 ], [ 13.298172362989643, 52.502683724934748 ], [ 13.298162210192903, 52.502694283825008 ], [ 13.298159802754654, 52.5027061445683 ], [ 13.298153361178599, 52.502721514993425 ], [ 13.298147149220021, 52.502730941034159 ], [ 13.298125399054568, 52.502738954016358 ], [ 13.298107683023996, 52.502743457314367 ], [ 13.298080639323356, 52.502737120588336 ], [ 13.298057400141689, 52.502733218561488 ], [ 13.298032442436748, 52.502723343220886 ], [ 13.298021489488129, 52.502704154849347 ], [ 13.29801443150636, 52.502685021611576 ], [ 13.298017022658907, 52.502668406263204 ], [ 13.298035060124555, 52.502655581739965 ], [ 13.298043220291984, 52.50264618373194 ], [ 13.298047621880295, 52.502633162141073 ], [ 13.298057728763219, 52.502623792137442 ], [ 13.298069280154211, 52.502627526926226 ], [ 13.298094375625517, 52.502633834729842 ], [ 13.298117844314154, 52.502631794162369 ], [ 13.298124008901832, 52.502623556079946 ], [ 13.298126186686108, 52.50261764061996 ], [ 13.298114728615445, 52.502611528104232 ], [ 13.298101229030335, 52.502607765294094 ], [ 13.298082391488245, 52.502590841679371 ], [ 13.29806912298144, 52.502581135401527 ], [ 13.298081223966278, 52.50257060454512 ], [ 13.298095182793755, 52.502562478589482 ], [ 13.298095733865109, 52.502548212966481 ], [ 13.298109828950752, 52.50253652125604 ], [ 13.298132838411563, 52.502546367656038 ], [ 13.298140401556761, 52.502552424140909 ], [ 13.298148607606297, 52.502541837248238 ], [ 13.29818353437334, 52.502545907382384 ], [ 13.298218324818336, 52.50255354505672 ], [ 13.29823934019854, 52.502564552289243 ], [ 13.298244771482755, 52.502575336251617 ], [ 13.29825609183351, 52.502585014486904 ], [ 13.298273302691753, 52.502593587913218 ], [ 13.298261109903704, 52.502606496543379 ], [ 13.29825693798743, 52.50261357375831 ], [ 13.29825629512438, 52.502630217136137 ], [ 13.298238625072498, 52.502633531577445 ] ], [ [ 13.297838839746154, 52.502548085730083 ], [ 13.297832949239323, 52.502549190516312 ], [ 13.297809664280978, 52.502546476419695 ], [ 13.297784293332745, 52.502547300917421 ], [ 13.297773156832843, 52.502532867131464 ], [ 13.297763645551553, 52.502526782592241 ], [ 13.297746572624582, 52.502514642462927 ], [ 13.297750790512707, 52.502506377288505 ], [ 13.29775528400673, 52.502490978854262 ], [ 13.297767522843284, 52.502476881400398 ], [ 13.297785468468257, 52.502466433769392 ], [ 13.297799243547976, 52.502463064253881 ], [ 13.297822574394385, 52.502464589475537 ], [ 13.297830044179795, 52.502473022813348 ], [ 13.297833159739037, 52.502493288878206 ], [ 13.297860432912675, 52.502493681276135 ], [ 13.297869668646145, 52.502506898169074 ], [ 13.297861416604929, 52.502518673918615 ], [ 13.297835953808571, 52.502521876180879 ], [ 13.297843562875373, 52.502526742909119 ], [ 13.297846999934668, 52.50253868773558 ], [ 13.297838839746154, 52.502548085730083 ] ], [ [ 13.29820522492278, 52.502741291845787 ], [ 13.298188059944266, 52.502731529532568 ], [ 13.298164591170616, 52.502733571012158 ], [ 13.298155125716029, 52.502726297626992 ], [ 13.298165509613982, 52.502709794374567 ], [ 13.298194775575803, 52.5027090258233 ], [ 13.298193378419533, 52.502694732175641 ], [ 13.298203303067487, 52.50269011768804 ], [ 13.298218933101975, 52.502689152982107 ], [ 13.298246436010587, 52.502683600907794 ], [ 13.298258354769468, 52.502677825562046 ], [ 13.298270042482443, 52.502677993679669 ], [ 13.29827733017868, 52.502691182518689 ], [ 13.298269261894413, 52.502698202789418 ], [ 13.298268527191855, 52.502717223920826 ], [ 13.298267792488472, 52.502736245052198 ], [ 13.298242008175212, 52.502747768640354 ], [ 13.298226607731799, 52.50274278896493 ], [ 13.29820522492278, 52.502741291845787 ] ], [ [ 13.297397554371791, 52.502416841549241 ], [ 13.29738336724906, 52.502430910926549 ], [ 13.297379838399621, 52.502421344738906 ], [ 13.297366248519729, 52.502419959619772 ], [ 13.297367691345606, 52.502433064399135 ], [ 13.297373258761944, 52.502440281749479 ], [ 13.297352013968132, 52.502435217848415 ], [ 13.297336567790603, 52.502431426935026 ], [ 13.297324786783966, 52.502433636457276 ], [ 13.297310599632665, 52.502447705825581 ], [ 13.297295015613726, 52.502447481535775 ], [ 13.297275717504688, 52.502442446537515 ], [ 13.297273403245821, 52.502451928631274 ], [ 13.297286763431346, 52.502459257244126 ], [ 13.297291964768487, 52.502475984731362 ], [ 13.297272484364425, 52.502475704360279 ], [ 13.29725712856853, 52.502469536561279 ], [ 13.297249060164679, 52.50247655676165 ], [ 13.297258433519339, 52.502486207970513 ], [ 13.297264002431444, 52.502493424448403 ], [ 13.297277362593077, 52.502500753961023 ], [ 13.297272914878979, 52.50251496349933 ], [ 13.297251394372077, 52.502517032837844 ], [ 13.297241333292734, 52.502525213895254 ], [ 13.297219720881325, 52.502529660980514 ], [ 13.297211928121422, 52.502529548819162 ], [ 13.297202738531375, 52.502515143000302 ], [ 13.29718747613901, 52.502506596562291 ], [ 13.297185849565073, 52.502498247286368 ], [ 13.297178516277567, 52.502486247258936 ], [ 13.29720026799273, 52.502478233573484 ], [ 13.297216173630719, 52.502470136640547 ], [ 13.297218489410222, 52.502460653670603 ], [ 13.297218811018459, 52.502452332435283 ], [ 13.297219178575498, 52.502442822323616 ], [ 13.297209621441683, 52.502437926616849 ], [ 13.297223580384983, 52.502429800764887 ], [ 13.29721796553839, 52.502423773161397 ], [ 13.297202840987568, 52.502411660995321 ], [ 13.297195921254881, 52.502388961082374 ], [ 13.297202271213912, 52.502375968463561 ], [ 13.297214326397503, 52.502366626573057 ], [ 13.297243823355805, 52.502359913894907 ], [ 13.297261217682829, 52.502363732859727 ], [ 13.297286404744449, 52.502367663978703 ], [ 13.297303890971428, 52.502369105184577 ], [ 13.297305563510077, 52.502376265582896 ], [ 13.297322775537406, 52.502384840065368 ], [ 13.297336641079138, 52.502379091931211 ], [ 13.297366092083006, 52.502373568098847 ], [ 13.29738112477739, 52.502388057997202 ], [ 13.297398289407582, 52.502397821324031 ], [ 13.297397554371791, 52.502416841549241 ] ], [ [ 13.29702373446213, 52.502370665292268 ], [ 13.297017158181044, 52.502374493495566 ], [ 13.296989885093074, 52.502374100903907 ], [ 13.296989106316445, 52.502366118082698 ], [ 13.296983376619639, 52.502362910257617 ], [ 13.296969877209799, 52.502359147319709 ], [ 13.296932864693867, 52.502358614514172 ], [ 13.296930353261697, 52.502353032579819 ], [ 13.296920582118799, 52.502351692491288 ], [ 13.29692090378202, 52.502343371257055 ], [ 13.296921205069781, 52.502342628424358 ], [ 13.29691416554553, 52.502338124987702 ], [ 13.296904838254889, 52.502327284876152 ], [ 13.296889300239483, 52.502325871657149 ], [ 13.296880082457061, 52.502326758563107 ], [ 13.296872615780574, 52.502331971175153 ], [ 13.296871517729235, 52.502332286244375 ], [ 13.296877105617781, 52.50233878101978 ], [ 13.296855677097369, 52.502338472534767 ], [ 13.296846211944663, 52.502331199046964 ], [ 13.296846519309497, 52.502323248045037 ], [ 13.296829542677829, 52.502320021259372 ], [ 13.296830765848464, 52.50232740806814 ], [ 13.296803538752252, 52.50232582655773 ], [ 13.296790178688344, 52.50231849789116 ], [ 13.296789781685574, 52.502314437137755 ], [ 13.296774045748039, 52.502310330882679 ], [ 13.296770472552049, 52.502301953575156 ], [ 13.29675312278224, 52.502296945641469 ], [ 13.296739624885278, 52.502293182698772 ], [ 13.296733044047656, 52.502288972674684 ], [ 13.296722661794847, 52.50229071135464 ], [ 13.296720144567574, 52.502292902237471 ], [ 13.296720138557404, 52.502293057698935 ], [ 13.296728253491516, 52.502306901419153 ], [ 13.296699033742472, 52.502306480733722 ], [ 13.296669858485105, 52.502304871143906 ], [ 13.296648843660524, 52.502293862737879 ], [ 13.296631539837893, 52.502287666809146 ], [ 13.296624254173185, 52.502274477893828 ], [ 13.296640251804163, 52.502264002387811 ], [ 13.296638717270627, 52.502253275353063 ], [ 13.296620566680181, 52.502246268810964 ], [ 13.296618664152252, 52.502246241418199 ], [ 13.296593728279506, 52.502257385746788 ], [ 13.296585567879056, 52.502266783653411 ], [ 13.296530977354649, 52.50226718716312 ], [ 13.296511725416769, 52.502260962266924 ], [ 13.296498595261953, 52.502247689188557 ], [ 13.296486717045074, 52.502238999874677 ], [ 13.296464792380318, 52.502242836317635 ], [ 13.296460896027281, 52.502242780212377 ], [ 13.29646082436104, 52.502242271176783 ], [ 13.296453928014927, 52.502243478296599 ], [ 13.296446411125562, 52.502236232830164 ], [ 13.296462638594463, 52.502219813867697 ], [ 13.296476689505969, 52.502209311251022 ], [ 13.296488744795319, 52.502199969435154 ], [ 13.296517837536932, 52.502196852099225 ], [ 13.296523217047437, 52.502193720593773 ], [ 13.29652469540609, 52.502193629490314 ], [ 13.296530022728591, 52.502191047495515 ], [ 13.296537681111355, 52.50218438288654 ], [ 13.296538089720084, 52.502184027323189 ], [ 13.2965556232863, 52.502184279784188 ], [ 13.296590687474431, 52.502184784655995 ], [ 13.296617684685742, 52.502192309695829 ], [ 13.296643009481434, 52.502192674322693 ], [ 13.296656737157898, 52.502190492896219 ], [ 13.296677845334646, 52.502199123569852 ], [ 13.29670122196015, 52.502199460135202 ], [ 13.296712958276327, 52.502203588838363 ], [ 13.296713850637717, 52.50220360168597 ], [ 13.296714895920221, 52.502204270396589 ], [ 13.296722374674236, 52.502206901005152 ], [ 13.296749417848702, 52.502213238037776 ], [ 13.296768714288561, 52.502218273998174 ], [ 13.29678801076763, 52.502223309056824 ], [ 13.296799422591622, 52.502230610575054 ], [ 13.296799269600273, 52.50223456810356 ], [ 13.296810104583514, 52.50223472408981 ], [ 13.296824212375206, 52.502234067631527 ], [ 13.296834488293616, 52.502231115396143 ], [ 13.296851790650875, 52.502237311274072 ], [ 13.296871226459858, 52.502238780610803 ], [ 13.296884632436539, 52.502244921290703 ], [ 13.296917336398103, 52.502256097028123 ], [ 13.296920817705143, 52.502266852083757 ], [ 13.29693436303892, 52.502269426150939 ], [ 13.296963752109788, 52.502269849215772 ], [ 13.296966657330074, 52.502269093516212 ], [ 13.296988177727759, 52.502267024228495 ], [ 13.297025098260026, 52.502269934768485 ], [ 13.297040406492984, 52.502277292350527 ], [ 13.297043154596798, 52.502278924252316 ], [ 13.297058579556618, 52.502283109613472 ], [ 13.297071847749937, 52.502292816002495 ], [ 13.29708511742165, 52.502302522411298 ], [ 13.297086376680065, 52.502303944964972 ], [ 13.297092587003331, 52.502310956693087 ], [ 13.297092295382486, 52.502318501519113 ], [ 13.297093344332088, 52.502319685475683 ], [ 13.297092198615463, 52.502321005078613 ], [ 13.297092081564166, 52.502324033432998 ], [ 13.297089581990994, 52.502338271006813 ], [ 13.297073538520102, 52.502349934551738 ], [ 13.297053506769371, 52.502363919760953 ], [ 13.297041633860033, 52.502368507007432 ], [ 13.29702373446213, 52.502370665292268 ] ], [ [ 13.296668288314232, 52.502194227820112 ], [ 13.296657104832757, 52.502180982786918 ], [ 13.296644112549105, 52.502164143096373 ], [ 13.296660156063577, 52.502152479609713 ], [ 13.296670402488605, 52.502139543118567 ], [ 13.296688348196017, 52.502129096553119 ], [ 13.296676752564348, 52.50212655052848 ], [ 13.296673132000134, 52.502119361174216 ], [ 13.296677763681558, 52.502100397053354 ], [ 13.296685646816425, 52.502098131476004 ], [ 13.296699421896284, 52.50209476118949 ], [ 13.296715005785675, 52.502094985556681 ], [ 13.296742278696991, 52.502095378210221 ], [ 13.296781560879815, 52.502087616988277 ], [ 13.296791623414414, 52.502079435990439 ], [ 13.296824648908279, 52.502082290518445 ], [ 13.296843991258493, 52.502086137590162 ], [ 13.296870896538771, 52.50209604032441 ], [ 13.296874333337533, 52.502107984278858 ], [ 13.296887693317915, 52.502115313834359 ], [ 13.296900915466439, 52.502126209118238 ], [ 13.296902679810339, 52.502130992668214 ], [ 13.296910288706151, 52.502135859455329 ], [ 13.296907972879112, 52.502145342419325 ], [ 13.296892159218679, 52.502151061559438 ], [ 13.296874397346251, 52.502156753550267 ], [ 13.296864105018484, 52.502170878935623 ], [ 13.296850329926887, 52.502174249241179 ], [ 13.296842676579855, 52.502170570448932 ], [ 13.296817351794271, 52.502170205864907 ], [ 13.296801398761433, 52.502179491599783 ], [ 13.296795142072089, 52.502190107364427 ], [ 13.296771581642652, 52.502194525423285 ], [ 13.296749831465084, 52.502202539052043 ], [ 13.296724644548046, 52.502198607819906 ], [ 13.29670530068702, 52.502194760707241 ], [ 13.296668288314232, 52.502194227820112 ] ], [ [ 13.296484198341407, 52.502295452715288 ], [ 13.296497697689107, 52.502299215708177 ], [ 13.296517315910311, 52.502295929578473 ], [ 13.296536658325913, 52.502299776700845 ], [ 13.296547749866546, 52.502315399496908 ], [ 13.296549284379724, 52.502326126532573 ], [ 13.296546738664336, 52.502341552969284 ], [ 13.296546279029613, 52.502353440829552 ], [ 13.296543871212537, 52.502365300638431 ], [ 13.29651655216921, 52.502366096813631 ], [ 13.296506857227127, 52.502364767678408 ], [ 13.29649345123325, 52.502358627854562 ], [ 13.296481900049308, 52.502354892913438 ], [ 13.296466178162516, 52.502358235142303 ], [ 13.296444657732573, 52.502360303435808 ], [ 13.296450824029769, 52.502352066362427 ], [ 13.296431757384662, 52.50234108686962 ], [ 13.2964303148398, 52.502327982081049 ], [ 13.296442645990115, 52.502311507015463 ], [ 13.29645057661677, 52.502308052599638 ], [ 13.29646625251763, 52.502305899248533 ], [ 13.296484198341407, 52.502295452715288 ] ], [ [ 13.296630780347382, 52.502335625804328 ], [ 13.296644969149547, 52.502321556540011 ], [ 13.296685832026172, 52.502323334409361 ], [ 13.296706709020999, 52.502337908538294 ], [ 13.296720024552176, 52.50234642701006 ], [ 13.296717524857515, 52.502360664574859 ], [ 13.29671488730852, 52.502378467868937 ], [ 13.296706818801374, 52.502385488930472 ], [ 13.296693091066276, 52.502387670362168 ], [ 13.296692539544077, 52.502401935975186 ], [ 13.296696020806802, 52.502412691036788 ], [ 13.29666870320855, 52.502413487268122 ], [ 13.296653439448813, 52.502404941639696 ], [ 13.296642073569288, 52.502396451231483 ], [ 13.296634326799937, 52.502395150156339 ], [ 13.296620461163347, 52.502400898207362 ], [ 13.296608955922698, 52.502395974402575 ], [ 13.29659949223686, 52.502388701815136 ], [ 13.296609599402437, 52.502379331038327 ], [ 13.29661409328037, 52.502363932650397 ], [ 13.296608708352437, 52.502351960639061 ], [ 13.29662285120213, 52.502339080253485 ], [ 13.296630780347382, 52.502335625804328 ] ], [ [ 13.29712523041583, 52.502503321551586 ], [ 13.297111271431081, 52.502511447391512 ], [ 13.297101531958322, 52.502511307202042 ], [ 13.297084044209742, 52.502509865944639 ], [ 13.297078614656403, 52.502499082851159 ], [ 13.297078982279052, 52.502489571841643 ], [ 13.297085468672897, 52.502473012579976 ], [ 13.297078228795064, 52.502458634815369 ], [ 13.297088243936388, 52.502451642648069 ], [ 13.297109580610556, 52.502454328843868 ], [ 13.297121040006422, 52.502460440578112 ], [ 13.297130503759714, 52.502467714023226 ], [ 13.297130090231789, 52.5024784130106 ], [ 13.297129446946316, 52.502495056379061 ], [ 13.29712523041583, 52.502503321551586 ] ], [ [ 13.297320680737133, 52.50248948203361 ], [ 13.297335051647813, 52.502470658057455 ], [ 13.297364456768303, 52.502466323102489 ], [ 13.297384304690743, 52.50245709334596 ], [ 13.297393724050281, 52.502465554769323 ], [ 13.297404906352554, 52.502478799713565 ], [ 13.297408159548175, 52.50249549915879 ], [ 13.297429404377551, 52.502500563049978 ], [ 13.297440771944228, 52.502509052506539 ], [ 13.297432565697243, 52.502519640247357 ], [ 13.297426169840648, 52.502533821755058 ], [ 13.297423762249062, 52.502545681583449 ], [ 13.297411844891373, 52.502551456865824 ], [ 13.297403546775744, 52.502564421458587 ], [ 13.29740113914149, 52.502576282185032 ], [ 13.297389221772516, 52.502582057465006 ], [ 13.297371642095342, 52.502582994002537 ], [ 13.297348540897717, 52.50257552520852 ], [ 13.297342971996331, 52.502568307835858 ], [ 13.297329290180079, 52.502569300464756 ], [ 13.297305913350771, 52.502568964021819 ], [ 13.297292553129157, 52.502561635410942 ], [ 13.297275570742917, 52.502547117440962 ], [ 13.297275984271277, 52.502536417554239 ], [ 13.297284374313477, 52.502521075218048 ], [ 13.297300371845646, 52.502510600521212 ], [ 13.297310342524842, 52.502504796333881 ], [ 13.297320680737133, 52.50248948203361 ] ], [ [ 13.300022057336459, 52.502016486187635 ], [ 13.299989354216819, 52.502005311324083 ], [ 13.29998990466396, 52.50199104568572 ], [ 13.299996206396584, 52.501979241765547 ], [ 13.299992770231205, 52.501967297915634 ], [ 13.299989561962132, 52.501949409654017 ], [ 13.300003656355129, 52.501937717710902 ], [ 13.300025405822614, 52.501929703477423 ], [ 13.300046880051317, 52.501928822508695 ], [ 13.30006636171424, 52.501929102442169 ], [ 13.300074428098773, 52.501922082023235 ], [ 13.30009567300519, 52.501927144537774 ], [ 13.300102915226448, 52.50194152304632 ], [ 13.300084970394327, 52.501951970137618 ], [ 13.300084557591344, 52.501962669142316 ], [ 13.300092075038636, 52.501969914382585 ], [ 13.300077703993988, 52.501988738685348 ], [ 13.300077520509589, 52.501993494197905 ], [ 13.300071171452677, 52.502006486979511 ], [ 13.300065053187238, 52.50201353628988 ], [ 13.300049376117906, 52.502015689200526 ], [ 13.3000277642294, 52.502020136803658 ], [ 13.300022057336459, 52.502016486187635 ] ], [ [ 13.299941915778302, 52.501921367426519 ], [ 13.299924337883267, 52.501922304367376 ], [ 13.299909165436194, 52.501911380499699 ], [ 13.299897294413253, 52.501915968057837 ], [ 13.299892847605252, 52.50193017770367 ], [ 13.299876941141015, 52.501938275880491 ], [ 13.299861311439292, 52.501939239909518 ], [ 13.299857782087045, 52.501929673790826 ], [ 13.299854527998271, 52.50191297440395 ], [ 13.299853223549016, 52.501896303034798 ], [ 13.299857531263008, 52.50188566000336 ], [ 13.299861932200798, 52.501872639236822 ], [ 13.299881595869262, 52.501868163667226 ], [ 13.299905293452241, 52.501860178350476 ], [ 13.299936233250882, 52.50186656975 ], [ 13.29994950340002, 52.501876276741022 ], [ 13.299948907067675, 52.501891731257473 ], [ 13.299954383129339, 52.501901325347475 ], [ 13.299942145151483, 52.501915423036209 ], [ 13.299941915778302, 52.501921367426519 ] ], [ [ 13.299554896756224, 52.501899152687734 ], [ 13.29954433006716, 52.501920410682963 ], [ 13.299530648496251, 52.501921403568701 ], [ 13.29951320825251, 52.501918773814943 ], [ 13.299474338159628, 52.501915836926955 ], [ 13.29944901348583, 52.501915472907321 ], [ 13.299437325967924, 52.501915304907818 ], [ 13.299423872366971, 52.501910353371258 ], [ 13.299429946303666, 52.501904492993603 ], [ 13.299430405153098, 52.501892605114634 ], [ 13.299408976821262, 52.501892297093761 ], [ 13.299407349845625, 52.501883947844156 ], [ 13.299423346679529, 52.501873471954312 ], [ 13.299431322850999, 52.501868829356653 ], [ 13.29944138488244, 52.501860647229933 ], [ 13.299449405465026, 52.501854815732074 ], [ 13.29948048281736, 52.501857641515471 ], [ 13.299501865246764, 52.501859138401059 ], [ 13.299523385335178, 52.501857068649272 ], [ 13.299552698071713, 52.501855110900372 ], [ 13.29956800662638, 52.501862467259564 ], [ 13.299563377651662, 52.501881432424859 ], [ 13.299554896756224, 52.501899152687734 ] ], [ [ 13.299248849793878, 52.501849553655511 ], [ 13.299266152315395, 52.501855750080949 ], [ 13.299271492017967, 52.501868910855514 ], [ 13.299261200523388, 52.501883036457116 ], [ 13.299258977036578, 52.501890140817984 ], [ 13.299237135700027, 52.501900531766239 ], [ 13.299246647054282, 52.501906616188208 ], [ 13.299252260609082, 52.501912644575164 ], [ 13.29924414820311, 52.501920853814568 ], [ 13.299238028345972, 52.501927903060071 ], [ 13.299225972236448, 52.501937245140923 ], [ 13.299200555764267, 52.501939258823285 ], [ 13.299183436765469, 52.501928307776382 ], [ 13.299183987506643, 52.501914041245158 ], [ 13.299176379866642, 52.501909174623442 ], [ 13.29915912176042, 52.501901789286116 ], [ 13.299143905054001, 52.501892055119306 ], [ 13.299134623164338, 52.501880027199981 ], [ 13.299141293750282, 52.501858712327419 ], [ 13.299162997401433, 52.501851888029606 ], [ 13.299186511557664, 52.501848657469687 ], [ 13.299195885244476, 52.501858307629853 ], [ 13.299218848673345, 52.501869343595004 ], [ 13.299225060312919, 52.501859916595087 ], [ 13.299248849793878, 52.501849553655511 ] ], [ [ 13.29897423185882, 52.501849174063871 ], [ 13.298987410022535, 52.501861258017406 ], [ 13.298994742232596, 52.501873257916017 ], [ 13.298996185529157, 52.501886362680622 ], [ 13.298986124882433, 52.501894544789835 ], [ 13.298981861385734, 52.501903998889105 ], [ 13.298967765262814, 52.50191569068717 ], [ 13.298953624703586, 52.501928571382102 ], [ 13.298938362132311, 52.501920026066685 ], [ 13.298940815180478, 52.501906977325035 ], [ 13.298927591148999, 52.501896081345272 ], [ 13.298900547808843, 52.501889745707842 ], [ 13.298912603973422, 52.50188040366055 ], [ 13.298909212574186, 52.501867269984295 ], [ 13.29893132934612, 52.501849746730947 ], [ 13.298945333689025, 52.501840431793298 ], [ 13.298962911568863, 52.501839494997832 ], [ 13.298974461361928, 52.501843229676467 ], [ 13.29897423185882, 52.501849174063871 ] ], [ [ 13.298709869131303, 52.501881056124844 ], [ 13.29868992992699, 52.501892663863828 ], [ 13.298676294256449, 52.501892467773374 ], [ 13.298653422737935, 52.501879054848068 ], [ 13.298642192823619, 52.501866998874753 ], [ 13.298642927342366, 52.501847977736901 ], [ 13.298658648852939, 52.501844636114157 ], [ 13.298681841715108, 52.501849726897255 ], [ 13.298705126363856, 52.501852440819825 ], [ 13.298706844986157, 52.501858413221996 ], [ 13.298714316314486, 52.501866846526561 ], [ 13.298713994960716, 52.501875168667802 ], [ 13.298709869131303, 52.501881056124844 ] ], [ [ 13.298185616236259, 52.501879463645899 ], [ 13.298177502166938, 52.501887672789415 ], [ 13.298165906509112, 52.501885126910445 ], [ 13.298158710784818, 52.501869561208892 ], [ 13.298145212894259, 52.501865798426394 ], [ 13.298144937390786, 52.501872930789617 ], [ 13.298129122478487, 52.501878650974966 ], [ 13.298115578663928, 52.501876077066051 ], [ 13.298106388908243, 52.501861671314465 ], [ 13.298090988787996, 52.501856691623424 ], [ 13.298101187328278, 52.50184494385941 ], [ 13.298109393251526, 52.501834356967215 ], [ 13.298138797874058, 52.50183002181506 ], [ 13.298168248378614, 52.501824498677166 ], [ 13.298167835112215, 52.501835197671511 ], [ 13.298188758297401, 52.501848582665261 ], [ 13.298186259108586, 52.501862820266574 ], [ 13.298185616236259, 52.501879463645899 ] ], [ [ 13.29792263056447, 52.501875680329022 ], [ 13.297912660143782, 52.501881483671838 ], [ 13.297920039429773, 52.501892295677514 ], [ 13.297931267678686, 52.501904351696545 ], [ 13.297931681003549, 52.501893652703657 ], [ 13.297972545018277, 52.501895430146909 ], [ 13.297985905155251, 52.501902758681055 ], [ 13.29799239259048, 52.501886199385623 ], [ 13.298031169187839, 52.501891515390739 ], [ 13.298081772546443, 52.501893432908076 ], [ 13.298081221483587, 52.501907698532577 ], [ 13.298090549014018, 52.501918537655214 ], [ 13.298080394905794, 52.501929096519916 ], [ 13.298066254050438, 52.501941978005142 ], [ 13.298063846666837, 52.501953837849442 ], [ 13.298049796212265, 52.501964340658944 ], [ 13.298047480674594, 52.501973822749036 ], [ 13.298031896817264, 52.501973598557676 ], [ 13.298012508481307, 52.501970940556546 ], [ 13.297995068278993, 52.501968311478201 ], [ 13.297983700776189, 52.501959821175866 ], [ 13.297974191025183, 52.501953737573558 ], [ 13.297954893085709, 52.501948701788251 ], [ 13.297941073688365, 52.501953261120242 ], [ 13.297934769938937, 52.501965064905178 ], [ 13.297911393422755, 52.501964728584547 ], [ 13.297880178314081, 52.501965469019538 ], [ 13.297862967810984, 52.501956894639278 ], [ 13.297849378057574, 52.501955509576611 ], [ 13.297823869654941, 52.501959900716081 ], [ 13.297813991031642, 52.501963327195341 ], [ 13.297796688677302, 52.501957130559028 ], [ 13.297806979183864, 52.501943005067893 ], [ 13.297799463557993, 52.501935760605427 ], [ 13.297785458979865, 52.501945074506345 ], [ 13.297762358042245, 52.501937605794602 ], [ 13.297753261730232, 52.501920822284369 ], [ 13.297761925612873, 52.50189834662882 ], [ 13.297781131641836, 52.501905760198788 ], [ 13.297794583614685, 52.501910711000768 ], [ 13.297796991068026, 52.501898851162487 ], [ 13.297789657709242, 52.501886851171214 ], [ 13.297782188028448, 52.501878417830959 ], [ 13.297802127496622, 52.5018668093483 ], [ 13.297796880099497, 52.501851270757584 ], [ 13.297807126120954, 52.501838334163949 ], [ 13.297805820975979, 52.50182166275745 ], [ 13.297827341083977, 52.501819594213352 ], [ 13.297868249482956, 52.501820182794063 ], [ 13.297879616962559, 52.501828672208624 ], [ 13.297898913368373, 52.501833707984602 ], [ 13.297923732791116, 52.501847149082209 ], [ 13.297929026161501, 52.501861498790824 ], [ 13.29792263056447, 52.501875680329022 ] ], [ [ 13.297618550865932, 52.501896283596118 ], [ 13.297631910908644, 52.501903613068336 ], [ 13.297639427973909, 52.501910857562379 ], [ 13.297670457830343, 52.50191487267606 ], [ 13.297654276859074, 52.501930102933159 ], [ 13.297655857567063, 52.501939641080675 ], [ 13.297655214492652, 52.50195628445519 ], [ 13.297635412756124, 52.501964326282348 ], [ 13.297615932574718, 52.501964045969714 ], [ 13.297602662919704, 52.501954338721205 ], [ 13.297585406502828, 52.501946954076246 ], [ 13.297595834888115, 52.501929261974183 ], [ 13.297596477984326, 52.501912618600073 ], [ 13.297618550865932, 52.501896283596118 ] ], [ [ 13.297094452627164, 52.501941077780998 ], [ 13.297086246441166, 52.501951664600405 ], [ 13.297070662599449, 52.501951440282525 ], [ 13.297053406309372, 52.501944054662097 ], [ 13.297033926142847, 52.501943774253753 ], [ 13.297032529382161, 52.501929480594008 ], [ 13.297025288134405, 52.501915102804624 ], [ 13.297014335819483, 52.501895914342747 ], [ 13.297024580642043, 52.501882976899218 ], [ 13.297057514100711, 52.501888209113766 ], [ 13.297069339453236, 52.501884810719304 ], [ 13.29710050856225, 52.501885259374163 ], [ 13.297117810788356, 52.501891456111316 ], [ 13.297141095355704, 52.501894170341082 ], [ 13.297130942500116, 52.501904729143646 ], [ 13.297124592591222, 52.50191772176003 ], [ 13.297123995267372, 52.501933176253679 ], [ 13.29711996108797, 52.501936686799361 ], [ 13.297102429087634, 52.501936434443998 ], [ 13.297094452627164, 52.501941077780998 ] ], [ [ 13.296842834044, 52.501945781548741 ], [ 13.296807722609651, 52.501946465608164 ], [ 13.296760969624875, 52.501945792517169 ], [ 13.296737776977642, 52.501940700460402 ], [ 13.296728541700608, 52.501927483481751 ], [ 13.296729185124304, 52.501910840115194 ], [ 13.296735443226074, 52.501900225273154 ], [ 13.296747362000032, 52.501894449180149 ], [ 13.296755612743976, 52.501882673487458 ], [ 13.296787241408481, 52.501871234362277 ], [ 13.296788775967006, 52.501881961395966 ], [ 13.296806261983646, 52.501883402673748 ], [ 13.296814146545566, 52.501881137108562 ], [ 13.296837660844275, 52.501877907915741 ], [ 13.2968449020247, 52.501892285716117 ], [ 13.296838827783635, 52.501898145059528 ], [ 13.296836374114447, 52.501911193752292 ], [ 13.296839948730794, 52.501919571978021 ], [ 13.296845240282687, 52.501933921710844 ], [ 13.296842834044, 52.501945781548741 ] ], [ [ 13.298254965088338, 52.501900681542686 ], [ 13.298264981388835, 52.501893689293645 ], [ 13.298280106060181, 52.501905801325961 ], [ 13.298307332957902, 52.501907382494878 ], [ 13.29832472720287, 52.501911201302867 ], [ 13.298348103695673, 52.501911537541623 ], [ 13.298344528792104, 52.501903159359003 ], [ 13.298360114099571, 52.501903383530177 ], [ 13.298381404675032, 52.501907258372349 ], [ 13.298380807792133, 52.501922712876116 ], [ 13.298370516000753, 52.501936838397576 ], [ 13.298389950261225, 52.501938307463021 ], [ 13.298401639251397, 52.501938475587181 ], [ 13.298407162334, 52.501946881788641 ], [ 13.298416442564875, 52.5019589097422 ], [ 13.298412270771824, 52.501965986065528 ], [ 13.298384677972352, 52.501973915958381 ], [ 13.298380643923362, 52.501977425649159 ], [ 13.298362972670796, 52.501980740090538 ], [ 13.298345670195955, 52.501974544432429 ], [ 13.298324241836303, 52.501974236214821 ], [ 13.298314501003611, 52.501974096104995 ], [ 13.298295112649365, 52.501971438150015 ], [ 13.298265891626414, 52.501971017832119 ], [ 13.298242744705865, 52.501964737191827 ], [ 13.298219781441967, 52.501953701937673 ], [ 13.298220470224219, 52.501935869681255 ], [ 13.298217126386856, 52.501921548029785 ], [ 13.29823915165246, 52.501906401763939 ], [ 13.298254965088338, 52.501900681542686 ] ], [ [ 13.298720502304306, 52.501908566598431 ], [ 13.298742114229691, 52.501904120133304 ], [ 13.298762007553945, 52.501893700364221 ], [ 13.298787194488989, 52.501897631162848 ], [ 13.298780936879472, 52.501908246117601 ], [ 13.298801952063675, 52.501919254148547 ], [ 13.29881109615444, 52.501934848725327 ], [ 13.298814349848483, 52.501951548137541 ], [ 13.298802293643483, 52.501960890174381 ], [ 13.298788428421895, 52.501966638483438 ], [ 13.298793859805407, 52.501977421523556 ], [ 13.298793676217249, 52.501982176134334 ], [ 13.298779855421378, 52.501986735543788 ], [ 13.298764271553559, 52.501986511448827 ], [ 13.2987352800153, 52.501980147758786 ], [ 13.298721736132686, 52.501977573918673 ], [ 13.29871621298601, 52.501969167731424 ], [ 13.298697100023245, 52.501959377476979 ], [ 13.298699644961639, 52.501943950986444 ], [ 13.298700287641337, 52.501927307602891 ], [ 13.298702785195868, 52.501913069968325 ], [ 13.298720502304306, 52.501908566598431 ] ], [ [ 13.29939302036232, 52.502002688407444 ], [ 13.299366161956128, 52.501991596487123 ], [ 13.299354931817245, 52.501979540579839 ], [ 13.299343519593521, 52.501972240203379 ], [ 13.299318378455185, 52.501967120644792 ], [ 13.299291426797938, 52.501958407340609 ], [ 13.299278156771148, 52.501948700276884 ], [ 13.299272725271978, 52.501937917257983 ], [ 13.29929048822639, 52.50193222492468 ], [ 13.299319571564794, 52.501936211622386 ], [ 13.299331121414088, 52.501939946266617 ], [ 13.29934517155875, 52.501929443302252 ], [ 13.299347393595962, 52.501922338019902 ], [ 13.299347806577304, 52.501911639019191 ], [ 13.29937132075281, 52.501908408422715 ], [ 13.299402352248213, 52.501912423104585 ], [ 13.299417386936783, 52.50192691277082 ], [ 13.29943264960473, 52.501935458025258 ], [ 13.299418737110441, 52.501942395265566 ], [ 13.299424076868693, 52.501955556033536 ], [ 13.299449401565232, 52.501955920058698 ], [ 13.299492487726219, 52.5019505916935 ], [ 13.299516185422595, 52.501942606455749 ], [ 13.299533581262777, 52.501946425108152 ], [ 13.299569997015759, 52.501962411610592 ], [ 13.299588880755737, 52.501978146114858 ], [ 13.299603961412009, 52.501991446880083 ], [ 13.299595893467732, 52.501998467244952 ], [ 13.299576229712899, 52.502002942766239 ], [ 13.299558789434084, 52.50200031301889 ], [ 13.299563097229672, 52.501989669999013 ], [ 13.299531837707855, 52.501991599768957 ], [ 13.29951402890806, 52.501998480117656 ], [ 13.299490835902786, 52.501993388597732 ], [ 13.299457719982632, 52.501992912589756 ], [ 13.299446306265713, 52.501985612201935 ], [ 13.299422883831545, 52.501986465057527 ], [ 13.299410645660597, 52.502000562689418 ], [ 13.29939302036232, 52.502002688407444 ] ], [ [ 13.299727948707384, 52.502011070132866 ], [ 13.299726367547949, 52.502001532009345 ], [ 13.2997460312875, 52.501997056462955 ], [ 13.299744450124582, 52.501987518339632 ], [ 13.299734892803125, 52.501982622834959 ], [ 13.299723526395441, 52.501974133616748 ], [ 13.299710210352041, 52.501965616377205 ], [ 13.299712892579173, 52.501946623228179 ], [ 13.299732556295373, 52.501942147683785 ], [ 13.299759829154851, 52.501942539641121 ], [ 13.29975824799063, 52.50193300151787 ], [ 13.299737003156233, 52.501927938044439 ], [ 13.299733427951645, 52.501919560800218 ], [ 13.299757033864106, 52.501913952370955 ], [ 13.299786025477626, 52.501920316710184 ], [ 13.299787331374489, 52.501936988100958 ], [ 13.299798653379156, 52.501946666212262 ], [ 13.299804221171362, 52.501953882553003 ], [ 13.299795879466343, 52.501968036220902 ], [ 13.299809331654984, 52.501972987694408 ], [ 13.299821020662961, 52.501973155678449 ], [ 13.299824595853703, 52.5019815338188 ], [ 13.299814809168321, 52.501982581811838 ], [ 13.299802936643898, 52.501987169339024 ], [ 13.2997947784469, 52.501996567494849 ], [ 13.299792646762659, 52.502001295009002 ], [ 13.299782630642145, 52.502008288287705 ], [ 13.299763104539993, 52.502009197206313 ], [ 13.299727948707384, 52.502011070132866 ] ], [ [ 13.299113236066315, 52.501979634567732 ], [ 13.299075856677808, 52.501988612556801 ], [ 13.29905080732545, 52.501981116085126 ], [ 13.29902199934593, 52.501969996954998 ], [ 13.299012855173162, 52.501954402393906 ], [ 13.299011733168213, 52.501932975487122 ], [ 13.299023927019746, 52.501920066795975 ], [ 13.299028649501684, 52.501898723920071 ], [ 13.299048818079529, 52.501881172634292 ], [ 13.299053402849294, 52.501863396389723 ], [ 13.299077008814946, 52.501857788096565 ], [ 13.299100293494021, 52.5018605019416 ], [ 13.29911379146702, 52.501864264615833 ], [ 13.299105679044576, 52.501872473845701 ], [ 13.299089772459135, 52.501880571915663 ], [ 13.299091169886598, 52.501894865556906 ], [ 13.299104761136181, 52.50189625049827 ], [ 13.299114546380034, 52.501895201644189 ], [ 13.299133751202419, 52.501902614076691 ], [ 13.299135010955514, 52.5019204743502 ], [ 13.299126852629726, 52.501929872458916 ], [ 13.299140304729887, 52.501934824007847 ], [ 13.299149448944117, 52.501950418559474 ], [ 13.299133221097142, 52.501966837879394 ], [ 13.299113236066315, 52.501979634567732 ] ], [ [ 13.297060362969674, 52.501814503275988 ], [ 13.297089629821974, 52.501813735014331 ], [ 13.297095243067778, 52.501819763501132 ], [ 13.297091162945314, 52.501824462922258 ], [ 13.297108374751774, 52.501833036535857 ], [ 13.297114173278231, 52.5018343095376 ], [ 13.297125401290463, 52.501846365631216 ], [ 13.297123131476843, 52.501854659724032 ], [ 13.297111122367605, 52.501862812730671 ], [ 13.297091916472423, 52.501855399052978 ], [ 13.297082085241408, 52.501857636615817 ], [ 13.297073833116501, 52.501869412310882 ], [ 13.297058387126265, 52.50186562226132 ], [ 13.297050870199671, 52.501858376832537 ], [ 13.297031757660644, 52.501848586312406 ], [ 13.297036159427256, 52.501835565657998 ], [ 13.297044410083084, 52.501823789944318 ], [ 13.297060362969674, 52.501814503275988 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 10, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299679710092541, 52.502959944043326 ], [ 13.299693437866603, 52.502957762265503 ], [ 13.299712919980811, 52.502958042260985 ], [ 13.299719267813908, 52.502945049481511 ], [ 13.299745005983057, 52.502934714447207 ], [ 13.299739895425326, 52.502915609305248 ], [ 13.299740491852997, 52.502900154793195 ], [ 13.299739003899345, 52.502888238936571 ], [ 13.299705977595167, 52.502885385215833 ], [ 13.299657275335838, 52.502884685261265 ], [ 13.29962053616379, 52.502877020005322 ], [ 13.299595258293859, 52.502875467155832 ], [ 13.299579167886478, 52.502888320798611 ], [ 13.299585905413995, 52.502915775193344 ], [ 13.299589297123688, 52.502928907951748 ], [ 13.299596400241334, 52.502946852202122 ], [ 13.299621358464519, 52.502956726317841 ], [ 13.299640611155093, 52.502962950713588 ], [ 13.299669418465179, 52.502974069676917 ], [ 13.299679710092541, 52.502959944043326 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 11, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299948533740141, 52.503468775222601 ], [ 13.299945995587674, 52.503468150724771 ], [ 13.299946372526126, 52.503469035481565 ], [ 13.299948533740141, 52.503468775222601 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 12, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299121894947627, 52.502878178926231 ], [ 13.299094667417265, 52.502876597939697 ], [ 13.299075003219404, 52.502881073373231 ], [ 13.299056551419921, 52.50290459703249 ], [ 13.299054052443458, 52.50291883465156 ], [ 13.299065328632901, 52.502929701709611 ], [ 13.299076880191203, 52.502933436401236 ], [ 13.299100073639234, 52.502938528005338 ], [ 13.299115657846386, 52.50293875205891 ], [ 13.299141166695584, 52.502934360645305 ], [ 13.29915526163246, 52.502922668809695 ], [ 13.299153680568999, 52.502913130680724 ], [ 13.299158035961048, 52.502901297923941 ], [ 13.299148847129009, 52.502886892272194 ], [ 13.299121894947627, 52.502878178926231 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 13, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299706503814821, 52.503421578199841 ], [ 13.299730110505051, 52.503415970682795 ], [ 13.299734157172278, 52.503416028839943 ], [ 13.299665630157659, 52.503399168198001 ], [ 13.299664379742834, 52.503401941997474 ], [ 13.299646709481246, 52.503405256646943 ], [ 13.299634836587613, 52.503409843254204 ], [ 13.299625051017921, 52.503410892148324 ], [ 13.299621709720128, 52.503410844125398 ], [ 13.29966722838723, 52.503420672067406 ], [ 13.299677281808929, 52.503421158222423 ], [ 13.299706503814821, 52.503421578199841 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 14, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.29954772268942, 52.503394317636868 ], [ 13.299544986625637, 52.503394278310893 ], [ 13.299550521735823, 52.503395473677209 ], [ 13.29954772268942, 52.503394317636868 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 15, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297606826984353, 52.502866829704168 ], [ 13.29758966209217, 52.502857067305015 ], [ 13.297581823333013, 52.502858144043977 ], [ 13.297573156820615, 52.502862683075833 ], [ 13.297561929328152, 52.502868562708301 ], [ 13.297548603914818, 52.50287825049918 ], [ 13.297545885784542, 52.502880226315675 ], [ 13.297540167313857, 52.502882292026548 ], [ 13.2976336986512, 52.50290766604445 ], [ 13.2976365268715, 52.502905319573905 ], [ 13.297636092661557, 52.502899779234276 ], [ 13.297635221733998, 52.502888649067266 ], [ 13.297626606606833, 52.502877482991551 ], [ 13.297624085199516, 52.502874215267802 ], [ 13.297606826984353, 52.502866829704168 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 16, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299006359927798, 52.502891980925163 ], [ 13.299027927916546, 52.502888722412258 ], [ 13.299037667482422, 52.502888862443882 ], [ 13.299049861597364, 52.502875953754227 ], [ 13.299034413642454, 52.502872163039051 ], [ 13.299007782803056, 52.502855127526132 ], [ 13.298984589375092, 52.502850036802947 ], [ 13.29895332925482, 52.502851965515667 ], [ 13.298933665054188, 52.50285644092574 ], [ 13.298925275517163, 52.502871783382425 ], [ 13.29892681062627, 52.50288251039138 ], [ 13.298930202136694, 52.502895643167051 ], [ 13.298953349644387, 52.502901923676902 ], [ 13.298968612519747, 52.502910469890537 ], [ 13.298990224949579, 52.502906022486172 ], [ 13.299006359927798, 52.502891980925163 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 17, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299424465821042, 52.503355673026583 ], [ 13.29944037141216, 52.503347574896345 ], [ 13.299457676017402, 52.503353771318658 ], [ 13.299468492888259, 52.503350662989995 ], [ 13.299100851847712, 52.503260202462137 ], [ 13.299100626324533, 52.503261807747364 ], [ 13.299073077290807, 52.503268547996228 ], [ 13.299041907171517, 52.503268099852711 ], [ 13.299026001537365, 52.503276197929083 ], [ 13.299027039127512, 52.503282448251831 ], [ 13.299264931597611, 52.503333809469083 ], [ 13.299298802070426, 52.503328887259791 ], [ 13.299339759267463, 52.50332828648731 ], [ 13.299376405506132, 52.503338329566375 ], [ 13.299389903953347, 52.503342092212222 ], [ 13.29940113444075, 52.503354148116593 ], [ 13.299424465821042, 52.503355673026583 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 18, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298866582440935, 52.502826929266192 ], [ 13.298845245472522, 52.502824243385007 ], [ 13.29882553536749, 52.502829907654352 ], [ 13.298797894750708, 52.502839025595897 ], [ 13.298799246199735, 52.502854508114972 ], [ 13.298810522309665, 52.502865375196777 ], [ 13.298825739238067, 52.50287511030573 ], [ 13.298856817264907, 52.502877936258265 ], [ 13.298876389672767, 52.502875838615566 ], [ 13.298896053887674, 52.502871363215164 ], [ 13.29889655880153, 52.50285828646421 ], [ 13.298899103729237, 52.502842859971544 ], [ 13.298893580409114, 52.502834454691111 ], [ 13.29887604803552, 52.502834202595515 ], [ 13.298870432945471, 52.502828174169906 ], [ 13.298866582440935, 52.502826929266192 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 19, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298982982937124, 52.503231200598101 ], [ 13.298816268159539, 52.50319017901429 ], [ 13.298835204652773, 52.503199707759919 ], [ 13.298862111065317, 52.503209610049119 ], [ 13.298875427313149, 52.503218127384152 ], [ 13.298859290699195, 52.503232168904006 ], [ 13.298838535479518, 52.503234521071398 ], [ 13.298933767094201, 52.503260353674129 ], [ 13.298933087886564, 52.503259398931938 ], [ 13.298948857293023, 52.50325486752088 ], [ 13.298978262789003, 52.503250532168437 ], [ 13.29898057820018, 52.503241050060957 ], [ 13.298982982937124, 52.503231200598101 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 20, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.29924015681434, 52.502844194781837 ], [ 13.299254297590524, 52.502831314056351 ], [ 13.299256750602149, 52.502818265309735 ], [ 13.299263191809526, 52.502802894821606 ], [ 13.299232572724639, 52.502788181100115 ], [ 13.299229135236109, 52.502776237209652 ], [ 13.299207935956117, 52.502769984761528 ], [ 13.299188501299097, 52.502768515827348 ], [ 13.299171014845861, 52.502767074898252 ], [ 13.299162747269374, 52.502756361695241 ], [ 13.299161732769834, 52.502755046981086 ], [ 13.299154741953638, 52.502754617398828 ], [ 13.299136453515647, 52.50275349401312 ], [ 13.299108996569188, 52.502757857417471 ], [ 13.299102694483809, 52.502769661285917 ], [ 13.299088965268298, 52.502771842974326 ], [ 13.299098017833563, 52.502789815283869 ], [ 13.299101363489877, 52.502804136932589 ], [ 13.29908708350812, 52.502820584250422 ], [ 13.299100307886439, 52.502831479314835 ], [ 13.299117886159372, 52.502830542499815 ], [ 13.299132919587462, 52.502845032180709 ], [ 13.299146327540324, 52.502851172629128 ], [ 13.299169475091261, 52.502857452198192 ], [ 13.299182881580505, 52.502863592621352 ], [ 13.299198650819426, 52.502859061175279 ], [ 13.299214556235331, 52.502850963972236 ], [ 13.29924015681434, 52.502844194781837 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 21, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298805550824005, 52.503187542431604 ], [ 13.298703719631215, 52.503162485169149 ], [ 13.298701520759456, 52.503178753715567 ], [ 13.298685661041739, 52.50318566197037 ], [ 13.298710710975698, 52.503193159421762 ], [ 13.298726157541129, 52.503196950158568 ], [ 13.298737569973829, 52.503204250594933 ], [ 13.298747545756076, 52.503209836426983 ], [ 13.298756310536515, 52.503212213863243 ], [ 13.298772452852546, 52.503209510356562 ], [ 13.298790582141669, 52.503194307965046 ], [ 13.298805550824005, 52.503187542431604 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 22, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298735765966162, 52.502782227535192 ], [ 13.298718463160109, 52.502776031030699 ], [ 13.29870264799367, 52.502781751289774 ], [ 13.298695132113977, 52.502774505984128 ], [ 13.298697446086004, 52.502765023860618 ], [ 13.298736500468779, 52.502763206399365 ], [ 13.298757929216638, 52.502763514545549 ], [ 13.298756531876212, 52.502749220903091 ], [ 13.298756990921284, 52.502737333029977 ], [ 13.298768863756171, 52.502732745610956 ], [ 13.298777160032488, 52.502719780898438 ], [ 13.298766160885835, 52.502701780571677 ], [ 13.298768659941288, 52.502687542958917 ], [ 13.298739576126678, 52.502683557021086 ], [ 13.298716520582341, 52.502674898716116 ], [ 13.298712853697046, 52.502668899197516 ], [ 13.298691562724558, 52.50266502441211 ], [ 13.298672357683481, 52.502657611007898 ], [ 13.298653059320422, 52.502652576232393 ], [ 13.298616092437362, 52.502650855074101 ], [ 13.298608529209483, 52.502644798619038 ], [ 13.29860041651194, 52.502653008709807 ], [ 13.298565029085797, 52.50266082565004 ], [ 13.298560489942259, 52.502677412993961 ], [ 13.29856369767648, 52.502695301287744 ], [ 13.298549694485303, 52.502704615298491 ], [ 13.298555170337792, 52.502714209450275 ], [ 13.29855438984479, 52.502734419461518 ], [ 13.29854632303401, 52.502741440671194 ], [ 13.29853027832249, 52.50275310439379 ], [ 13.298525924285167, 52.50276493624898 ], [ 13.29850621269442, 52.502770600443867 ], [ 13.298500002276301, 52.502780026524796 ], [ 13.298487808028758, 52.502792935156812 ], [ 13.298494958063317, 52.502809690613923 ], [ 13.29850641627316, 52.502815802194867 ], [ 13.298517554522157, 52.502830236833546 ], [ 13.298536713731806, 52.502838838241473 ], [ 13.298541580297877, 52.5028416429085 ], [ 13.298555781087154, 52.502849818299019 ], [ 13.298579020376495, 52.502853720228778 ], [ 13.298602232993046, 52.502854726600184 ], [ 13.298602293038611, 52.502854736005332 ], [ 13.298619885312034, 52.502855497455961 ], [ 13.298657036010253, 52.502852463106059 ], [ 13.298669184314983, 52.502840743333543 ], [ 13.298675440616309, 52.502830128366512 ], [ 13.298693525342214, 52.502816114886208 ], [ 13.298719309614688, 52.502804590300485 ], [ 13.298741335158155, 52.502789443945581 ], [ 13.298735765966162, 52.502782227535192 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 23, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298543208334486, 52.503122990099726 ], [ 13.298352279492166, 52.503076009097732 ], [ 13.298356437445189, 52.503079822733866 ], [ 13.298367254310698, 52.503102578625601 ], [ 13.298367097776246, 52.503106631410681 ], [ 13.298409364283774, 52.503118094420884 ], [ 13.298411784872556, 52.503110355434103 ], [ 13.298448707687029, 52.503113265557424 ], [ 13.298483634900933, 52.503117336505277 ], [ 13.298493238240018, 52.503121043234074 ], [ 13.298516431750972, 52.503126134054092 ], [ 13.298530067802094, 52.503126330166161 ], [ 13.298543208334486, 52.503122990099726 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 24, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298305716377858, 52.503064552598779 ], [ 13.297868302310833, 52.502956918389067 ], [ 13.29787178375668, 52.502961041497464 ], [ 13.297878326254931, 52.502974029025758 ], [ 13.298261009893841, 52.503077848620315 ], [ 13.298265106787129, 52.503072562274873 ], [ 13.298286719364947, 52.503068115000403 ], [ 13.298305716377858, 52.503064552598779 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 25, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.29641806455512, 52.502530941505476 ], [ 13.296407208783656, 52.50253673197399 ], [ 13.296415229888289, 52.502530900686899 ], [ 13.29641806455512, 52.502530941505476 ], [ 13.296431091224317, 52.502523991859334 ], [ 13.296434925227574, 52.502519221482558 ], [ 13.296443376460884, 52.502508705670436 ], [ 13.296443744186005, 52.502499195562699 ], [ 13.296434954387628, 52.502491870626507 ], [ 13.296424861361659, 52.502483460565379 ], [ 13.296429539196655, 52.502463306682195 ], [ 13.296439692350782, 52.502452747943337 ], [ 13.296451657283665, 52.502445783006415 ], [ 13.296475953347896, 52.502422343898253 ], [ 13.296478453124465, 52.502408106339345 ], [ 13.296463189403999, 52.502399560686733 ], [ 13.296448632062459, 52.502390236674046 ], [ 13.296448577182723, 52.502390208011022 ], [ 13.296390089097013, 52.502377589102025 ], [ 13.296390024235373, 52.502377589966251 ], [ 13.296362672720043, 52.502377892020668 ], [ 13.296325612754558, 52.502378547879644 ], [ 13.296322837126963, 52.502399917790775 ], [ 13.296296960531159, 52.502413819603071 ], [ 13.296286899300439, 52.502422000578129 ], [ 13.296286163749599, 52.502441021690871 ], [ 13.296281945594428, 52.502449286810908 ], [ 13.29627726769368, 52.502469440687726 ], [ 13.296246096747051, 52.502468990892758 ], [ 13.296204592643997, 52.502519947005418 ], [ 13.297290312081708, 52.502814508015561 ], [ 13.29729905713223, 52.502811254079923 ], [ 13.297309257616185, 52.502799505512215 ], [ 13.297317632137176, 52.502794630521407 ], [ 13.297319226853169, 52.502793702202382 ], [ 13.297346959631616, 52.502782206855997 ], [ 13.297357206041729, 52.502769269407644 ], [ 13.297379277940205, 52.50275293533236 ], [ 13.297381383808954, 52.502739428468139 ], [ 13.297381869336592, 52.50273632000021 ], [ 13.297380426498584, 52.502723215221543 ], [ 13.297375225100716, 52.502706487738372 ], [ 13.297395027220379, 52.502698445958991 ], [ 13.297397296995294, 52.50269015276065 ], [ 13.29737025343308, 52.502683815873304 ], [ 13.297333332529329, 52.502680905438112 ], [ 13.297332827120126, 52.502693983077442 ], [ 13.297318272346542, 52.502717562557166 ], [ 13.297317721020153, 52.502731828173971 ], [ 13.297311554829239, 52.502740065292976 ], [ 13.297282011677259, 52.502747966861818 ], [ 13.297280385112204, 52.50273961668865 ], [ 13.29729439005218, 52.502730302851788 ], [ 13.29729290129295, 52.502718386948544 ], [ 13.297275597246974, 52.502712190213686 ], [ 13.297276194534453, 52.502696735720846 ], [ 13.297268816714922, 52.502685924596371 ], [ 13.297245439824485, 52.502685588140984 ], [ 13.297241175875255, 52.502695042172491 ], [ 13.297231021347184, 52.502705601858302 ], [ 13.297209914291987, 52.502696971302335 ], [ 13.297192519838701, 52.502693152329122 ], [ 13.297173269057932, 52.502686928458495 ], [ 13.297145903871719, 52.502688913653529 ], [ 13.297128186326798, 52.502693415884401 ], [ 13.297104993209173, 52.502688324795152 ], [ 13.297103643832308, 52.502672842282607 ], [ 13.297101237663517, 52.502667204323807 ], [ 13.297096494349875, 52.502656086745723 ], [ 13.297100758360768, 52.50264663182103 ], [ 13.297109055108514, 52.502633667229382 ], [ 13.297091706607153, 52.502628660264008 ], [ 13.297070094164976, 52.502633106422685 ], [ 13.297054280313837, 52.502638826481359 ], [ 13.297038878573398, 52.50263384663149 ], [ 13.29703349497084, 52.502621874660001 ], [ 13.297020179261439, 52.502613357121326 ], [ 13.297017427805418, 52.502595459165505 ], [ 13.297017064069466, 52.502593091038541 ], [ 13.297013791420929, 52.502593441341382 ], [ 13.29699749170601, 52.502595188374045 ], [ 13.296981723814982, 52.502599719546986 ], [ 13.296967141480804, 52.502603425307335 ], [ 13.296964006289922, 52.502604221753501 ], [ 13.296934464641776, 52.502612123257116 ], [ 13.296910812083926, 52.502618919093763 ], [ 13.296904553918163, 52.502629533943285 ], [ 13.296890778685341, 52.50263290425211 ], [ 13.296861512758728, 52.502633672481068 ], [ 13.296842352498066, 52.502625070783893 ], [ 13.296828946412919, 52.502618930098635 ], [ 13.296810109359505, 52.502602006285493 ], [ 13.296786962288779, 52.502595726258555 ], [ 13.2967601486831, 52.502583444852988 ], [ 13.296701659909763, 52.502583792321104 ], [ 13.296645258659787, 52.502580602102199 ], [ 13.296617985449533, 52.502580209423762 ], [ 13.29660512949131, 52.502559803080274 ], [ 13.296591907289001, 52.502548907763639 ], [ 13.296571930328899, 52.502540358100568 ], [ 13.296568898212847, 52.502539060168267 ], [ 13.296541854831579, 52.50253272399182 ], [ 13.296532386961943, 52.50253664809884 ], [ 13.296527989174175, 52.502538471132794 ], [ 13.296498767802065, 52.502538050375428 ], [ 13.296487308473958, 52.502531938581505 ], [ 13.29641806455512, 52.502530941505476 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 26, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298453254595145, 52.502678249801271 ], [ 13.298455340540912, 52.502674711190259 ], [ 13.298478350151978, 52.502684557524098 ], [ 13.298492031981219, 52.502683564762407 ], [ 13.298500283886767, 52.502671788968044 ], [ 13.298516510760733, 52.502655369719825 ], [ 13.29852879678889, 52.50264008333005 ], [ 13.298519560829495, 52.502626866486942 ], [ 13.298519882193995, 52.502618545246236 ], [ 13.298490477078749, 52.502622880488403 ], [ 13.298475443937958, 52.502608390727744 ], [ 13.298465381589759, 52.502616571869702 ], [ 13.298453556144011, 52.502619970402556 ], [ 13.298447987054994, 52.502612753080392 ], [ 13.2984385215439, 52.502605480616147 ], [ 13.298430638405138, 52.502607746310126 ], [ 13.29841899515414, 52.50260638931038 ], [ 13.298411295682529, 52.502603899495092 ], [ 13.298407858430439, 52.50259195558241 ], [ 13.29838448157599, 52.502591619352465 ], [ 13.298374556969124, 52.502596233855684 ], [ 13.298358605541239, 52.502605520721183 ], [ 13.298350445448319, 52.502614918751661 ], [ 13.298342285351888, 52.502624316781564 ], [ 13.298339602486694, 52.502643309892107 ], [ 13.298342947885903, 52.502657631560382 ], [ 13.298356217948964, 52.502667337829337 ], [ 13.298338638255034, 52.502668274510434 ], [ 13.298338454578481, 52.502673030018116 ], [ 13.298320922274122, 52.502672777840814 ], [ 13.298311228679504, 52.502671448873713 ], [ 13.298287759943038, 52.502673490378115 ], [ 13.298281548020027, 52.502682916426245 ], [ 13.298296672961984, 52.502695028457183 ], [ 13.298297978311169, 52.502711699857727 ], [ 13.298274371789873, 52.502717307989911 ], [ 13.298271918494045, 52.502730356713933 ], [ 13.298271275633946, 52.502747000091574 ], [ 13.298282735234023, 52.502753112613654 ], [ 13.2982863102258, 52.502761489898809 ], [ 13.298295820157588, 52.502767574375653 ], [ 13.298309411683405, 52.502768958511645 ], [ 13.298310991052105, 52.502778497527999 ], [ 13.298326301185668, 52.502785854068961 ], [ 13.298359325844688, 52.502788708152373 ], [ 13.298368975020599, 52.502791226013869 ], [ 13.298382473212584, 52.502794988772841 ], [ 13.298407706542852, 52.50279773077883 ], [ 13.298435301330192, 52.502789800907202 ], [ 13.298441696902231, 52.502775619343097 ], [ 13.298449857006371, 52.502766221306658 ], [ 13.298462049784797, 52.502753312657298 ], [ 13.298456893887924, 52.502735396341322 ], [ 13.298457490761644, 52.50271994183904 ], [ 13.29845238225889, 52.502700836666961 ], [ 13.298453254595145, 52.502678249801271 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 27, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297722784590649, 52.502921112119047 ], [ 13.297636092661557, 52.502899779234276 ], [ 13.2976365268715, 52.502905319573905 ], [ 13.2976336986512, 52.50290766604445 ], [ 13.297713286114993, 52.502929260850792 ], [ 13.297721256199086, 52.502922001836495 ], [ 13.297722784590649, 52.502921112119047 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 28, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298966307237571, 52.502717743393987 ], [ 13.298941073915556, 52.502715001507212 ], [ 13.298919553382172, 52.502717071148943 ], [ 13.298890195649594, 52.502720216735433 ], [ 13.298876191011281, 52.502729531662752 ], [ 13.298856389176906, 52.502737572793571 ], [ 13.298846372739526, 52.502744565989509 ], [ 13.298845867812849, 52.502757642740363 ], [ 13.298859183922842, 52.502766160076675 ], [ 13.298862483594556, 52.502781670608726 ], [ 13.298875752337569, 52.502791376799301 ], [ 13.298893284694156, 52.502791628894805 ], [ 13.298918242634498, 52.502801504055689 ], [ 13.298955211130368, 52.502803225141818 ], [ 13.298966898880828, 52.502803393190895 ], [ 13.298980949426086, 52.502792889375698 ], [ 13.298993096156037, 52.502781169547738 ], [ 13.298980148669163, 52.502763142127648 ], [ 13.298997726916978, 52.502762205330257 ], [ 13.299013678269585, 52.502752918378135 ], [ 13.299000684915589, 52.502736078938888 ], [ 13.298979669261241, 52.502725071839563 ], [ 13.298966307237571, 52.502717743393987 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 29, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297548603914818, 52.50287825049918 ], [ 13.297297833003908, 52.502816546672591 ], [ 13.297540167313857, 52.502882292026548 ], [ 13.297545885784542, 52.502880226315675 ], [ 13.297548603914818, 52.50287825049918 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 30, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.30016446593517, 52.502815455785885 ], [ 13.300180143284706, 52.502813302862606 ], [ 13.300199623867421, 52.502813582756282 ], [ 13.300207598692861, 52.502808939187929 ], [ 13.300212228969947, 52.50278997401994 ], [ 13.300222245168968, 52.502782981605279 ], [ 13.300238104538687, 52.502776072242128 ], [ 13.300247891426162, 52.502775023317028 ], [ 13.300263429717418, 52.502776436097065 ], [ 13.300265377921354, 52.502776464087304 ], [ 13.300267509594461, 52.502771736565137 ], [ 13.300263934223807, 52.502763359336598 ], [ 13.300254515718022, 52.502754898158152 ], [ 13.300252750985535, 52.502750114655363 ], [ 13.300235675806762, 52.502737974858029 ], [ 13.300232147820177, 52.502728407873057 ], [ 13.300234462932007, 52.502718925737703 ], [ 13.300232881640843, 52.502709387620882 ], [ 13.300221467645493, 52.502702086407425 ], [ 13.300202308159569, 52.502693485269859 ], [ 13.300179114707595, 52.502688393881662 ], [ 13.300161674120387, 52.502685764220907 ], [ 13.300152045035867, 52.50268562586902 ], [ 13.300151933121144, 52.502685624261005 ], [ 13.300133302199928, 52.502684479924397 ], [ 13.300126653866984, 52.502684071504667 ], [ 13.300099518123742, 52.50268011389263 ], [ 13.300074147164889, 52.502680937982475 ], [ 13.300054666642072, 52.502680658068641 ], [ 13.300054345531976, 52.502688980214529 ], [ 13.300077676586064, 52.50269050499309 ], [ 13.300083200115933, 52.50269891022014 ], [ 13.300077309642589, 52.502700015118442 ], [ 13.300055880910945, 52.502699707211434 ], [ 13.300042061488105, 52.502704266788179 ], [ 13.300035941680727, 52.50271131517556 ], [ 13.300030097078164, 52.50271123119348 ], [ 13.300014788153272, 52.502703874891949 ], [ 13.300008622467436, 52.502712112155848 ], [ 13.300008347222454, 52.502719245423549 ], [ 13.300008117886222, 52.502725188914674 ], [ 13.300003854488882, 52.502734643949267 ], [ 13.299993746517575, 52.502744014101154 ], [ 13.299993333680648, 52.502754713103897 ], [ 13.300006879351292, 52.502757286821911 ], [ 13.300000713621609, 52.502765524983879 ], [ 13.299998844644751, 52.502766613037792 ], [ 13.299984762504124, 52.502774811172159 ], [ 13.299984579004494, 52.502779566683813 ], [ 13.299994135087086, 52.502784461249632 ], [ 13.299998337144759, 52.502785225642718 ], [ 13.300015426236984, 52.502788335801988 ], [ 13.300034908279548, 52.502788615743825 ], [ 13.300054388849883, 52.502788895661311 ], [ 13.300063853162118, 52.502796168876074 ], [ 13.300079255344446, 52.502801147436728 ], [ 13.300081111804598, 52.502803553185927 ], [ 13.300086725586009, 52.502809581534379 ], [ 13.300108109972342, 52.502811078335355 ], [ 13.300121791808467, 52.502810085385242 ], [ 13.300133481040399, 52.502810253340279 ], [ 13.300144985351981, 52.502815175886425 ], [ 13.30016446593517, 52.502815455785885 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 31, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300319902220508, 52.502856274775873 ], [ 13.300310242501725, 52.502782648141384 ], [ 13.300300800541386, 52.502782891020075 ], [ 13.300296242168931, 52.502754050061959 ], [ 13.300306436555863, 52.502753628279173 ], [ 13.30029719387074, 52.502683247166615 ], [ 13.300182492944035, 52.502688934239146 ], [ 13.300153509085854, 52.502683156339522 ], [ 13.300152045035867, 52.50268562586902 ], [ 13.300161674120387, 52.502685764220907 ], [ 13.300179114707595, 52.502688393881662 ], [ 13.300202308159569, 52.502693485269859 ], [ 13.300221467645493, 52.502702086407425 ], [ 13.300232881640843, 52.502709387620882 ], [ 13.300234462932007, 52.502718925737703 ], [ 13.300232147820177, 52.502728407873057 ], [ 13.300235675806762, 52.502737974858029 ], [ 13.300252750985535, 52.502750114655363 ], [ 13.300254515718022, 52.502754898158152 ], [ 13.300263934223807, 52.502763359336598 ], [ 13.300267509594461, 52.502771736565137 ], [ 13.300265377921354, 52.502776464087304 ], [ 13.300263429717418, 52.502776436097065 ], [ 13.300247891426162, 52.502775023317028 ], [ 13.300238104538687, 52.502776072242128 ], [ 13.300222245168968, 52.502782981605279 ], [ 13.300212228969947, 52.50278997401994 ], [ 13.300207598692861, 52.502808939187929 ], [ 13.300199623867421, 52.502813582756282 ], [ 13.300180143284706, 52.502813302862606 ], [ 13.30016446593517, 52.502815455785885 ], [ 13.300144985351981, 52.502815175886425 ], [ 13.300133481040399, 52.502810253340279 ], [ 13.300121791808467, 52.502810085385242 ], [ 13.300108109972342, 52.502811078335355 ], [ 13.300086725586009, 52.502809581534379 ], [ 13.300081111804598, 52.502803553185927 ], [ 13.300079255344446, 52.502801147436728 ], [ 13.300063853162118, 52.502796168876074 ], [ 13.300054388849883, 52.502788895661311 ], [ 13.300034908279548, 52.502788615743825 ], [ 13.300015426236984, 52.502788335801988 ], [ 13.299998337144759, 52.502785225642718 ], [ 13.299997583948898, 52.502786569795084 ], [ 13.299995526471429, 52.502789831912317 ], [ 13.29999307076281, 52.502792990302929 ], [ 13.299990419914508, 52.502796089244853 ], [ 13.299987392525736, 52.502799056899008 ], [ 13.300074383756726, 52.502816704161191 ], [ 13.300170417830532, 52.502838077770726 ], [ 13.300170482073479, 52.50283721463849 ], [ 13.300170680146676, 52.502836357924707 ], [ 13.300170942770112, 52.502835508432256 ], [ 13.30017133435541, 52.502834676976939 ], [ 13.300171790282914, 52.502833858134601 ], [ 13.300172368831214, 52.502833068926833 ], [ 13.300173007096006, 52.502832297660383 ], [ 13.300173763182526, 52.502831565849874 ], [ 13.300174571449332, 52.502830856367957 ], [ 13.30017548688318, 52.50283019518011 ], [ 13.300176451309479, 52.502829562568941 ], [ 13.300177510775585, 52.502828987068767 ], [ 13.300178610294706, 52.502828442714211 ], [ 13.300179789815912, 52.502827963346661 ], [ 13.300181004833773, 52.502827518655771 ], [ 13.300182281974882, 52.5028271440897 ], [ 13.300183588583709, 52.502826807710107 ], [ 13.300184939436985, 52.502826546593219 ], [ 13.30018630938067, 52.502826325311965 ], [ 13.300187705759283, 52.50282618263401 ], [ 13.300189112392957, 52.502826079664757 ], [ 13.300190529124659, 52.502826058660503 ], [ 13.300191945838096, 52.502826076318243 ], [ 13.300193353471535, 52.502826176564618 ], [ 13.300194750744081, 52.50282631622342 ], [ 13.300196122877123, 52.502826534643653 ], [ 13.300197475813855, 52.502826792349424 ], [ 13.300198781591913, 52.502827126701973 ], [ 13.300200062352779, 52.502827498458196 ], [ 13.300201279999438, 52.502827940338108 ], [ 13.300202460952338, 52.502828416756579 ], [ 13.300203565676648, 52.502828959513813 ], [ 13.300204626517743, 52.502829532210733 ], [ 13.30020559520935, 52.502830163824676 ], [ 13.300206514266163, 52.50283082169917 ], [ 13.300207326759832, 52.502831530191585 ], [ 13.300208086881565, 52.502832259510463 ], [ 13.300208730478943, 52.502833030312914 ], [ 13.30020931300767, 52.502833818220424 ], [ 13.300209773572421, 52.502834635844799 ], [ 13.300210171769319, 52.502835466059885 ], [ 13.300210439548213, 52.502836315980097 ], [ 13.300210640818889, 52.50283717123861 ], [ 13.300210710649717, 52.50283803449895 ], [ 13.300210714111003, 52.502838899503061 ], [ 13.300210585110555, 52.502839760805792 ], [ 13.300210391455829, 52.502840617583111 ], [ 13.300210067262595, 52.502841458998034 ], [ 13.300209675712663, 52.502842289554934 ], [ 13.300209161368461, 52.502843094970373 ], [ 13.300208584258558, 52.50284388509813 ], [ 13.300207890660714, 52.502844639385579 ], [ 13.300207136047108, 52.502845371217475 ], [ 13.300206275600203, 52.502846058370984 ], [ 13.300297356020181, 52.502866331210456 ], [ 13.300319902220508, 52.502856274775873 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 32, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298267792488472, 52.502736245052198 ], [ 13.298268527191855, 52.502717223920826 ], [ 13.298269261894413, 52.502698202789418 ], [ 13.29827733017868, 52.502691182518689 ], [ 13.298270042482443, 52.502677993679669 ], [ 13.298258354769468, 52.502677825562046 ], [ 13.298246436010587, 52.502683600907794 ], [ 13.298218933101975, 52.502689152982107 ], [ 13.298203303067487, 52.50269011768804 ], [ 13.298193378419533, 52.502694732175641 ], [ 13.298194775575803, 52.5027090258233 ], [ 13.298165509613982, 52.502709794374567 ], [ 13.298155125716029, 52.502726297626992 ], [ 13.298164591170616, 52.502733571012158 ], [ 13.298188059944266, 52.502731529532568 ], [ 13.29820522492278, 52.502741291845787 ], [ 13.298226607731799, 52.50274278896493 ], [ 13.298242008175212, 52.502747768640354 ], [ 13.298267792488472, 52.502736245052198 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 33, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299979356583378, 52.502761029088489 ], [ 13.299980893422964, 52.502760988233788 ], [ 13.299982428441009, 52.502761032769399 ], [ 13.299983961759951, 52.502761121337521 ], [ 13.299985472641453, 52.502761294999715 ], [ 13.299986968605531, 52.502761511605271 ], [ 13.299988427475867, 52.502761811296125 ], [ 13.299989859717565, 52.502762151963857 ], [ 13.299991235860903, 52.502762571847249 ], [ 13.299992578082163, 52.502763030804488 ], [ 13.299993845339134, 52.502763561513326 ], [ 13.299995071380552, 52.502764129392965 ], [ 13.299996205029618, 52.50276476247997 ], [ 13.299997291780931, 52.50276542726138 ], [ 13.29999827316421, 52.502766149870673 ], [ 13.299998844644751, 52.502766613037792 ], [ 13.300000713621609, 52.502765524983879 ], [ 13.300006879351292, 52.502757286821911 ], [ 13.299993333680648, 52.502754713103897 ], [ 13.299993746517575, 52.502744014101154 ], [ 13.300003854488882, 52.502734643949267 ], [ 13.300008117886222, 52.502725188914674 ], [ 13.300008347222454, 52.502719245423549 ], [ 13.300008622467436, 52.502712112155848 ], [ 13.300014788153272, 52.502703874891949 ], [ 13.300030097078164, 52.50271123119348 ], [ 13.300035941680727, 52.50271131517556 ], [ 13.300042061488105, 52.502704266788179 ], [ 13.300055880910945, 52.502699707211434 ], [ 13.300077309642589, 52.502700015118442 ], [ 13.300083200115933, 52.50269891022014 ], [ 13.300077676586064, 52.50269050499309 ], [ 13.300054345531976, 52.502688980214529 ], [ 13.300054666642072, 52.502680658068641 ], [ 13.300074147164889, 52.502680937982475 ], [ 13.300099518123742, 52.50268011389263 ], [ 13.300126653866984, 52.502684071504667 ], [ 13.300133302199928, 52.502684479924397 ], [ 13.299982172303707, 52.502650943405826 ], [ 13.299982951506466, 52.502647054215501 ], [ 13.299954872948385, 52.502640650000608 ], [ 13.299925986861277, 52.502645665599836 ], [ 13.299919535973459, 52.502659613574302 ], [ 13.299925902765363, 52.50266281242817 ], [ 13.299878609300068, 52.50274737128197 ], [ 13.299892746476285, 52.502767261593405 ], [ 13.299954508397844, 52.502781217841765 ], [ 13.29995434051345, 52.502779879335449 ], [ 13.299954379065607, 52.502778536602619 ], [ 13.299954520627976, 52.502777197148284 ], [ 13.299954869440576, 52.502775870562417 ], [ 13.299955317971483, 52.502774556198915 ], [ 13.299955967134151, 52.502773273490341 ], [ 13.299956714230413, 52.502772011070547 ], [ 13.299957652498906, 52.502770796353964 ], [ 13.299958683971223, 52.502769609950285 ], [ 13.299959892703802, 52.502768488133185 ], [ 13.29996118552741, 52.502767401691003 ], [ 13.299962641768703, 52.502766394921501 ], [ 13.299964172953532, 52.502765431487589 ], [ 13.29996584648935, 52.502764559112165 ], [ 13.299967584452762, 52.502763735315952 ], [ 13.299969440720641, 52.50276301482041 ], [ 13.299971350865441, 52.502762349046307 ], [ 13.299973353934391, 52.502761795199305 ], [ 13.299974813705735, 52.502761498786029 ], [ 13.299976312991358, 52.502761285659638 ], [ 13.299977825407545, 52.502761114081466 ], [ 13.299979356583378, 52.502761029088489 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 34, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298175642900773, 52.502649277328601 ], [ 13.29817619394781, 52.502635011705188 ], [ 13.29819368178538, 52.502636452801852 ], [ 13.298213024502438, 52.502640299652413 ], [ 13.298238625072498, 52.502633531577445 ], [ 13.29825629512438, 52.502630217136137 ], [ 13.29825693798743, 52.50261357375831 ], [ 13.298261109903704, 52.502606496543379 ], [ 13.298273302691753, 52.502593587913218 ], [ 13.29825609183351, 52.502585014486904 ], [ 13.298244771482755, 52.502575336251617 ], [ 13.29823934019854, 52.502564552289243 ], [ 13.298218324818336, 52.50255354505672 ], [ 13.29818353437334, 52.502545907382384 ], [ 13.298148607606297, 52.502541837248238 ], [ 13.298140401556761, 52.502552424140909 ], [ 13.298132838411563, 52.502546367656038 ], [ 13.298109828950752, 52.50253652125604 ], [ 13.298095733865109, 52.502548212966481 ], [ 13.298095182793755, 52.502562478589482 ], [ 13.298081223966278, 52.50257060454512 ], [ 13.29806912298144, 52.502581135401527 ], [ 13.298082391488245, 52.502590841679371 ], [ 13.298101229030335, 52.502607765294094 ], [ 13.298114728615445, 52.502611528104232 ], [ 13.298126186686108, 52.50261764061996 ], [ 13.298124008901832, 52.502623556079946 ], [ 13.298117844314154, 52.502631794162369 ], [ 13.298094375625517, 52.502633834729842 ], [ 13.298069280154211, 52.502627526926226 ], [ 13.298057728763219, 52.502623792137442 ], [ 13.298047621880295, 52.502633162141073 ], [ 13.298043220291984, 52.50264618373194 ], [ 13.298035060124555, 52.502655581739965 ], [ 13.298017022658907, 52.502668406263204 ], [ 13.29801443150636, 52.502685021611576 ], [ 13.298021489488129, 52.502704154849347 ], [ 13.298032442436748, 52.502723343220886 ], [ 13.298057400141689, 52.502733218561488 ], [ 13.298080639323356, 52.502737120588336 ], [ 13.298107683023996, 52.502743457314367 ], [ 13.298125399054568, 52.502738954016358 ], [ 13.298147149220021, 52.502730941034159 ], [ 13.298153361178599, 52.502721514993425 ], [ 13.298159802754654, 52.5027061445683 ], [ 13.298162210192903, 52.502694283825008 ], [ 13.298172362989643, 52.502683724934748 ], [ 13.298194067196174, 52.502676900820376 ], [ 13.298194296811372, 52.50267095643612 ], [ 13.298188775119879, 52.502662551145086 ], [ 13.298175642900773, 52.502649277328601 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 35, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299717913720253, 52.502722208042428 ], [ 13.299713911293674, 52.502722150520157 ], [ 13.299711687799565, 52.502729255787415 ], [ 13.299713799654173, 52.502731991594054 ], [ 13.299717913720253, 52.502722208042428 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 36, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299711687799565, 52.502729255787415 ], [ 13.299713911293674, 52.502722150520157 ], [ 13.299717913720253, 52.502722208042428 ], [ 13.299735340032401, 52.502722458489181 ], [ 13.299753057331547, 52.502717955865684 ], [ 13.299759221627809, 52.502709717695723 ], [ 13.299759772163435, 52.502695452060792 ], [ 13.299764402619866, 52.50267648691149 ], [ 13.299757114478663, 52.5026632981603 ], [ 13.299757573252405, 52.502651410280841 ], [ 13.299753956186878, 52.502645490633938 ], [ 13.299750240708807, 52.502639410428152 ], [ 13.299738918540282, 52.502629732311902 ], [ 13.299715358156648, 52.502634150957213 ], [ 13.299685907259091, 52.502639675375178 ], [ 13.29968673311321, 52.502618276473946 ], [ 13.299704955044273, 52.502600697100419 ], [ 13.299721364973031, 52.50257952217158 ], [ 13.299701930380818, 52.502578053322267 ], [ 13.299674657128904, 52.50257766134829 ], [ 13.29964729211026, 52.502579647123554 ], [ 13.299627536365914, 52.502586499507629 ], [ 13.299615296616347, 52.502600597137302 ], [ 13.299622538820543, 52.50261497477468 ], [ 13.29962787868215, 52.502628136431866 ], [ 13.299645043944706, 52.502637898538673 ], [ 13.2996427287109, 52.502647380661593 ], [ 13.299609428757471, 52.502651660202851 ], [ 13.299582293117973, 52.502647701574091 ], [ 13.299560818531136, 52.502648582455336 ], [ 13.299556463308109, 52.502660414329952 ], [ 13.29955781501987, 52.502675896843009 ], [ 13.299532214586526, 52.502682666103581 ], [ 13.299522198302453, 52.502689658459197 ], [ 13.299506476517109, 52.50269300109283 ], [ 13.299507247772688, 52.502700007340707 ], [ 13.299507919987304, 52.502706105850933 ], [ 13.29953519331638, 52.502706497864018 ], [ 13.299552725645166, 52.502706749861524 ], [ 13.299576194346475, 52.502704708105881 ], [ 13.299583803604497, 52.502709575622561 ], [ 13.299599204193809, 52.502714555122537 ], [ 13.299628288111435, 52.502718540849827 ], [ 13.299643734593033, 52.502722331466373 ], [ 13.299649303892698, 52.502729548733889 ], [ 13.299662893945614, 52.502730933592105 ], [ 13.299684047425096, 52.502738373938278 ], [ 13.299697317776252, 52.502748080957986 ], [ 13.299708959668969, 52.502749436912765 ], [ 13.299720970021445, 52.502741283662722 ], [ 13.299713799654173, 52.502731991594054 ], [ 13.299711687799565, 52.502729255787415 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 37, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.29955781501987, 52.502675896843009 ], [ 13.299556463308109, 52.502660414329952 ], [ 13.299560818531136, 52.502648582455336 ], [ 13.299582293117973, 52.502647701574091 ], [ 13.299609428757471, 52.502651660202851 ], [ 13.2996427287109, 52.502647380661593 ], [ 13.299645043944706, 52.502637898538673 ], [ 13.29962787868215, 52.502628136431866 ], [ 13.299622538820543, 52.50261497477468 ], [ 13.299615296616347, 52.502600597137302 ], [ 13.299627536365914, 52.502586499507629 ], [ 13.29964729211026, 52.502579647123554 ], [ 13.299674657128904, 52.50257766134829 ], [ 13.299701930380818, 52.502578053322267 ], [ 13.299721364973031, 52.50257952217158 ], [ 13.299704955044273, 52.502600697100419 ], [ 13.29968673311321, 52.502618276473946 ], [ 13.299685907259091, 52.502639675375178 ], [ 13.299715358156648, 52.502634150957213 ], [ 13.299738918540282, 52.502629732311902 ], [ 13.299750240708807, 52.502639410428152 ], [ 13.299753956186878, 52.502645490633938 ], [ 13.299761922549099, 52.502631445763832 ], [ 13.299753680175495, 52.502628933848193 ], [ 13.299789981703459, 52.5025672237851 ], [ 13.299789767357865, 52.502542769106761 ], [ 13.299789067228504, 52.502542432664086 ], [ 13.299771902004988, 52.502532669679397 ], [ 13.299756593185339, 52.502525313345103 ], [ 13.299761130390033, 52.502508725929793 ], [ 13.299763445625091, 52.502499242905472 ], [ 13.299779673405132, 52.502482824398847 ], [ 13.299787602333796, 52.502479369736271 ], [ 13.299809307702725, 52.502472544444871 ], [ 13.299831011557748, 52.502465720026905 ], [ 13.299834493593465, 52.502476475002602 ], [ 13.299855648419284, 52.502483916237395 ], [ 13.299880973425072, 52.50248428017273 ], [ 13.299894425814486, 52.502489230738945 ], [ 13.299896602657816, 52.502495131483386 ], [ 13.29989896790835, 52.502452316958461 ], [ 13.299801833893422, 52.502452255365696 ], [ 13.299793625156669, 52.502445169207782 ], [ 13.299722817416562, 52.502443769465799 ], [ 13.299711899431456, 52.502454505406185 ], [ 13.299638445578008, 52.502454249029149 ], [ 13.299638023474424, 52.502463620795687 ], [ 13.299626732438457, 52.502472989198104 ], [ 13.299622745847337, 52.502516194910903 ], [ 13.299607769232988, 52.502516189151301 ], [ 13.299526032178607, 52.502497146099273 ], [ 13.299548212438179, 52.50246003808882 ], [ 13.299520991805196, 52.502468400678609 ], [ 13.299520487083727, 52.502481477434699 ], [ 13.299493077703643, 52.502484652073605 ], [ 13.299468427875357, 52.502487752183548 ], [ 13.299476858323732, 52.502489551122324 ], [ 13.299473756229903, 52.502494835624042 ], [ 13.299521155857564, 52.502505301169485 ], [ 13.299513491573483, 52.502518122166499 ], [ 13.299602023360501, 52.502538361612707 ], [ 13.299615098852225, 52.502538532460576 ], [ 13.29957582186408, 52.502602354898457 ], [ 13.299563826997158, 52.502603274028054 ], [ 13.299524474453666, 52.502673440460633 ], [ 13.299516606600454, 52.502671837528311 ], [ 13.299507161849705, 52.5026864680393 ], [ 13.299496648323831, 52.502690348580416 ], [ 13.299507247772688, 52.502700007340707 ], [ 13.299506476517109, 52.50269300109283 ], [ 13.299522198302453, 52.502689658459197 ], [ 13.299532214586526, 52.502682666103581 ], [ 13.29955781501987, 52.502675896843009 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 38, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299262133709355, 52.50264843667582 ], [ 13.299272561597121, 52.502630744423669 ], [ 13.299239904114621, 52.502618380474281 ], [ 13.299254549619715, 52.502592422993771 ], [ 13.299341799506376, 52.502604382182007 ], [ 13.299323074085162, 52.502635039172553 ], [ 13.29934826149527, 52.502638969855262 ], [ 13.299356742622679, 52.502621249611217 ], [ 13.299372326719741, 52.502621473630043 ], [ 13.299371663784822, 52.502622807387482 ], [ 13.299377090915275, 52.502624011100764 ], [ 13.299399402449604, 52.502586293602569 ], [ 13.299167729049344, 52.502534537409318 ], [ 13.299158376164879, 52.502551098727977 ], [ 13.299182619515214, 52.502549178779475 ], [ 13.299195797941632, 52.50256126271065 ], [ 13.299199556675889, 52.502564884459915 ], [ 13.299202992633932, 52.502576829228886 ], [ 13.299218394640272, 52.502581807900349 ], [ 13.299227675189185, 52.502593836690657 ], [ 13.299211861660487, 52.502599556141668 ], [ 13.299207414558008, 52.502613765757708 ], [ 13.299203013348279, 52.502626786496052 ], [ 13.299183349273095, 52.502631261948821 ], [ 13.299156305480642, 52.502624925467856 ], [ 13.299141318015636, 52.502609246912563 ], [ 13.2991418144912, 52.502596386727348 ], [ 13.299127156301221, 52.502622098551868 ], [ 13.299212461714554, 52.502640460394353 ], [ 13.299214766048292, 52.502635613991252 ], [ 13.299260803231933, 52.502645231063958 ], [ 13.299258246460017, 52.502650319298994 ], [ 13.299341994028293, 52.50266835295097 ], [ 13.299345245075003, 52.502662951010763 ], [ 13.299262133709355, 52.50264843667582 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 39, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297628945515497, 52.502633086021213 ], [ 13.297635339849634, 52.502618904481174 ], [ 13.297652919536443, 52.502617967904563 ], [ 13.297668136156132, 52.502627702261286 ], [ 13.29767425632765, 52.502620653999401 ], [ 13.297688259674455, 52.502611340093523 ], [ 13.297675220934007, 52.502595689389487 ], [ 13.297665985275966, 52.50258247158326 ], [ 13.297668117182326, 52.502577745008672 ], [ 13.297691539957128, 52.502576892503527 ], [ 13.297690097031062, 52.502563787727567 ], [ 13.297678914621445, 52.502550542809573 ], [ 13.29766773074595, 52.502537297869345 ], [ 13.297668236052916, 52.502524220227365 ], [ 13.297662713007501, 52.502515814891382 ], [ 13.297629550788217, 52.502516527245085 ], [ 13.297609885136051, 52.502521002414291 ], [ 13.297608719339649, 52.502500764399571 ], [ 13.297611171344693, 52.50248771566914 ], [ 13.297621233854642, 52.502479533702164 ], [ 13.297646696606288, 52.502476332384653 ], [ 13.297657126569801, 52.502458640300787 ], [ 13.297641954475084, 52.502447717045122 ], [ 13.297620709645754, 52.502442653192034 ], [ 13.297614956962708, 52.502440191337875 ], [ 13.297611383596299, 52.502431814054468 ], [ 13.297594217432541, 52.502422050736939 ], [ 13.297574874832488, 52.502418204684929 ], [ 13.297555486330758, 52.502415546607665 ], [ 13.297541435606648, 52.502426049353737 ], [ 13.297529518262108, 52.502431825547099 ], [ 13.297529977667631, 52.502419936782296 ], [ 13.297502796392118, 52.50241716745429 ], [ 13.297500526652248, 52.502425460655338 ], [ 13.297469403096848, 52.502423823226167 ], [ 13.297477701165809, 52.502410858628103 ], [ 13.297472315996885, 52.502398886654142 ], [ 13.297442728667397, 52.502407976264749 ], [ 13.297430809842519, 52.502413752426932 ], [ 13.297434108986876, 52.502429262995221 ], [ 13.297459250194317, 52.50243438205564 ], [ 13.297453038095219, 52.502443808958034 ], [ 13.297431150119603, 52.502455388442414 ], [ 13.297428283135236, 52.502479136136536 ], [ 13.297429771924511, 52.502491052038778 ], [ 13.297449160451311, 52.502493710136825 ], [ 13.297462797771733, 52.502493906391422 ], [ 13.297482324127744, 52.502492997854759 ], [ 13.297494057728619, 52.502491977172191 ], [ 13.297507465342498, 52.502498117803839 ], [ 13.29750909197748, 52.502506467075925 ], [ 13.297530244942802, 52.502513908702042 ], [ 13.297540305966423, 52.502505727619592 ], [ 13.297575371900814, 52.50250623222297 ], [ 13.297580619292305, 52.502521770821836 ], [ 13.297576217612994, 52.502534791495862 ], [ 13.297562166855061, 52.502545294243902 ], [ 13.297547979742147, 52.502559363640749 ], [ 13.29754357804921, 52.502572384313353 ], [ 13.297533379188515, 52.502584132025262 ], [ 13.297530466273709, 52.502609069496849 ], [ 13.297540663963588, 52.502625758279294 ], [ 13.297534396914777, 52.502624405728263 ], [ 13.297534423504379, 52.502624442075756 ], [ 13.297536190053227, 52.502626730585376 ], [ 13.297527616313475, 52.502646828445492 ], [ 13.297532863703724, 52.502662367046092 ], [ 13.297558096901176, 52.502665109232325 ], [ 13.297592931828655, 52.502671557294029 ], [ 13.297627906019326, 52.502674439635889 ], [ 13.297626738739613, 52.502654201600549 ], [ 13.297627116871205, 52.502644415610355 ], [ 13.297624878654307, 52.502643923952427 ], [ 13.297624681653623, 52.502642540067299 ], [ 13.297628945515497, 52.502633086021213 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 40, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.296909800981512, 52.502545481383301 ], [ 13.296882895420117, 52.502535578656207 ], [ 13.296826815969256, 52.502524065493283 ], [ 13.29678015278582, 52.502521015530107 ], [ 13.29670617198094, 52.50251876088938 ], [ 13.296634140846029, 52.502516534273532 ], [ 13.296552275369573, 52.502516545088575 ], [ 13.296492298183951, 52.502504975644214 ], [ 13.296434954387628, 52.502491870626507 ], [ 13.296443744186005, 52.502499195562699 ], [ 13.296443376460884, 52.502508705670436 ], [ 13.296434925227574, 52.502519221482558 ], [ 13.296447193515419, 52.502522168334004 ], [ 13.296509117428855, 52.502533765832077 ], [ 13.296532386961943, 52.50253664809884 ], [ 13.296541854831579, 52.50253272399182 ], [ 13.296568898212847, 52.502539060168267 ], [ 13.296571930328899, 52.502540358100568 ], [ 13.296602440807321, 52.502539866809933 ], [ 13.296612135790875, 52.502541195937631 ], [ 13.296609773945661, 52.502551866871684 ], [ 13.296712976185397, 52.502554542284905 ], [ 13.296713527704016, 52.502540276672164 ], [ 13.296738852700894, 52.502540641280163 ], [ 13.29678560631619, 52.502541314385184 ], [ 13.296826147719853, 52.502551413441203 ], [ 13.296872395839952, 52.502565163249706 ], [ 13.296908811080176, 52.502581151464454 ], [ 13.296941469338977, 52.502593516072849 ], [ 13.296967141480804, 52.502603425307335 ], [ 13.296981723814982, 52.502599719546986 ], [ 13.29699749170601, 52.502595188374045 ], [ 13.297013791420929, 52.502593441341382 ], [ 13.296996039155202, 52.502583595772137 ], [ 13.296978874509275, 52.502573833286014 ], [ 13.296950250505629, 52.50255795814919 ], [ 13.296909800981512, 52.502545481383301 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 41, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297396451398445, 52.502661593488149 ], [ 13.297420563345387, 52.502642908790406 ], [ 13.297432022798844, 52.502649021393736 ], [ 13.297442039435969, 52.502642028319158 ], [ 13.297446347807439, 52.502631385382379 ], [ 13.297421344355772, 52.502622698790731 ], [ 13.297421665927589, 52.502614377554686 ], [ 13.297400742620155, 52.502600992425812 ], [ 13.297388687410397, 52.502610334334747 ], [ 13.297374958164408, 52.502612515823984 ], [ 13.297370603832492, 52.502624347634438 ], [ 13.297371631685923, 52.502648152279214 ], [ 13.297396451398445, 52.502661593488149 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 42, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297455932254017, 52.502585134044864 ], [ 13.297430699109114, 52.502582391837265 ], [ 13.297433952321285, 52.502599091281652 ], [ 13.297452877950789, 52.502606828147584 ], [ 13.29745300408392, 52.502606881212664 ], [ 13.297455059409549, 52.502607720899199 ], [ 13.297460260827034, 52.502624448379265 ], [ 13.297489620062223, 52.5026213031691 ], [ 13.29749072346233, 52.502615016207066 ], [ 13.29749072899123, 52.502614987514761 ], [ 13.2974921180866, 52.502607064666734 ], [ 13.297478987533161, 52.502593791694501 ], [ 13.297455932254017, 52.502585134044864 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 43, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299195797941632, 52.50256126271065 ], [ 13.299182619515214, 52.502549178779475 ], [ 13.299158376164879, 52.502551098727977 ], [ 13.299157202722286, 52.502551191550666 ], [ 13.299157120468733, 52.502553322184426 ], [ 13.299156376613047, 52.502572590446263 ], [ 13.299148088644467, 52.502585381773869 ], [ 13.299141868749285, 52.502594981281923 ], [ 13.2991418144912, 52.502596386727348 ], [ 13.299141318015636, 52.502609246912563 ], [ 13.299156305480642, 52.502624925467856 ], [ 13.299183349273095, 52.502631261948821 ], [ 13.299203013348279, 52.502626786496052 ], [ 13.299207414558008, 52.502613765757708 ], [ 13.299211861660487, 52.502599556141668 ], [ 13.299227675189185, 52.502593836690657 ], [ 13.299218394640272, 52.502581807900349 ], [ 13.299202992633932, 52.502576829228886 ], [ 13.299199556675889, 52.502564884459915 ], [ 13.299195797941632, 52.50256126271065 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 44, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.29740113914149, 52.502576282185032 ], [ 13.297403546775744, 52.502564421458587 ], [ 13.297411844891373, 52.502551456865824 ], [ 13.297423762249062, 52.502545681583449 ], [ 13.297426169840648, 52.502533821755058 ], [ 13.297432565697243, 52.502519640247357 ], [ 13.297440771944228, 52.502509052506539 ], [ 13.297429404377551, 52.502500563049978 ], [ 13.297408159548175, 52.50249549915879 ], [ 13.297404906352554, 52.502478799713565 ], [ 13.297393724050281, 52.502465554769323 ], [ 13.297384304690743, 52.50245709334596 ], [ 13.297364456768303, 52.502466323102489 ], [ 13.297335051647813, 52.502470658057455 ], [ 13.297320680737133, 52.50248948203361 ], [ 13.297310342524842, 52.502504796333881 ], [ 13.297300371845646, 52.502510600521212 ], [ 13.297284374313477, 52.502521075218048 ], [ 13.297275984271277, 52.502536417554239 ], [ 13.297275570742917, 52.502547117440962 ], [ 13.297292553129157, 52.502561635410942 ], [ 13.297305913350771, 52.502568964021819 ], [ 13.297329290180079, 52.502569300464756 ], [ 13.297342971996331, 52.502568307835858 ], [ 13.297348540897717, 52.50257552520852 ], [ 13.297371642095342, 52.502582994002537 ], [ 13.297389221772516, 52.502582057465006 ], [ 13.29740113914149, 52.502576282185032 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 45, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300257317658589, 52.502480171525924 ], [ 13.300257502201546, 52.502475388156014 ], [ 13.300257720477349, 52.502469730425958 ], [ 13.300257868028423, 52.502465905886702 ], [ 13.300258464264777, 52.50245045136932 ], [ 13.300248952618563, 52.50244436792569 ], [ 13.300231282819354, 52.50244768176799 ], [ 13.300219274079318, 52.502455835991483 ], [ 13.300201373458133, 52.502465094198747 ], [ 13.300193536301403, 52.502466171133115 ], [ 13.300176140197612, 52.50246235257584 ], [ 13.300168532353254, 52.502457486016688 ], [ 13.300164223182545, 52.502468129037446 ], [ 13.300162045644241, 52.502474045436131 ], [ 13.300156246942539, 52.502472772582458 ], [ 13.300136995862408, 52.50246654829251 ], [ 13.300129386583821, 52.502461680811038 ], [ 13.300125031497963, 52.502473513607015 ], [ 13.300116781535035, 52.502485289540239 ], [ 13.300122442659729, 52.502490128131448 ], [ 13.300122213306111, 52.502496072521758 ], [ 13.300108392481745, 52.502500632085848 ], [ 13.30010027865063, 52.502508841362463 ], [ 13.300099774100316, 52.502521918122348 ], [ 13.300099269549612, 52.502534994882183 ], [ 13.300106649481172, 52.502545806755847 ], [ 13.300113937708867, 52.502558995486503 ], [ 13.300125120857867, 52.502572240180534 ], [ 13.300133422851488, 52.502575574723529 ], [ 13.300140477099182, 52.502578407610862 ], [ 13.300167289354667, 52.502583186858288 ], [ 13.300169513602363, 52.502583582960362 ], [ 13.300169686407989, 52.502583724806996 ], [ 13.300182738181283, 52.502594477905816 ], [ 13.300217804234364, 52.502594981726801 ], [ 13.30025296051601, 52.502593108658608 ], [ 13.300257773061409, 52.502590307807637 ], [ 13.300260936768042, 52.502588465107323 ], [ 13.300282457180968, 52.502586395220447 ], [ 13.300286787840667, 52.502585582594534 ], [ 13.30029423661445, 52.502584185381643 ], [ 13.300288852129658, 52.502572213550742 ], [ 13.300287270831243, 52.502562675434369 ], [ 13.300275948597616, 52.502552996469902 ], [ 13.300278309556527, 52.502542325455124 ], [ 13.300280762247295, 52.502529276683994 ], [ 13.300275468033242, 52.502514927074969 ], [ 13.300271283779505, 52.502512250516929 ], [ 13.300268451140653, 52.502510438551397 ], [ 13.300260780010751, 52.50250553152776 ], [ 13.300258348543313, 52.502503976180485 ], [ 13.3002549108311, 52.50249203141852 ], [ 13.300257317658589, 52.502480171525924 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 46, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297838839746154, 52.502548085730083 ], [ 13.297846999934668, 52.50253868773558 ], [ 13.297843562875373, 52.502526742909119 ], [ 13.297835953808571, 52.502521876180879 ], [ 13.297861416604929, 52.502518673918615 ], [ 13.297869668646145, 52.502506898169074 ], [ 13.297860432912675, 52.502493681276135 ], [ 13.297833159739037, 52.502493288878206 ], [ 13.297830044179795, 52.502473022813348 ], [ 13.297822574394385, 52.502464589475537 ], [ 13.297799243547976, 52.502463064253881 ], [ 13.297785468468257, 52.502466433769392 ], [ 13.297767522843284, 52.502476881400398 ], [ 13.29775528400673, 52.502490978854262 ], [ 13.297750790512707, 52.502506377288505 ], [ 13.297746572624582, 52.502514642462927 ], [ 13.297763645551553, 52.502526782592241 ], [ 13.297773156832843, 52.502532867131464 ], [ 13.297784293332745, 52.502547300917421 ], [ 13.297809664280978, 52.502546476419695 ], [ 13.297832949239323, 52.502549190516312 ], [ 13.297838839746154, 52.502548085730083 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 47, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297249060164679, 52.50247655676165 ], [ 13.29725712856853, 52.502469536561279 ], [ 13.297272484364425, 52.502475704360279 ], [ 13.297291964768487, 52.502475984731362 ], [ 13.297286763431346, 52.502459257244126 ], [ 13.297273403245821, 52.502451928631274 ], [ 13.297275717504688, 52.502442446537515 ], [ 13.297295015613726, 52.502447481535775 ], [ 13.297310599632665, 52.502447705825581 ], [ 13.297324786783966, 52.502433636457276 ], [ 13.297336567790603, 52.502431426935026 ], [ 13.297352013968132, 52.502435217848415 ], [ 13.297373258761944, 52.502440281749479 ], [ 13.297367691345606, 52.502433064399135 ], [ 13.297366248519729, 52.502419959619772 ], [ 13.297379838399621, 52.502421344738906 ], [ 13.29738336724906, 52.502430910926549 ], [ 13.297397554371791, 52.502416841549241 ], [ 13.297398289407582, 52.502397821324031 ], [ 13.29738112477739, 52.502388057997202 ], [ 13.297366092083006, 52.502373568098847 ], [ 13.297336641079138, 52.502379091931211 ], [ 13.297322775537406, 52.502384840065368 ], [ 13.297305563510077, 52.502376265582896 ], [ 13.297303890971428, 52.502369105184577 ], [ 13.297286404744449, 52.502367663978703 ], [ 13.297261217682829, 52.502363732859727 ], [ 13.297243823355805, 52.502359913894907 ], [ 13.297214326397503, 52.502366626573057 ], [ 13.297202271213912, 52.502375968463561 ], [ 13.297195921254881, 52.502388961082374 ], [ 13.297202840987568, 52.502411660995321 ], [ 13.29721796553839, 52.502423773161397 ], [ 13.297223580384983, 52.502429800764887 ], [ 13.297209621441683, 52.502437926616849 ], [ 13.297219178575498, 52.502442822323616 ], [ 13.297218811018459, 52.502452332435283 ], [ 13.297218489410222, 52.502460653670603 ], [ 13.297216173630719, 52.502470136640547 ], [ 13.29720026799273, 52.502478233573484 ], [ 13.297178516277567, 52.502486247258936 ], [ 13.297185849565073, 52.502498247286368 ], [ 13.29718747613901, 52.502506596562291 ], [ 13.297202738531375, 52.502515143000302 ], [ 13.297211928121422, 52.502529548819162 ], [ 13.297219720881325, 52.502529660980514 ], [ 13.297241333292734, 52.502525213895254 ], [ 13.297251394372077, 52.502517032837844 ], [ 13.297272914878979, 52.50251496349933 ], [ 13.297277362593077, 52.502500753961023 ], [ 13.297264002431444, 52.502493424448403 ], [ 13.297258433519339, 52.502486207970513 ], [ 13.297249060164679, 52.50247655676165 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 48, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299858876655284, 52.502551761753722 ], [ 13.299884339331825, 52.502548559054986 ], [ 13.299884591292471, 52.502542029662024 ], [ 13.29988502745533, 52.502530726784983 ], [ 13.299891101383952, 52.502524867283071 ], [ 13.299895253850815, 52.502519508851961 ], [ 13.299899307010294, 52.502514280265295 ], [ 13.299899719901145, 52.502503580363999 ], [ 13.299896602657816, 52.502495131483386 ], [ 13.299894425814486, 52.502489230738945 ], [ 13.299880973425072, 52.50248428017273 ], [ 13.299855648419284, 52.502483916237395 ], [ 13.299834493593465, 52.502476475002602 ], [ 13.299831011557748, 52.502465720026905 ], [ 13.299809307702725, 52.502472544444871 ], [ 13.299787602333796, 52.502479369736271 ], [ 13.299779673405132, 52.502482824398847 ], [ 13.299763445625091, 52.502499242905472 ], [ 13.299761130390033, 52.502508725929793 ], [ 13.299756593185339, 52.502525313345103 ], [ 13.299771902004988, 52.502532669679397 ], [ 13.299789067228504, 52.502542432664086 ], [ 13.299789767357865, 52.502542769106761 ], [ 13.299804377532126, 52.502549789015426 ], [ 13.299833597489586, 52.502550208935439 ], [ 13.299858876655284, 52.502551761753722 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 49, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297109580610556, 52.502454328843868 ], [ 13.297088243936388, 52.502451642648069 ], [ 13.297078228795064, 52.502458634815369 ], [ 13.297085468672897, 52.502473012579976 ], [ 13.297078982279052, 52.502489571841643 ], [ 13.297078614656403, 52.502499082851159 ], [ 13.297084044209742, 52.502509865944639 ], [ 13.297101531958322, 52.502511307202042 ], [ 13.297111271431081, 52.502511447391512 ], [ 13.29712523041583, 52.502503321551586 ], [ 13.297129446946316, 52.502495056379061 ], [ 13.297130090231789, 52.5024784130106 ], [ 13.297130503759714, 52.502467714023226 ], [ 13.297121040006422, 52.502460440578112 ], [ 13.297109580610556, 52.502454328843868 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 50, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.296834260614959, 52.502467018215249 ], [ 13.296855827074006, 52.502463760076473 ], [ 13.296879203842076, 52.502464096607852 ], [ 13.296894879778979, 52.502461943200309 ], [ 13.296916492188481, 52.502457496174351 ], [ 13.296909298325783, 52.502441929523776 ], [ 13.296902286790662, 52.502421608245832 ], [ 13.29690251657798, 52.502415663865371 ], [ 13.296903068032483, 52.502401398250825 ], [ 13.296903711401274, 52.502384754884076 ], [ 13.296908343048923, 52.502365789855801 ], [ 13.296883155989237, 52.502361859555229 ], [ 13.296871696686933, 52.502355746899681 ], [ 13.296856342497064, 52.502349578171987 ], [ 13.296821506463319, 52.502343129874937 ], [ 13.296792332649147, 52.502341520336259 ], [ 13.2967857987329, 52.502359268436301 ], [ 13.296777592393003, 52.502369855232295 ], [ 13.296767852953256, 52.502369715015583 ], [ 13.296744568159877, 52.502367000710677 ], [ 13.296738264029637, 52.502378804427778 ], [ 13.29672987382636, 52.502394146724271 ], [ 13.296721621469368, 52.502405923290695 ], [ 13.296705485951637, 52.502419964536507 ], [ 13.296716807394267, 52.502429642937336 ], [ 13.296724093111415, 52.502442831847077 ], [ 13.296747286020024, 52.502447923908349 ], [ 13.296752854843126, 52.502455140409594 ], [ 13.296762254293377, 52.502457863404167 ], [ 13.296762376910815, 52.502457893042269 ], [ 13.296762481579695, 52.502457929614913 ], [ 13.296762802831406, 52.502458004371384 ], [ 13.296770204674823, 52.502460148343694 ], [ 13.296775634113031, 52.502470932349347 ], [ 13.296791173648039, 52.502472345604843 ], [ 13.296820255663915, 52.50247633199789 ], [ 13.296827003258372, 52.502471846316567 ], [ 13.296834260614959, 52.502467018215249 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 51, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298643199292213, 52.502334455716792 ], [ 13.298637584296415, 52.502328427280439 ], [ 13.29861837941006, 52.502321013868404 ], [ 13.298600755420221, 52.50232313948807 ], [ 13.298596721320932, 52.502326650084534 ], [ 13.298581045510371, 52.50232880371879 ], [ 13.298571855510598, 52.502314398002348 ], [ 13.298568464147998, 52.502301265216467 ], [ 13.298542955647271, 52.502305655610982 ], [ 13.298532711166482, 52.502318593188079 ], [ 13.298527988429807, 52.50233993514167 ], [ 13.298519598854647, 52.502355277570537 ], [ 13.298507406205699, 52.502368186227699 ], [ 13.298481805796019, 52.502374955261431 ], [ 13.298471790660392, 52.502383670261842 ], [ 13.298461636763959, 52.50239250644529 ], [ 13.298451023475501, 52.502414954111373 ], [ 13.29845001258321, 52.502422435546123 ], [ 13.298448294756117, 52.502435136102562 ], [ 13.298461701050512, 52.502441275707348 ], [ 13.298496537377892, 52.502447724422652 ], [ 13.29850219831814, 52.502452563089847 ], [ 13.298503687362714, 52.502464479879713 ], [ 13.298511544937067, 52.502474663945264 ], [ 13.29851296772593, 52.502476507825804 ], [ 13.298520778152376, 52.502476620155548 ], [ 13.29852660504859, 52.502476703957768 ], [ 13.298529615325606, 52.502478492445732 ], [ 13.298553281387093, 52.502492550699507 ], [ 13.298576428663598, 52.502498830382585 ], [ 13.298587582392548, 52.502499967235288 ], [ 13.298588884141404, 52.502500100144658 ], [ 13.298590018608943, 52.502500215363227 ], [ 13.298617337708949, 52.502499418709661 ], [ 13.29862712457032, 52.502498369918364 ], [ 13.298635374931623, 52.502486594092453 ], [ 13.298647431317994, 52.502477252074954 ], [ 13.29864807402978, 52.502460608693319 ], [ 13.298656462108763, 52.502445266234261 ], [ 13.298666845751283, 52.502428762934947 ], [ 13.298667304815238, 52.502416875061755 ], [ 13.298668131157312, 52.502395476171024 ], [ 13.29866478567012, 52.502381154510594 ], [ 13.298669003373377, 52.502372889301668 ], [ 13.2986810582485, 52.502363547259066 ], [ 13.298681333704783, 52.502356413995855 ], [ 13.298676144955845, 52.502353426225326 ], [ 13.298662266493727, 52.502345434857844 ], [ 13.298643199292213, 52.502334455716792 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 52, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298961029808625, 52.502381846649897 ], [ 13.298931900308762, 52.502379048739435 ], [ 13.298920073489578, 52.502382447298722 ], [ 13.298906024570361, 52.50239295023092 ], [ 13.298903571481604, 52.50240599897046 ], [ 13.29888938483928, 52.502420068532551 ], [ 13.298881178989904, 52.502430655478506 ], [ 13.298880628175134, 52.502444921107625 ], [ 13.298885967829577, 52.502458081897963 ], [ 13.29891301147987, 52.502464418439686 ], [ 13.298930314192544, 52.502470614915275 ], [ 13.298945668755429, 52.502476782477892 ], [ 13.298959030668717, 52.502484111824081 ], [ 13.298980551086332, 52.502482042174101 ], [ 13.298996318735481, 52.502477510732575 ], [ 13.299004478665486, 52.50246811265589 ], [ 13.299014401740566, 52.502463497179129 ], [ 13.299016808899694, 52.50245163731455 ], [ 13.299021164245284, 52.502439805460483 ], [ 13.29902950776329, 52.502425651872322 ], [ 13.299020363496918, 52.502410057312147 ], [ 13.299012755825519, 52.502405189781115 ], [ 13.29899943979296, 52.502396672462254 ], [ 13.298988027557103, 52.502389371152987 ], [ 13.298961029808625, 52.502381846649897 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 53, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299542676105153, 52.502411618164743 ], [ 13.29951558647163, 52.502406471542727 ], [ 13.299518222926887, 52.50238866727809 ], [ 13.299508894980425, 52.502377827366374 ], [ 13.299481575967333, 52.502378624226672 ], [ 13.299470040082339, 52.502362385721042 ], [ 13.299472353898951, 52.502352902681473 ], [ 13.299453148823815, 52.502345490302346 ], [ 13.299427915681822, 52.502342748520789 ], [ 13.299404538947192, 52.502342412493427 ], [ 13.299379030467076, 52.502346803967356 ], [ 13.299376669302276, 52.502357474962736 ], [ 13.29937996912914, 52.502372985482694 ], [ 13.299368327381218, 52.50237162859775 ], [ 13.299348892893645, 52.502370159691239 ], [ 13.299349259989937, 52.502360649568992 ], [ 13.299357601914933, 52.502346495935342 ], [ 13.299341834383764, 52.502351026527208 ], [ 13.299321986877521, 52.502360257514873 ], [ 13.29929453022911, 52.502364620066125 ], [ 13.299280892935734, 52.502364424022964 ], [ 13.299270786336646, 52.50237379503286 ], [ 13.299272275621883, 52.502385710916293 ], [ 13.299287584311221, 52.502393067312354 ], [ 13.299273673120931, 52.502400004554858 ], [ 13.299282907789321, 52.502413221319927 ], [ 13.299304015159041, 52.502421850610133 ], [ 13.299325397855121, 52.502423347529735 ], [ 13.299340983354485, 52.50242357157326 ], [ 13.29934705733973, 52.502417712099664 ], [ 13.299370663567949, 52.50241210375065 ], [ 13.299378592515206, 52.50240864911563 ], [ 13.299408354276711, 52.502407320946006 ], [ 13.299407616141439, 52.502426329445413 ], [ 13.299381832303927, 52.502437853286622 ], [ 13.299373444477855, 52.502453195799298 ], [ 13.299398356482614, 52.502464258837669 ], [ 13.299411579326495, 52.502475154745106 ], [ 13.299430427191718, 52.502479646146057 ], [ 13.299432824419259, 52.502480217379826 ], [ 13.299450127212932, 52.502486413780012 ], [ 13.299467615037615, 52.502487854688205 ], [ 13.299468427875357, 52.502487752183548 ], [ 13.299493077703643, 52.502484652073605 ], [ 13.299520487083727, 52.502481477434699 ], [ 13.299520991805196, 52.502468400678609 ], [ 13.299548212438179, 52.50246003808882 ], [ 13.299550580231122, 52.502459310566223 ], [ 13.29955683773742, 52.502448695570827 ], [ 13.299570979764871, 52.502435814825937 ], [ 13.299583218008962, 52.502421717179089 ], [ 13.299573923651074, 52.502415770772679 ], [ 13.299568001065287, 52.502411982166713 ], [ 13.299542676105153, 52.502411618164743 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 54, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300280736274599, 52.503542829294574 ], [ 13.300269919786373, 52.503520073591297 ], [ 13.300249934366484, 52.503532870465222 ], [ 13.300222614646167, 52.503533667490508 ], [ 13.300215142751489, 52.503525234278662 ], [ 13.300203039686897, 52.503531395592923 ], [ 13.300199237270173, 52.503533331614861 ], [ 13.300192831547587, 52.503534154884534 ], [ 13.300337501155242, 52.503565390058228 ], [ 13.300336400444165, 52.503564208085116 ], [ 13.300327078271255, 52.503554200012353 ], [ 13.30029998787713, 52.503549052665562 ], [ 13.300280736274599, 52.503542829294574 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 55, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300098234226903, 52.503473596680003 ], [ 13.300063305064013, 52.503469527082878 ], [ 13.300061540325522, 52.50346474357773 ], [ 13.300032732596231, 52.503453624707213 ], [ 13.30000410691094, 52.503437750297408 ], [ 13.300000439822934, 52.503431749918811 ], [ 13.299990377528767, 52.503439932088803 ], [ 13.299973275832574, 52.503449055186131 ], [ 13.2999664955985, 52.503452672924581 ], [ 13.29996399688056, 52.503466910563809 ], [ 13.299948533740141, 52.503468775222601 ], [ 13.299946372526126, 52.503469035481565 ], [ 13.299945995587674, 52.503468150724771 ], [ 13.299942797163183, 52.503460658244364 ], [ 13.299954853546613, 52.503451316095045 ], [ 13.299956367295161, 52.503445115930262 ], [ 13.299957168794208, 52.503441833068436 ], [ 13.299930399621582, 52.503428364392079 ], [ 13.299901315242494, 52.503424377836737 ], [ 13.299877937928366, 52.503424041898079 ], [ 13.299859355484537, 52.503422511594799 ], [ 13.299842964419302, 52.503421161135229 ], [ 13.299833131533669, 52.503423398004529 ], [ 13.299819403620535, 52.503425579797209 ], [ 13.299795978953128, 52.503426432698838 ], [ 13.299771021898861, 52.503416558639323 ], [ 13.299734157172278, 52.503416028839943 ], [ 13.299730110505051, 52.503415970682795 ], [ 13.299706503814821, 52.503421578199841 ], [ 13.299677281808929, 52.503421158222423 ], [ 13.29966722838723, 52.503420672067406 ], [ 13.300143339562354, 52.503523468940621 ], [ 13.300139487031698, 52.50351582051082 ], [ 13.300139368274001, 52.503515728892388 ], [ 13.300120556574043, 52.503501274968976 ], [ 13.300111411670272, 52.5034856813893 ], [ 13.300107235655732, 52.503480268919759 ], [ 13.300105843674238, 52.503478464164118 ], [ 13.300098234226903, 52.503473596680003 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 56, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299677260576896, 52.503371200957218 ], [ 13.29967387028247, 52.503358068222965 ], [ 13.29966481736979, 52.503340095956148 ], [ 13.299657437383985, 52.503329284954788 ], [ 13.299636283662359, 52.503321843703588 ], [ 13.299595557377151, 52.503316500212094 ], [ 13.299573898916673, 52.503322136603131 ], [ 13.299548527545854, 52.503322961475597 ], [ 13.29952686907636, 52.503328597857958 ], [ 13.299501176513742, 52.503337743964238 ], [ 13.299499353695619, 52.50333862767426 ], [ 13.299489257699749, 52.503343520333075 ], [ 13.2994695015862, 52.503350372686832 ], [ 13.299468492888259, 52.503350662989995 ], [ 13.299457676017402, 52.503353771318658 ], [ 13.29944037141216, 52.503347574896345 ], [ 13.299424465821042, 52.503355673026583 ], [ 13.29940113444075, 52.503354148116593 ], [ 13.299389903953347, 52.503342092212222 ], [ 13.299376405506132, 52.503338329566375 ], [ 13.299339759267463, 52.50332828648731 ], [ 13.299298802070426, 52.503328887259791 ], [ 13.299264931597611, 52.503333809469083 ], [ 13.29952077121073, 52.503389049826573 ], [ 13.299544986625637, 52.503394278310893 ], [ 13.29954772268942, 52.503394317636868 ], [ 13.299550521735823, 52.503395473677209 ], [ 13.299621709720128, 52.503410844125398 ], [ 13.299625051017921, 52.503410892148324 ], [ 13.299634836587613, 52.503409843254204 ], [ 13.299646709481246, 52.503405256646943 ], [ 13.299664379742834, 52.503401941997474 ], [ 13.299665630157659, 52.503399168198001 ], [ 13.299668643280588, 52.503392486977305 ], [ 13.299676618232681, 52.503387844345184 ], [ 13.299676920902051, 52.503380002066315 ], [ 13.299677260576896, 52.503371200957218 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 57, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300547665914642, 52.503274277988858 ], [ 13.300477198925744, 52.503259082994781 ], [ 13.300477071587624, 52.5032592519985 ], [ 13.300461302366775, 52.503263782715976 ], [ 13.300458666231757, 52.503281587001865 ], [ 13.300458482779911, 52.503286342514329 ], [ 13.300465817093684, 52.503298342345545 ], [ 13.30047718545447, 52.503306831515872 ], [ 13.300478104171676, 52.503306925634433 ], [ 13.300480640359947, 52.503312566288137 ], [ 13.300480273495197, 52.50332207641447 ], [ 13.300471885874504, 52.503337419004616 ], [ 13.300473650652902, 52.503342202504086 ], [ 13.300475322268543, 52.503349362839934 ], [ 13.300487195094849, 52.50334477524877 ], [ 13.300500968778616, 52.503341405398601 ], [ 13.300508004635279, 52.503340288160523 ], [ 13.300547665914642, 52.503274277988858 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 58, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299026001537365, 52.503276197929083 ], [ 13.299041907171517, 52.503268099852711 ], [ 13.299073077290807, 52.503268547996228 ], [ 13.299100626324533, 52.503261807747364 ], [ 13.299100851847712, 52.503260202462137 ], [ 13.299102826353641, 52.503246230635348 ], [ 13.299103307433196, 52.503242814597883 ], [ 13.299101957341021, 52.503227332103421 ], [ 13.299071108517758, 52.503218562725465 ], [ 13.29903817381628, 52.503213331062177 ], [ 13.299016515337867, 52.503218967351096 ], [ 13.298995012436151, 52.503221108291932 ], [ 13.298985207581447, 52.503222084926001 ], [ 13.298982982937124, 52.503231200598101 ], [ 13.29898057820018, 52.503241050060957 ], [ 13.298978262789003, 52.503250532168437 ], [ 13.298948857293023, 52.50325486752088 ], [ 13.298933087886564, 52.503259398931938 ], [ 13.298933767094201, 52.503260353674129 ], [ 13.298969007064139, 52.503269913613153 ], [ 13.299027039127512, 52.503282448251831 ], [ 13.299026001537365, 52.503276197929083 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 59, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300285762863499, 52.503256153871064 ], [ 13.300296053110262, 52.503246872628971 ], [ 13.300315488019011, 52.503248341383184 ], [ 13.300333022043082, 52.503248593288063 ], [ 13.30032777359725, 52.50323305480417 ], [ 13.300296832818974, 52.503226662601236 ], [ 13.30027956057099, 52.503222871915398 ], [ 13.300274834670743, 52.50321941523142 ], [ 13.300276801218585, 52.503215874873916 ], [ 13.300268748790081, 52.503214126380456 ], [ 13.300280474191734, 52.503201711642468 ], [ 13.300277219859513, 52.503185012268524 ], [ 13.300283752503598, 52.503167263967114 ], [ 13.300284394613405, 52.503150620573351 ], [ 13.300284990820517, 52.503135166956156 ], [ 13.300285311892228, 52.503126844809913 ], [ 13.30026475289937, 52.503103950032425 ], [ 13.300259230792907, 52.503095543936155 ], [ 13.300232048950374, 52.503092774331911 ], [ 13.300218411425007, 52.503092578394885 ], [ 13.300179083051395, 52.50310152963231 ], [ 13.300161137758318, 52.503111976729315 ], [ 13.300147821290274, 52.503103459537016 ], [ 13.300124168947953, 52.503110256017521 ], [ 13.300111838935299, 52.503126731474445 ], [ 13.300115047328957, 52.503144619730527 ], [ 13.300127949519938, 52.503163835908154 ], [ 13.300140989301433, 52.50317948634919 ], [ 13.300154351627119, 52.503186815563041 ], [ 13.30016325756813, 52.503191378887628 ], [ 13.300163347203778, 52.503191423333313 ], [ 13.300163909254149, 52.50319171103547 ], [ 13.30016935501326, 52.503196367603856 ], [ 13.30016912888459, 52.503202228421699 ], [ 13.300160878792486, 52.503214004355527 ], [ 13.300141122804687, 52.503220857721601 ], [ 13.300136950873778, 52.503225077594159 ], [ 13.300122087892941, 52.503231552599452 ], [ 13.300109314838263, 52.503234459349834 ], [ 13.300103511759183, 52.503235779496215 ], [ 13.300105930935546, 52.503238914425197 ], [ 13.300116505680506, 52.503252618817818 ], [ 13.30012978277972, 52.503251656015436 ], [ 13.300143871099179, 52.503250632937601 ], [ 13.30016516248722, 52.503254507464703 ], [ 13.300167346822031, 52.503255442465743 ], [ 13.30016917792415, 52.503257207674814 ], [ 13.300174821722688, 52.503258641042365 ], [ 13.3001939257241, 52.503266815201663 ], [ 13.300199678661006, 52.503269276932187 ], [ 13.300205159301134, 52.503273488035965 ], [ 13.300207086020672, 52.503285109882789 ], [ 13.300241921841749, 52.503291557171316 ], [ 13.300263488433291, 52.503288299311713 ], [ 13.300267034854, 52.503284214307293 ], [ 13.300277236330677, 52.503279907526682 ], [ 13.300277536846481, 52.503272118264007 ], [ 13.300279808096045, 52.503269502084805 ], [ 13.300281302407939, 52.503260176292422 ], [ 13.300285762863499, 52.503256153871064 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 60, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298816268159539, 52.50319017901429 ], [ 13.298808437446938, 52.503186238854525 ], [ 13.298805550824005, 52.503187542431604 ], [ 13.298790582141669, 52.503194307965046 ], [ 13.298772452852546, 52.503209510356562 ], [ 13.298756310536515, 52.503212213863243 ], [ 13.298838535479518, 52.503234521071398 ], [ 13.298859290699195, 52.503232168904006 ], [ 13.298875427313149, 52.503218127384152 ], [ 13.298862111065317, 52.503209610049119 ], [ 13.298835204652773, 52.503199707759919 ], [ 13.298816268159539, 52.50319017901429 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 61, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298712639087279, 52.503143229281221 ], [ 13.298699690140277, 52.503125201810342 ], [ 13.298682387205814, 52.503119005299766 ], [ 13.298662952414579, 52.503117536278204 ], [ 13.298654700466047, 52.50312931208272 ], [ 13.298642873423383, 52.503132710611993 ], [ 13.298618008622697, 52.50312045855825 ], [ 13.298600750142084, 52.503113073137683 ], [ 13.298593280102297, 52.503104639847251 ], [ 13.298585119964198, 52.503114037892821 ], [ 13.298555898179233, 52.50311361764021 ], [ 13.298555194399452, 52.503114329513018 ], [ 13.298547783911051, 52.503121827704732 ], [ 13.298543208334486, 52.503122990099726 ], [ 13.298530067802094, 52.503126330166161 ], [ 13.298516431750972, 52.503126134054092 ], [ 13.298493238240018, 52.503121043234074 ], [ 13.298483634900933, 52.503117336505277 ], [ 13.298448707687029, 52.503113265557424 ], [ 13.298411784872556, 52.503110355434103 ], [ 13.298409364283774, 52.503118094420884 ], [ 13.298747545756076, 52.503209836426983 ], [ 13.298737569973829, 52.503204250594933 ], [ 13.298726157541129, 52.503196950158568 ], [ 13.298710710975698, 52.503193159421762 ], [ 13.298685661041739, 52.50318566197037 ], [ 13.298701520759456, 52.503178753715567 ], [ 13.298703719631215, 52.503162485169149 ], [ 13.298704249416751, 52.503158571720391 ], [ 13.298707567524724, 52.503152504513039 ], [ 13.298712639087279, 52.503143229281221 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 62, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299553161897341, 52.503202892031361 ], [ 13.299566981527066, 52.50319833251384 ], [ 13.299586463746348, 52.503198612531037 ], [ 13.299603766841997, 52.503204808910588 ], [ 13.299629596961168, 52.50319209614814 ], [ 13.299651255350412, 52.503186459746146 ], [ 13.299670965478288, 52.503180796238652 ], [ 13.299669797251454, 52.503160558217076 ], [ 13.29965847415373, 52.503141051789363 ], [ 13.29965517646488, 52.503135369602546 ], [ 13.299636429868995, 52.503116068474512 ], [ 13.299621486481279, 52.503099201079621 ], [ 13.299627593700899, 52.503085037787713 ], [ 13.299630412629483, 52.503078501232537 ], [ 13.299632691299175, 52.503076953327984 ], [ 13.299634304174287, 52.503075858901568 ], [ 13.299634854757624, 52.50306159326842 ], [ 13.299629560694587, 52.503047243633013 ], [ 13.29960655063358, 52.503037397519861 ], [ 13.299587666441802, 52.503021663017236 ], [ 13.299545357815676, 52.503006781365578 ], [ 13.299525601862806, 52.503013633730447 ], [ 13.299513042215331, 52.503036053511522 ], [ 13.299512583369259, 52.503047941388687 ], [ 13.299506612074726, 52.503049764393992 ], [ 13.299493752797648, 52.503047003577677 ], [ 13.299482875557441, 52.503054388159434 ], [ 13.299483741214509, 52.50305673472046 ], [ 13.299479052217434, 52.503058164358379 ], [ 13.299479487396781, 52.503059677540435 ], [ 13.299484208565829, 52.503076080632759 ], [ 13.299497110426005, 52.503095297775516 ], [ 13.299502245116942, 52.503102819898395 ], [ 13.299504141945443, 52.503109408947999 ], [ 13.299519820819727, 52.503108899726733 ], [ 13.299529513159113, 52.503108584982641 ], [ 13.299523347324786, 52.503116822219489 ], [ 13.299516787852165, 52.503124378558105 ], [ 13.299502948914778, 52.503140317967201 ], [ 13.299498318292734, 52.503159283103471 ], [ 13.299505422880801, 52.503177227379787 ], [ 13.299518233037215, 52.503198821376706 ], [ 13.299517865931598, 52.503208332397101 ], [ 13.299541105464129, 52.503212235038596 ], [ 13.299553161897341, 52.503202892031361 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 63, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300021028402577, 52.503201289965531 ], [ 13.300034543982052, 52.503193420856043 ], [ 13.300035196334608, 52.503193430229757 ], [ 13.30003642296581, 52.503191348407874 ], [ 13.300045231297029, 52.503180226971807 ], [ 13.300045856405251, 52.503176894820477 ], [ 13.3000545753459, 52.503167937182532 ], [ 13.300061806139315, 52.50316050734645 ], [ 13.300071866882824, 52.503152325148214 ], [ 13.300079843256224, 52.503147681610514 ], [ 13.300109202668688, 52.503144535751865 ], [ 13.300109661355418, 52.503132647871226 ], [ 13.300090180632571, 52.50313236796157 ], [ 13.300082800610122, 52.503121556087144 ], [ 13.300065635063111, 52.503111794042162 ], [ 13.30005241036107, 52.503100899083471 ], [ 13.300045169412302, 52.503086521492833 ], [ 13.300064787734168, 52.503083234775993 ], [ 13.300057224232184, 52.50307717841163 ], [ 13.300034076436019, 52.503070899014489 ], [ 13.3000247940954, 52.503058870264475 ], [ 13.299989958490738, 52.503052422913669 ], [ 13.299966810752096, 52.503046142604887 ], [ 13.299941714770139, 52.503039835194592 ], [ 13.29994203590557, 52.503031513049699 ], [ 13.29995956836739, 52.503031764988037 ], [ 13.299988790119356, 52.503032184893591 ], [ 13.30000659779561, 52.503025304456301 ], [ 13.300005291792827, 52.503008633069925 ], [ 13.300003756417139, 52.50299790607292 ], [ 13.29999447557233, 52.502985877341779 ], [ 13.300004673876989, 52.50297412941373 ], [ 13.300007126675625, 52.502961080650053 ], [ 13.30000531608772, 52.502957486021771 ], [ 13.299988058871033, 52.502950101721758 ], [ 13.299978823880972, 52.502936885010158 ], [ 13.299970862773989, 52.50293242785748 ], [ 13.299963559325091, 52.502928338903956 ], [ 13.29994630212685, 52.502920954597954 ], [ 13.299919395674497, 52.502911051655715 ], [ 13.299897966841987, 52.502910743719255 ], [ 13.299880434428911, 52.502910491772205 ], [ 13.299876943091334, 52.502911965610238 ], [ 13.29985863856886, 52.502919694851194 ], [ 13.299838789472155, 52.502928925003665 ], [ 13.299809431623823, 52.50293207171616 ], [ 13.299795610630143, 52.502936631241944 ], [ 13.299787175530154, 52.502953161741111 ], [ 13.299776472483673, 52.502977987305322 ], [ 13.299785569779104, 52.50299477066546 ], [ 13.299803195457761, 52.502992644892508 ], [ 13.299816556232546, 52.502999973223965 ], [ 13.299831453783279, 52.503018029472599 ], [ 13.299844631054045, 52.503030113312121 ], [ 13.299862163514391, 52.503030365264848 ], [ 13.299901080460591, 52.50303211405425 ], [ 13.299914395351815, 52.503040632151354 ], [ 13.299917557817199, 52.503059708391447 ], [ 13.299915196705781, 52.503070380295739 ], [ 13.299893584319117, 52.503074826970497 ], [ 13.299891085601857, 52.50308906460905 ], [ 13.299892345685143, 52.503106924873926 ], [ 13.299927273037127, 52.503110995396646 ], [ 13.299946571715479, 52.503116030865527 ], [ 13.299960207773591, 52.503116226812779 ], [ 13.29995789258003, 52.503125708941468 ], [ 13.299949732726891, 52.503135107083565 ], [ 13.299971163141629, 52.50313541503273 ], [ 13.299975425079468, 52.50312596087776 ], [ 13.299990643864623, 52.503135694961983 ], [ 13.299992179241157, 52.503146421958903 ], [ 13.299987646316145, 52.503153505734069 ], [ 13.299987374289158, 52.503153453272652 ], [ 13.299985584455635, 52.50315672822672 ], [ 13.299983881729995, 52.503159387635506 ], [ 13.299982876392415, 52.503160545643048 ], [ 13.299976412266465, 52.503162578278406 ], [ 13.299961920916269, 52.503167135384018 ], [ 13.299953485797667, 52.503183666793198 ], [ 13.299986874620533, 52.503189847005387 ], [ 13.29998832297265, 52.503190114176618 ], [ 13.300007620220097, 52.503195149614669 ], [ 13.300021028402577, 52.503201289965531 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 64, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298343260571837, 52.503067738732021 ], [ 13.298310280153203, 52.503063695744487 ], [ 13.298305716377858, 52.503064552598779 ], [ 13.298286719364947, 52.503068115000403 ], [ 13.298265106787129, 52.503072562274873 ], [ 13.298261009893841, 52.503077848620315 ], [ 13.298367097776246, 52.503106631410681 ], [ 13.298367254310698, 52.503102578625601 ], [ 13.298356437445189, 52.503079822733866 ], [ 13.298352279492166, 52.503076009097732 ], [ 13.298343260571837, 52.503067738732021 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 65, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300378620013797, 52.503021444387876 ], [ 13.30036550588332, 52.503018566716079 ], [ 13.30036527659278, 52.503024510208043 ], [ 13.300362871233821, 52.503036370122892 ], [ 13.300346826853746, 52.503048034993071 ], [ 13.300332640456993, 52.503062104728905 ], [ 13.300299201213145, 52.503069950176389 ], [ 13.300306535448623, 52.503081950017233 ], [ 13.30033715362787, 52.503096663443678 ], [ 13.300339077537721, 52.503100033116141 ], [ 13.300378620013797, 52.503021444387876 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 66, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.29976971029496, 52.503001679067822 ], [ 13.299762192694306, 52.502994434705855 ], [ 13.299729351310177, 52.502986825501203 ], [ 13.299706111878548, 52.502982922897388 ], [ 13.299705974230243, 52.502986489530521 ], [ 13.299707509533036, 52.502997216530773 ], [ 13.299699579031504, 52.503000671165658 ], [ 13.299684407713391, 52.502989748188213 ], [ 13.299664558548953, 52.502998978310721 ], [ 13.299658118923475, 52.503014348842306 ], [ 13.299655620134105, 52.503028586475374 ], [ 13.299645649639281, 52.503034390861529 ], [ 13.299657017773278, 52.503042880109142 ], [ 13.299672097334886, 52.503056180845455 ], [ 13.299677253761764, 52.503074097112084 ], [ 13.299706246122959, 52.503080461476053 ], [ 13.299723686837202, 52.503083091204893 ], [ 13.299733931153472, 52.503070153524817 ], [ 13.299765146990577, 52.503069413505784 ], [ 13.299778968029331, 52.503064853984029 ], [ 13.299798632240764, 52.503060378433673 ], [ 13.299789212290431, 52.503051917197588 ], [ 13.29979143578954, 52.503044811929264 ], [ 13.299795882750821, 52.503030602291027 ], [ 13.299807663799474, 52.503028392523191 ], [ 13.299798197973749, 52.503021120165627 ], [ 13.299790588638697, 52.503016252662334 ], [ 13.299769343280909, 52.503011189191142 ], [ 13.29976971029496, 52.503001679067822 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 67, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298691109622755, 52.502166819313963 ], [ 13.298673852937405, 52.502159434825792 ], [ 13.29866184402719, 52.502167587991174 ], [ 13.298665189498784, 52.502181909652066 ], [ 13.29866078817531, 52.502194930370166 ], [ 13.298656432758943, 52.502206762210832 ], [ 13.298675775330333, 52.502210608984754 ], [ 13.298691634695082, 52.5022037007272 ], [ 13.2987016510318, 52.50219670754295 ], [ 13.298707908702882, 52.502186092593398 ], [ 13.298706419575968, 52.502174176703825 ], [ 13.298691109622755, 52.502166819313963 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 68, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299507060257131, 52.50216070819063 ], [ 13.299477884977868, 52.502159099293202 ], [ 13.299417037052709, 52.502170120026484 ], [ 13.299455171716261, 52.502192078058727 ], [ 13.299499150918182, 52.502214120985599 ], [ 13.299501190870556, 52.502211771232737 ], [ 13.299502246201675, 52.502184428841922 ], [ 13.299506739049651, 52.502169030334869 ], [ 13.299507060257131, 52.50216070819063 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 69, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299976412266458, 52.503162578278406 ], [ 13.299627593700892, 52.503085037787727 ], [ 13.299621486481277, 52.503099201079621 ], [ 13.299636429868992, 52.503116068474512 ], [ 13.299655176464876, 52.503135369602568 ], [ 13.299658474153729, 52.503141051789363 ], [ 13.299974123022658, 52.503211213003077 ], [ 13.299986874620526, 52.503189847005395 ], [ 13.299953485797666, 52.503183666793177 ], [ 13.299961920916266, 52.503167135384018 ], [ 13.299976412266458, 52.503162578278406 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 70, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298831580669232, 52.503140243524662 ], [ 13.298858276618178, 52.503095114778006 ], [ 13.298907970899561, 52.503106036147095 ], [ 13.298919155054117, 52.503087194933741 ], [ 13.298869460790998, 52.503076273569448 ], [ 13.298942092735466, 52.50295317896272 ], [ 13.298991494257859, 52.502964008873654 ], [ 13.299002678304745, 52.502945168549971 ], [ 13.29895327830776, 52.502934337766376 ], [ 13.298954472906154, 52.502932094552094 ], [ 13.298620965754719, 52.502858766913484 ], [ 13.298602293038607, 52.502854736005332 ], [ 13.298602232993044, 52.502854726600169 ], [ 13.298579020376488, 52.502853720228785 ], [ 13.298555781087151, 52.502849818299012 ], [ 13.29854158029787, 52.5028416429085 ], [ 13.298536596754273, 52.502840570064798 ], [ 13.298003145486824, 52.502725523805729 ], [ 13.298002615077976, 52.502725409179668 ], [ 13.297979253797143, 52.502764980591948 ], [ 13.297978823667597, 52.502765702692116 ], [ 13.297906428081548, 52.502888300213996 ], [ 13.298166510853884, 52.502945457665867 ], [ 13.298130014688574, 52.503011185312438 ], [ 13.298128027863145, 52.503014750519036 ], [ 13.298201578125822, 52.503035650341154 ], [ 13.298205318352514, 52.503029298803334 ], [ 13.298219224799764, 52.503005634365771 ], [ 13.298831580669232, 52.503140243524662 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 71, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300502805748911, 52.502967713976041 ], [ 13.300360869224312, 52.502047985278281 ], [ 13.300360432799868, 52.502041920730491 ], [ 13.299921958137714, 52.502035141343576 ], [ 13.29960117458762, 52.502030175132525 ], [ 13.29921919797096, 52.502024260013194 ], [ 13.298833060263386, 52.502018279329903 ], [ 13.298454087749029, 52.502012408531776 ], [ 13.298071771403173, 52.502006483919182 ], [ 13.297687707321041, 52.502000532022613 ], [ 13.297303850998821, 52.50199458186831 ], [ 13.297027663568368, 52.501990299060296 ], [ 13.296548084496949, 52.501982874106361 ], [ 13.296450508254479, 52.502147685197535 ], [ 13.296596601692176, 52.502179788875111 ], [ 13.296627590109038, 52.502126152892117 ], [ 13.296630853793983, 52.502126719575003 ], [ 13.296646289015674, 52.50210083581716 ], [ 13.296642672724555, 52.502100052764312 ], [ 13.296655310029509, 52.502078170272313 ], [ 13.296636626085288, 52.502073794974692 ], [ 13.29664556856868, 52.502057581295048 ], [ 13.296725696817084, 52.502059000187863 ], [ 13.29672587197992, 52.502063044260993 ], [ 13.296764625581456, 52.502063772132999 ], [ 13.29676431773416, 52.502059691983128 ], [ 13.296875530168194, 52.502061663483481 ], [ 13.296875115688689, 52.502065868102662 ], [ 13.296920435570076, 52.502066722819748 ], [ 13.296920850309567, 52.502062473248088 ], [ 13.297022122726345, 52.502064273645047 ], [ 13.297021713456592, 52.502069754193016 ], [ 13.297020521011602, 52.502085585842558 ], [ 13.297059646979445, 52.50208582895668 ], [ 13.297058325270497, 52.502110799214705 ], [ 13.297130540579341, 52.502111026782707 ], [ 13.297130109722673, 52.502135325632516 ], [ 13.297292247885512, 52.502136908539022 ], [ 13.297297556271621, 52.50213512016159 ], [ 13.297299400581682, 52.502093955254715 ], [ 13.297399797387135, 52.502094701542966 ], [ 13.297399377388357, 52.502101566584578 ], [ 13.297445080209908, 52.50210250213761 ], [ 13.297445561759565, 52.502109180548665 ], [ 13.29748843751249, 52.502109955816998 ], [ 13.297488206643045, 52.502103386210848 ], [ 13.297536502057916, 52.502104373423833 ], [ 13.29754478072979, 52.50209797122902 ], [ 13.29757474543846, 52.502098441088499 ], [ 13.297588541630287, 52.502110575452285 ], [ 13.29758601761964, 52.502178336482629 ], [ 13.297543847949534, 52.502246678781113 ], [ 13.297522071869373, 52.502252162949922 ], [ 13.297497001329285, 52.502247124041794 ], [ 13.297491050530567, 52.502239342825483 ], [ 13.297449471609243, 52.502230474348288 ], [ 13.297452427392656, 52.502225054722594 ], [ 13.297409050398512, 52.502216315902885 ], [ 13.297406112584849, 52.502221232278139 ], [ 13.297365297588033, 52.502212536606983 ], [ 13.297361150311479, 52.502217791626904 ], [ 13.297265656240302, 52.502197306427327 ], [ 13.297198311381775, 52.502311985733719 ], [ 13.297556234233976, 52.502390624734346 ], [ 13.297917229390791, 52.50246993622973 ], [ 13.298281359511591, 52.502549939075223 ], [ 13.298636050152101, 52.502627865637535 ], [ 13.298995256896204, 52.502706773163744 ], [ 13.299353627343379, 52.502785507525125 ], [ 13.29972167252933, 52.50286636149842 ], [ 13.300007723172223, 52.50292920279395 ], [ 13.300394959839801, 52.503014265875777 ], [ 13.300502805748911, 52.502967713976041 ] ], [ [ 13.300210714110994, 52.502838899503075 ], [ 13.300210710649715, 52.502838034498943 ], [ 13.300210640818882, 52.502837171238596 ], [ 13.300210439548204, 52.502836315980097 ], [ 13.300210171769315, 52.502835466059892 ], [ 13.300209773572416, 52.502834635844799 ], [ 13.300209313007667, 52.502833818220417 ], [ 13.300208730478939, 52.502833030312921 ], [ 13.300208086881561, 52.502832259510456 ], [ 13.300207326759825, 52.502831530191592 ], [ 13.300206514266158, 52.502830821699156 ], [ 13.300205595209343, 52.502830163824676 ], [ 13.30020462651774, 52.502829532210718 ], [ 13.300203565676645, 52.502828959513813 ], [ 13.300202460952333, 52.502828416756579 ], [ 13.300201279999435, 52.502827940338115 ], [ 13.300200062352777, 52.502827498458196 ], [ 13.300198781591908, 52.50282712670198 ], [ 13.300197475813851, 52.502826792349417 ], [ 13.300196122877118, 52.50282653464366 ], [ 13.300194750744074, 52.502826316223427 ], [ 13.300193353471533, 52.502826176564611 ], [ 13.30019194583809, 52.502826076318243 ], [ 13.300190529124656, 52.502826058660524 ], [ 13.300189112392955, 52.502826079664757 ], [ 13.30018770575928, 52.502826182634017 ], [ 13.300186309380667, 52.502826325311965 ], [ 13.300184939436981, 52.502826546593205 ], [ 13.300183588583705, 52.5028268077101 ], [ 13.30018228197488, 52.502827144089693 ], [ 13.300181004833767, 52.502827518655764 ], [ 13.300179789815909, 52.502827963346682 ], [ 13.300178610294703, 52.502828442714204 ], [ 13.30017751077558, 52.502828987068767 ], [ 13.300176451309474, 52.502829562568934 ], [ 13.300175486883175, 52.502830195180103 ], [ 13.300174571449329, 52.50283085636795 ], [ 13.300173763182524, 52.502831565849867 ], [ 13.300173007096001, 52.502832297660383 ], [ 13.300172368831207, 52.502833068926819 ], [ 13.300171790282912, 52.502833858134593 ], [ 13.300171334355403, 52.502834676976924 ], [ 13.300170942770105, 52.50283550843227 ], [ 13.300170680146671, 52.502836357924721 ], [ 13.300170482073474, 52.502837214638497 ], [ 13.300170417830527, 52.502838077770733 ], [ 13.300074383756723, 52.502816704161191 ], [ 13.299987392525734, 52.502799056898994 ], [ 13.299990419914502, 52.502796089244839 ], [ 13.299993070762806, 52.502792990302943 ], [ 13.299995526471422, 52.502789831912303 ], [ 13.299997583948892, 52.502786569795099 ], [ 13.29999833714475, 52.502785225642711 ], [ 13.299994135087083, 52.502784461249625 ], [ 13.299984579004491, 52.502779566683813 ], [ 13.299984762504122, 52.502774811172159 ], [ 13.299998844644747, 52.502766613037778 ], [ 13.299998273164201, 52.502766149870688 ], [ 13.299997291780922, 52.502765427261373 ], [ 13.299996205029613, 52.502764762479956 ], [ 13.29999507138055, 52.502764129392972 ], [ 13.299993845339133, 52.502763561513312 ], [ 13.299992578082156, 52.502763030804473 ], [ 13.299991235860901, 52.502762571847235 ], [ 13.299989859717558, 52.50276215196385 ], [ 13.299988427475858, 52.502761811296139 ], [ 13.299986968605522, 52.502761511605286 ], [ 13.299985472641451, 52.502761294999694 ], [ 13.299983961759946, 52.502761121337521 ], [ 13.299982428441002, 52.502761032769399 ], [ 13.29998089342296, 52.502760988233788 ], [ 13.299979356583375, 52.502761029088475 ], [ 13.299977825407542, 52.502761114081466 ], [ 13.299976312991355, 52.502761285659631 ], [ 13.299974813705731, 52.502761498786008 ], [ 13.299973353934387, 52.502761795199305 ], [ 13.299971350865439, 52.5027623490463 ], [ 13.299969440720641, 52.502763014820403 ], [ 13.299967584452759, 52.502763735315952 ], [ 13.299965846489348, 52.502764559112173 ], [ 13.29996417295353, 52.502765431487575 ], [ 13.299962641768696, 52.502766394921494 ], [ 13.299961185527408, 52.502767401690996 ], [ 13.299959892703798, 52.502768488133171 ], [ 13.299958683971216, 52.502769609950285 ], [ 13.299957652498904, 52.502770796353964 ], [ 13.299956714230408, 52.502772011070533 ], [ 13.299955967134149, 52.502773273490327 ], [ 13.299955317971474, 52.502774556198908 ], [ 13.299954869440572, 52.502775870562402 ], [ 13.299954520627969, 52.502777197148276 ], [ 13.299954379065598, 52.502778536602627 ], [ 13.299954340513446, 52.502779879335449 ], [ 13.299954508397843, 52.502781217841765 ], [ 13.299892746476276, 52.502767261593405 ], [ 13.299878609300066, 52.502747371281956 ], [ 13.299925902765354, 52.502662812428149 ], [ 13.29991953597345, 52.502659613574295 ], [ 13.299925986861275, 52.502645665599836 ], [ 13.299954872948383, 52.502640650000593 ], [ 13.299982951506463, 52.502647054215501 ], [ 13.2999821723037, 52.502650943405811 ], [ 13.300133302199924, 52.50268447992439 ], [ 13.300151933121141, 52.502685624261012 ], [ 13.300152045035865, 52.502685625869027 ], [ 13.300153509085852, 52.502683156339515 ], [ 13.300182492944032, 52.502688934239131 ], [ 13.300297193870735, 52.502683247166615 ], [ 13.300306436555857, 52.502753628279166 ], [ 13.300296242168926, 52.502754050061952 ], [ 13.300300800541381, 52.502782891020054 ], [ 13.300310242501716, 52.502782648141377 ], [ 13.300319902220499, 52.502856274775873 ], [ 13.300297356020179, 52.502866331210448 ], [ 13.300206275600202, 52.502846058370984 ], [ 13.300207136047101, 52.502845371217482 ], [ 13.300207890660712, 52.502844639385572 ], [ 13.30020858425855, 52.502843885098116 ], [ 13.300209161368453, 52.50284309497038 ], [ 13.300209675712656, 52.50284228955492 ], [ 13.300210067262594, 52.502841458998049 ], [ 13.300210391455821, 52.502840617583104 ], [ 13.300210585110552, 52.502839760805792 ], [ 13.300210714110994, 52.502838899503075 ] ], [ [ 13.299753057331539, 52.502717955865698 ], [ 13.299735340032397, 52.502722458489174 ], [ 13.299717913720251, 52.502722208042421 ], [ 13.299713799654169, 52.50273199159404 ], [ 13.29972097002144, 52.502741283662708 ], [ 13.29970895966896, 52.502749436912765 ], [ 13.299697317776248, 52.502748080957971 ], [ 13.299684047425087, 52.502738373938278 ], [ 13.299662893945612, 52.502730933592119 ], [ 13.299649303892695, 52.502729548733882 ], [ 13.299643734593028, 52.502722331466373 ], [ 13.299628288111426, 52.502718540849841 ], [ 13.2995992041938, 52.502714555122552 ], [ 13.299583803604495, 52.502709575622561 ], [ 13.299576194346471, 52.502704708105888 ], [ 13.299552725645162, 52.502706749861538 ], [ 13.299535193316377, 52.502706497864025 ], [ 13.299507919987295, 52.502706105850947 ], [ 13.299507247772681, 52.502700007340707 ], [ 13.299496648323824, 52.502690348580416 ], [ 13.2995071618497, 52.502686468039293 ], [ 13.299516606600452, 52.502671837528297 ], [ 13.29952447445366, 52.502673440460626 ], [ 13.299563826997153, 52.502603274028033 ], [ 13.299575821864071, 52.502602354898457 ], [ 13.299615098852222, 52.502538532460598 ], [ 13.299602023360496, 52.5025383616127 ], [ 13.29951349157348, 52.502518122166492 ], [ 13.29952115585756, 52.502505301169478 ], [ 13.299473756229899, 52.502494835624049 ], [ 13.299476858323724, 52.502489551122316 ], [ 13.29946842787535, 52.502487752183534 ], [ 13.299467615037614, 52.502487854688212 ], [ 13.299450127212927, 52.502486413780005 ], [ 13.299432824419256, 52.502480217379812 ], [ 13.299430427191716, 52.50247964614605 ], [ 13.299216263820995, 52.502433959051103 ], [ 13.29921383761817, 52.502437420853965 ], [ 13.299160306749442, 52.502425599277338 ], [ 13.299192165094489, 52.502371335873271 ], [ 13.299196233848011, 52.502319180601326 ], [ 13.299254333117412, 52.502320554413132 ], [ 13.299254711630979, 52.502325138179629 ], [ 13.299525210179052, 52.502331889305964 ], [ 13.299525998031694, 52.502326975244522 ], [ 13.299580108033128, 52.502328257389699 ], [ 13.299579119849284, 52.502342255989483 ], [ 13.299573923651073, 52.502415770772672 ], [ 13.299583218008962, 52.502421717179089 ], [ 13.299570979764864, 52.502435814825951 ], [ 13.299556837737416, 52.50244869557082 ], [ 13.29955058023112, 52.502459310566216 ], [ 13.299548212438173, 52.502460038088827 ], [ 13.299526032178603, 52.502497146099266 ], [ 13.299607769232981, 52.502516189151301 ], [ 13.299622745847337, 52.502516194910896 ], [ 13.299626732438451, 52.502472989198104 ], [ 13.299638023474417, 52.502463620795695 ], [ 13.299638445578001, 52.502454249029142 ], [ 13.29971189943145, 52.502454505406163 ], [ 13.299722817416555, 52.502443769465813 ], [ 13.299793625156662, 52.502445169207796 ], [ 13.299801833893415, 52.502452255365689 ], [ 13.299898967908346, 52.502452316958475 ], [ 13.299896602657814, 52.502495131483393 ], [ 13.29989971990114, 52.502503580363999 ], [ 13.299899307010291, 52.502514280265309 ], [ 13.299895253850812, 52.502519508851954 ], [ 13.299894001453383, 52.502542151403063 ], [ 13.299884591292468, 52.502542029662017 ], [ 13.299884339331822, 52.502548559054972 ], [ 13.29985887665528, 52.502551761753722 ], [ 13.299833597489577, 52.502550208935446 ], [ 13.299804377532121, 52.502549789015426 ], [ 13.29978976735786, 52.502542769106761 ], [ 13.29978998170345, 52.502567223785107 ], [ 13.29975368017549, 52.502628933848193 ], [ 13.299761922549093, 52.502631445763818 ], [ 13.299753956186873, 52.502645490633952 ], [ 13.299757573252403, 52.502651410280833 ], [ 13.299757114478659, 52.502663298160293 ], [ 13.299764402619862, 52.502676486911483 ], [ 13.299759772163432, 52.502695452060784 ], [ 13.299759221627806, 52.502709717695737 ], [ 13.299753057331539, 52.502717955865698 ] ], [ [ 13.300133422851482, 52.502575574723529 ], [ 13.300036642234746, 52.502553822611887 ], [ 13.300032493589326, 52.502559311475281 ], [ 13.299983093635396, 52.502544436874246 ], [ 13.299977247621873, 52.502492765797825 ], [ 13.299971261604636, 52.502439988588087 ], [ 13.299985185377038, 52.502429018886929 ], [ 13.30001816881585, 52.502427438349891 ], [ 13.300019639913392, 52.50243360677937 ], [ 13.30005372973206, 52.502431876692 ], [ 13.300054475823426, 52.502436597008789 ], [ 13.300200280496968, 52.502428425825642 ], [ 13.300201062624927, 52.502424383825378 ], [ 13.300238157016194, 52.502422490955247 ], [ 13.300255947159572, 52.502430340529685 ], [ 13.300262225636219, 52.502469511929718 ], [ 13.300257720477346, 52.502469730425958 ], [ 13.300257502201543, 52.502475388156 ], [ 13.30025810328144, 52.502479170402914 ], [ 13.300260780010744, 52.50250553152776 ], [ 13.300268451140649, 52.502510438551404 ], [ 13.300271069039225, 52.502510254979498 ], [ 13.300271283779496, 52.502512250516922 ], [ 13.300275468033238, 52.502514927074976 ], [ 13.300280762247294, 52.50252927668398 ], [ 13.300278309556523, 52.502542325455124 ], [ 13.300275948597607, 52.502552996469902 ], [ 13.300287270831239, 52.502562675434355 ], [ 13.300288852129656, 52.502572213550735 ], [ 13.300294236614445, 52.502584185381636 ], [ 13.30028678784066, 52.50258558259452 ], [ 13.300286811186766, 52.502585894025799 ], [ 13.300277048970507, 52.502589550759346 ], [ 13.300257773061404, 52.50259030780763 ], [ 13.300252960516005, 52.502593108658623 ], [ 13.300217804234356, 52.502594981726794 ], [ 13.300182738181281, 52.502594477905816 ], [ 13.300169686407987, 52.502583724806996 ], [ 13.300167289354667, 52.502583186858274 ], [ 13.300140477099179, 52.502578407610855 ], [ 13.300133422851482, 52.502575574723529 ] ], [ [ 13.300273280023884, 52.502366520886596 ], [ 13.300320145579047, 52.502363791929291 ], [ 13.300324930541549, 52.502389776039678 ], [ 13.300276519729575, 52.502392572710193 ], [ 13.300273280023884, 52.502366520886596 ] ], [ [ 13.299669019456648, 52.502344759203709 ], [ 13.299674069470125, 52.502241661229959 ], [ 13.299681963340053, 52.502237548813135 ], [ 13.299688776350777, 52.502158899003398 ], [ 13.299683541559805, 52.502155919606714 ], [ 13.299703842878632, 52.50214267511064 ], [ 13.29971471469065, 52.502142219957754 ], [ 13.299721054828973, 52.502149085056438 ], [ 13.299757395969122, 52.502149563284554 ], [ 13.299761211385311, 52.50215106750133 ], [ 13.299765224805409, 52.502152365967447 ], [ 13.299769324780279, 52.502153559581132 ], [ 13.299773576088391, 52.502154535084799 ], [ 13.299777893578337, 52.502155399149345 ], [ 13.299782315661462, 52.502156034541848 ], [ 13.299786777593932, 52.502156553621113 ], [ 13.299791297136794, 52.50215683975663 ], [ 13.29979583159971, 52.502157006523333 ], [ 13.299800372237867, 52.502156936909707 ], [ 13.299804904200546, 52.502156748487231 ], [ 13.299809393675039, 52.502156324783421 ], [ 13.299813849336722, 52.502155784606828 ], [ 13.299818213708049, 52.50215501384227 ], [ 13.299822519094532, 52.502154129839738 ], [ 13.299826685652979, 52.50215302535549 ], [ 13.299844239873575, 52.502162860458775 ], [ 13.299843970717109, 52.502211108825385 ], [ 13.299852193856657, 52.502216369971336 ], [ 13.29985173834881, 52.502230083114576 ], [ 13.299864442777281, 52.502236660235695 ], [ 13.299841584583866, 52.502236938652942 ], [ 13.299841392404771, 52.502268721621071 ], [ 13.2998486415646, 52.502261385570193 ], [ 13.299874058196531, 52.502259371750043 ], [ 13.299889506012478, 52.502263162354723 ], [ 13.299894982105473, 52.502272756447219 ], [ 13.299908847619843, 52.502272955699347 ], [ 13.299909857398706, 52.502254614652614 ], [ 13.299962670453301, 52.50225559925822 ], [ 13.29997771872215, 52.502250156430023 ], [ 13.299999479979922, 52.502247988455608 ], [ 13.300001501690295, 52.502180548357778 ], [ 13.299996629285193, 52.502180319199148 ], [ 13.299998273003006, 52.502155170124766 ], [ 13.300052972197218, 52.502156336442511 ], [ 13.300052203305549, 52.502160684333383 ], [ 13.300110644615698, 52.50216162746581 ], [ 13.300111058124683, 52.502153621339204 ], [ 13.300228708633117, 52.502155042015609 ], [ 13.30023580869239, 52.502158709940367 ], [ 13.30023978182448, 52.502184997982951 ], [ 13.300233758764458, 52.502187975651175 ], [ 13.300237201455461, 52.502193222034009 ], [ 13.300242358054364, 52.502211137380996 ], [ 13.300245704009077, 52.502225459900068 ], [ 13.300231175040746, 52.502238104090971 ], [ 13.300235246092118, 52.502260670279838 ], [ 13.300222523745525, 52.502260437139803 ], [ 13.300230724572348, 52.502306453298381 ], [ 13.300239496382922, 52.502307080137932 ], [ 13.300241643582035, 52.502327422132595 ], [ 13.300203029069831, 52.502329688775902 ], [ 13.300200825791284, 52.502323059369211 ], [ 13.300063423107943, 52.502329575515198 ], [ 13.300062307739934, 52.502331065516323 ], [ 13.300049103779555, 52.502330254495583 ], [ 13.300033151926749, 52.50233101071769 ], [ 13.300031093867368, 52.502331806538109 ], [ 13.300021169454272, 52.502336422080262 ], [ 13.300019742933406, 52.502338262762763 ], [ 13.300019755104993, 52.50233852008629 ], [ 13.300019535140207, 52.502338531311501 ], [ 13.300012963891154, 52.502347009107289 ], [ 13.299999419818269, 52.502344435412098 ], [ 13.299979939444952, 52.502344155487208 ], [ 13.29997029027129, 52.502341637756977 ], [ 13.29995284979937, 52.50233900896518 ], [ 13.299937219956719, 52.502339973003004 ], [ 13.299913749992655, 52.50234201481102 ], [ 13.299896217805662, 52.502341762868198 ], [ 13.299870892881568, 52.5023413989368 ], [ 13.299835920265494, 52.502338517280037 ], [ 13.299831682628559, 52.502336347043233 ], [ 13.299799824712437, 52.502335908991967 ], [ 13.299798326107055, 52.502335875766647 ], [ 13.299796828590235, 52.502335928872569 ], [ 13.299795338244186, 52.502336025239345 ], [ 13.29979386812979, 52.502336208212469 ], [ 13.29979241549397, 52.502336434594589 ], [ 13.299791003878926, 52.502336743386223 ], [ 13.299789617209248, 52.502337092996797 ], [ 13.299788292384124, 52.502337519921426 ], [ 13.299787001374282, 52.502337986893345 ], [ 13.299785790122357, 52.502338525142925 ], [ 13.299784623166998, 52.502339099094819 ], [ 13.299783548027314, 52.502339737304681 ], [ 13.299782526192923, 52.502340406850756 ], [ 13.299781612684397, 52.502341132799963 ], [ 13.299780755668976, 52.502341883837296 ], [ 13.299780020613856, 52.502342681583457 ], [ 13.299779349622659, 52.502343499131825 ], [ 13.299778806898113, 52.502344352690166 ], [ 13.299728231334306, 52.502343188866405 ], [ 13.299727455257578, 52.502346385776953 ], [ 13.299669019456648, 52.502344759203709 ] ], [ [ 13.299513053479425, 52.50222580439226 ], [ 13.299500647133859, 52.502232653598391 ], [ 13.299447818308458, 52.502231582243944 ], [ 13.299447476803385, 52.50222600098558 ], [ 13.299345955264418, 52.502223887092143 ], [ 13.299344764699699, 52.502229500274588 ], [ 13.299292348057689, 52.502228438372271 ], [ 13.299279664997171, 52.502219058036452 ], [ 13.299284591676987, 52.502142460795717 ], [ 13.299299627923356, 52.50213446797288 ], [ 13.299502299851607, 52.502138910704431 ], [ 13.299517241518815, 52.502147605981968 ], [ 13.299513053479425, 52.50222580439226 ] ], [ [ 13.299118033529023, 52.502178962293129 ], [ 13.299111545207674, 52.502265395041405 ], [ 13.299102545985338, 52.502265621711054 ], [ 13.299098848097325, 52.502323242675814 ], [ 13.299089335667571, 52.502358973657024 ], [ 13.299096470553517, 52.502359829700026 ], [ 13.299041894469985, 52.502457210192688 ], [ 13.29904637517742, 52.502458579239217 ], [ 13.298997194850291, 52.502541101986743 ], [ 13.29900239595095, 52.50254865565779 ], [ 13.29899255198959, 52.502567401960484 ], [ 13.298993636940224, 52.50257402789881 ], [ 13.298967389061653, 52.502576767757624 ], [ 13.298967407993507, 52.502572194200361 ], [ 13.298852896467942, 52.502546534845401 ], [ 13.298845006747902, 52.502549963935039 ], [ 13.298821811490361, 52.502544238372764 ], [ 13.298816551698973, 52.502546523832095 ], [ 13.298804204845419, 52.502543318049895 ], [ 13.298796309215275, 52.502547663255825 ], [ 13.298782129168366, 52.502535537003624 ], [ 13.298788520285919, 52.502531654790531 ], [ 13.298796859059946, 52.50251724490068 ], [ 13.298802475152089, 52.50251656859794 ], [ 13.298853142720883, 52.502436099328342 ], [ 13.298858010865439, 52.502437011804808 ], [ 13.298915015424065, 52.50233802901294 ], [ 13.298921985986453, 52.502236140121056 ], [ 13.298915741391216, 52.502236267020379 ], [ 13.29891959032091, 52.502154400729218 ], [ 13.298913964922917, 52.50215440346166 ], [ 13.29891632149852, 52.502134504727209 ], [ 13.298934725889691, 52.502126970379116 ], [ 13.298992741752219, 52.502137519553152 ], [ 13.299000211773482, 52.502143012693423 ], [ 13.299026061387616, 52.502142338677864 ], [ 13.299036959375606, 52.502136168249905 ], [ 13.299038875917068, 52.502127932881137 ], [ 13.29906658788323, 52.502132295536526 ], [ 13.299098052510359, 52.502132994271768 ], [ 13.299129919121686, 52.502129119553487 ], [ 13.299125896731502, 52.502178965649172 ], [ 13.299118033529023, 52.502178962293129 ] ], [ [ 13.298525605926933, 52.502118774584545 ], [ 13.298753390822128, 52.502124599375918 ], [ 13.298752881995735, 52.502147468401425 ], [ 13.298743917393644, 52.502147066156589 ], [ 13.298742153534816, 52.502188166598742 ], [ 13.298725205707541, 52.502187930977172 ], [ 13.298720004326686, 52.502293365362007 ], [ 13.298729557992605, 52.502293813842478 ], [ 13.29872701093511, 52.502315437945114 ], [ 13.298692185648738, 52.502311261535347 ], [ 13.298684327033888, 52.502340212618229 ], [ 13.29867614495584, 52.502353426225319 ], [ 13.29868133370478, 52.502356413995848 ], [ 13.298681058248496, 52.502363547259058 ], [ 13.298669003373369, 52.502372889301668 ], [ 13.298664785670114, 52.502381154510587 ], [ 13.298668131157305, 52.502395476171024 ], [ 13.298667304815231, 52.502416875061741 ], [ 13.298666845751274, 52.50242876293494 ], [ 13.298656462108756, 52.502445266234254 ], [ 13.298648074029776, 52.502460608693312 ], [ 13.298647431317992, 52.502477252074947 ], [ 13.298635374931616, 52.502486594092453 ], [ 13.298627124570316, 52.502498369918357 ], [ 13.298617337708947, 52.502499418709654 ], [ 13.298590018608939, 52.502500215363227 ], [ 13.298588884141399, 52.502500100144651 ], [ 13.29858784134111, 52.50250047266897 ], [ 13.298587582392546, 52.502499967235288 ], [ 13.298576428663591, 52.502498830382585 ], [ 13.298553281387091, 52.502492550699486 ], [ 13.298529615325601, 52.502478492445711 ], [ 13.298520778152371, 52.502476620155555 ], [ 13.298512967725927, 52.502476507825804 ], [ 13.298511544937062, 52.502474663945272 ], [ 13.298484228190009, 52.502468876343258 ], [ 13.298481268678982, 52.502474700544823 ], [ 13.298414955148909, 52.502461081761361 ], [ 13.298427466299069, 52.502436151048464 ], [ 13.298444354942093, 52.50243250435441 ], [ 13.298450012583199, 52.502422435546137 ], [ 13.298451023475494, 52.502414954111373 ], [ 13.298461636763951, 52.502392506445297 ], [ 13.298471790660388, 52.502383670261835 ], [ 13.298480321508821, 52.502368485416149 ], [ 13.29850733060638, 52.502362322869736 ], [ 13.29854626166828, 52.502293719669282 ], [ 13.298534078908784, 52.502292256916959 ], [ 13.298537067067226, 52.502274740952956 ], [ 13.298550184033454, 52.502275209225587 ], [ 13.29855402920138, 52.502193801440541 ], [ 13.298541295294411, 52.50219333418169 ], [ 13.298542522028781, 52.502174807447837 ], [ 13.298530165100352, 52.502173201027979 ], [ 13.298531759916051, 52.502156741273012 ], [ 13.29853198513476, 52.50215636508289 ], [ 13.298532150430287, 52.502155976342429 ], [ 13.298532286448509, 52.502155582685262 ], [ 13.298532362231217, 52.502155184565297 ], [ 13.298532407194692, 52.502154783304746 ], [ 13.298532390241879, 52.502154382951929 ], [ 13.29853234092786, 52.502153981234635 ], [ 13.298532232434454, 52.502153585859162 ], [ 13.29853209435138, 52.502153193654614 ], [ 13.298531894005084, 52.502152811344054 ], [ 13.298531666944761, 52.502152434044014 ], [ 13.298531380323356, 52.502152072970695 ], [ 13.298531068391066, 52.502151718726338 ], [ 13.298530702614336, 52.502151385286474 ], [ 13.298530311387948, 52.502151062270052 ], [ 13.298529873575738, 52.502150762859912 ], [ 13.298529413154805, 52.502150476611448 ], [ 13.298528910426901, 52.502150217627261 ], [ 13.298528387965922, 52.502149973644336 ], [ 13.29852782894932, 52.502149760604944 ], [ 13.298527256055134, 52.502149563550098 ], [ 13.298526655371173, 52.502149399363113 ], [ 13.298526042178063, 52.502149253877732 ], [ 13.298525409926285, 52.502149144083099 ], [ 13.298524769652438, 52.502149051256396 ], [ 13.298524117613189, 52.502148996023578 ], [ 13.298523463337974, 52.502148960539266 ], [ 13.298522804729448, 52.502148960957491 ], [ 13.298525605926933, 52.502118774584545 ] ], [ [ 13.29844652701575, 52.502132718207037 ], [ 13.298437208113027, 52.50228062000204 ], [ 13.298356278654438, 52.502420479565714 ], [ 13.298253437570795, 52.502397665068465 ], [ 13.298240017877138, 52.502386456900318 ], [ 13.29825214575253, 52.502363593166663 ], [ 13.298261494202048, 52.50236588372988 ], [ 13.298316959155777, 52.502266168765999 ], [ 13.298326109594823, 52.502162578648012 ], [ 13.298315524730636, 52.502162126990847 ], [ 13.298316043802288, 52.50213789234801 ], [ 13.298333326048906, 52.502130804100595 ], [ 13.29844652701575, 52.502132718207037 ] ], [ [ 13.298097200539555, 52.502329454474484 ], [ 13.298076899566114, 52.502340424824887 ], [ 13.298004105660143, 52.502323013598328 ], [ 13.298019824596599, 52.502296402762312 ], [ 13.298013892938959, 52.502295522604598 ], [ 13.298007284168159, 52.502296761824695 ], [ 13.297978339975828, 52.502289208194433 ], [ 13.297965070108983, 52.502279501884203 ], [ 13.297955560327305, 52.502273417381559 ], [ 13.297943176812556, 52.502270515783223 ], [ 13.297913897780525, 52.502276778613108 ], [ 13.297838278284573, 52.502265083705126 ], [ 13.297831982614854, 52.502252269657788 ], [ 13.29780166969933, 52.502247233611378 ], [ 13.297768902893292, 52.502259148450655 ], [ 13.297649297604925, 52.502241391633959 ], [ 13.297681475515565, 52.502189808109556 ], [ 13.297683038305179, 52.50214219244792 ], [ 13.297807828217001, 52.502156018243312 ], [ 13.297830225171142, 52.502171987000203 ], [ 13.29785683216854, 52.50217245613095 ], [ 13.297866477375507, 52.502162525642142 ], [ 13.297956497544126, 52.502172499098492 ], [ 13.29798939018406, 52.502187148760285 ], [ 13.298020948864126, 52.502173208742093 ], [ 13.298060397011369, 52.502173695325851 ], [ 13.298062248694684, 52.50214647139417 ], [ 13.298135750724473, 52.50214787043128 ], [ 13.298149149452367, 52.502164119684018 ], [ 13.298143493256893, 52.50224532428178 ], [ 13.298097200539555, 52.502329454474484 ] ], [ [ 13.2978467927695, 52.502331823908975 ], [ 13.29782539422407, 52.502367540227013 ], [ 13.297745298393068, 52.502349937465752 ], [ 13.297763022587301, 52.502315055726498 ], [ 13.2978467927695, 52.502331823908975 ] ], [ [ 13.298204920954269, 52.502412771037818 ], [ 13.298183687346739, 52.502446169159306 ], [ 13.298104490997916, 52.502430142254923 ], [ 13.298124495951706, 52.502395635838532 ], [ 13.298204920954269, 52.502412771037818 ] ], [ [ 13.29915837616487, 52.502551098727977 ], [ 13.299167729049339, 52.502534537409304 ], [ 13.299399402449598, 52.502586293602569 ], [ 13.299377090915272, 52.502624011100771 ], [ 13.299371663784818, 52.502622807387482 ], [ 13.299369666786765, 52.502622364186223 ], [ 13.299345245074996, 52.502662951010755 ], [ 13.299341994028289, 52.502668352950955 ], [ 13.299258246460012, 52.502650319298986 ], [ 13.299260803231929, 52.502645231063944 ], [ 13.299214766048285, 52.502635613991238 ], [ 13.299212461714548, 52.502640460394339 ], [ 13.299127156301218, 52.502622098551868 ], [ 13.299141814491195, 52.502596386727362 ], [ 13.299141868749279, 52.502594981281931 ], [ 13.299148088644465, 52.502585381773855 ], [ 13.299154282212832, 52.502574517722621 ], [ 13.29914616720156, 52.50257271520374 ], [ 13.299157120468728, 52.502553322184411 ], [ 13.299157202722283, 52.502551191550666 ], [ 13.29915837616487, 52.502551098727977 ] ], [ [ 13.297829664323716, 52.502102986649916 ], [ 13.2978291487942, 52.502065609949398 ], [ 13.297918953488344, 52.502066892125555 ], [ 13.297917681350956, 52.502105046021214 ], [ 13.297829664323716, 52.502102986649916 ] ], [ [ 13.298212809537446, 52.50207118604235 ], [ 13.298300348894042, 52.502072336439333 ], [ 13.298298940510309, 52.502109105528746 ], [ 13.298210610441869, 52.502107521170018 ], [ 13.298212809537446, 52.50207118604235 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 72, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300368736566293, 52.503572133542178 ], [ 13.300420365382791, 52.503486183804974 ], [ 13.300363696014202, 52.503475356181973 ], [ 13.300340364568569, 52.503473830557809 ], [ 13.300322648459639, 52.503478334186376 ], [ 13.300310637978416, 52.503486488394401 ], [ 13.300280085848792, 52.50352054333073 ], [ 13.300107235655728, 52.503480268919752 ], [ 13.300111411670265, 52.503485681389293 ], [ 13.30012055657404, 52.503501274968968 ], [ 13.300139368273992, 52.503515728892388 ], [ 13.300203039686888, 52.503531395592901 ], [ 13.300215142751485, 52.503525234278655 ], [ 13.300222614646165, 52.503533667490508 ], [ 13.30024993436648, 52.503532870465214 ], [ 13.300269919786366, 52.503520073591282 ], [ 13.300280736274596, 52.503542829294567 ], [ 13.300299987877128, 52.503549052665548 ], [ 13.300327078271248, 52.503554200012346 ], [ 13.300336400444158, 52.503564208085109 ], [ 13.300367728312779, 52.503571915856064 ], [ 13.300368736566293, 52.503572133542178 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 73, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299156376613047, 52.502572590446263 ], [ 13.299157120468733, 52.502553322184426 ], [ 13.299146167201563, 52.502572715203726 ], [ 13.299154282212841, 52.502574517722628 ], [ 13.299148088644467, 52.502585381773869 ], [ 13.299156376613047, 52.502572590446263 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 74, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299973275832571, 52.503449055186117 ], [ 13.299956367295154, 52.503445115930248 ], [ 13.299954853546607, 52.503451316095038 ], [ 13.29994279716318, 52.503460658244364 ], [ 13.299945995587672, 52.503468150724764 ], [ 13.299948533740137, 52.503468775222601 ], [ 13.299963996880559, 52.503466910563809 ], [ 13.2999664955985, 52.503452672924567 ], [ 13.299973275832571, 52.503449055186117 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 75, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.29899255198959, 52.502567401960498 ], [ 13.299002395950957, 52.502548655657804 ], [ 13.298997194850296, 52.502541101986729 ], [ 13.299046375177429, 52.502458579239224 ], [ 13.299041894469987, 52.502457210192688 ], [ 13.299096470553522, 52.502359829700033 ], [ 13.299089335667576, 52.502358973657032 ], [ 13.29909884809733, 52.502323242675807 ], [ 13.299102545985342, 52.502265621711061 ], [ 13.299111545207676, 52.50226539504142 ], [ 13.29911803352903, 52.502178962293144 ], [ 13.299125896731509, 52.502178965649186 ], [ 13.29912991912169, 52.502129119553487 ], [ 13.299098052510365, 52.502132994271776 ], [ 13.299066587883233, 52.502132295536541 ], [ 13.299038875917077, 52.502127932881145 ], [ 13.299036959375611, 52.502136168249898 ], [ 13.299026061387623, 52.502142338677871 ], [ 13.299000211773487, 52.502143012693416 ], [ 13.298992741752224, 52.502137519553152 ], [ 13.298934725889694, 52.50212697037913 ], [ 13.298916321498526, 52.502134504727188 ], [ 13.298913964922919, 52.502154403461667 ], [ 13.298919590320919, 52.502154400729232 ], [ 13.298915741391218, 52.502236267020379 ], [ 13.298921985986453, 52.50223614012107 ], [ 13.298915015424068, 52.502338029012932 ], [ 13.298858010865445, 52.502437011804815 ], [ 13.298853142720887, 52.502436099328349 ], [ 13.298802475152094, 52.502516568597947 ], [ 13.298796859059953, 52.502517244900694 ], [ 13.298788520285925, 52.502531654790531 ], [ 13.298782129168373, 52.502535537003624 ], [ 13.298796309215279, 52.502547663255832 ], [ 13.298804204845426, 52.502543318049909 ], [ 13.29881655169898, 52.502546523832116 ], [ 13.298821811490367, 52.502544238372742 ], [ 13.298845006747905, 52.502549963935046 ], [ 13.298852896467945, 52.502546534845393 ], [ 13.298967407993512, 52.502572194200383 ], [ 13.298967389061662, 52.502576767757631 ], [ 13.298993636940233, 52.502574027898817 ], [ 13.29899255198959, 52.502567401960498 ] ], [ [ 13.298880628175134, 52.502444921107625 ], [ 13.298881178989904, 52.502430655478506 ], [ 13.29888938483928, 52.502420068532551 ], [ 13.298903571481604, 52.50240599897046 ], [ 13.298906024570361, 52.50239295023092 ], [ 13.298920073489578, 52.502382447298722 ], [ 13.298931900308762, 52.502379048739435 ], [ 13.298961029808625, 52.502381846649897 ], [ 13.298988027557103, 52.502389371152987 ], [ 13.29899943979296, 52.502396672462254 ], [ 13.299012755825519, 52.502405189781115 ], [ 13.299020363496918, 52.502410057312147 ], [ 13.29902950776329, 52.502425651872322 ], [ 13.299021164245284, 52.502439805460483 ], [ 13.299016808899694, 52.50245163731455 ], [ 13.299014401740566, 52.502463497179129 ], [ 13.299004478665486, 52.50246811265589 ], [ 13.298996318735481, 52.502477510732575 ], [ 13.298980551086332, 52.502482042174101 ], [ 13.298959030668717, 52.502484111824081 ], [ 13.298945668755429, 52.502476782477892 ], [ 13.298930314192544, 52.502470614915275 ], [ 13.29891301147987, 52.502464418439686 ], [ 13.298885967829577, 52.502458081897963 ], [ 13.298880628175134, 52.502444921107625 ] ], [ [ 13.298947489112173, 52.502341789418104 ], [ 13.298951179996978, 52.502296642764236 ], [ 13.29898276967981, 52.502304714323259 ], [ 13.298973890390423, 52.502301147516903 ], [ 13.29895672540834, 52.502291385314535 ], [ 13.29894364040404, 52.502276923623846 ], [ 13.298942288917328, 52.502261441104686 ], [ 13.298958148294272, 52.502254531912655 ], [ 13.298978087594856, 52.502242924129931 ], [ 13.298999424291214, 52.502245609985088 ], [ 13.299004901659902, 52.502255204138329 ], [ 13.299020257596897, 52.502261372610732 ], [ 13.299034168782175, 52.502254435397859 ], [ 13.2990559185449, 52.50224642224449 ], [ 13.299069555800203, 52.502246618313293 ], [ 13.299072762241492, 52.502264506574853 ], [ 13.29906840840068, 52.502276338452596 ], [ 13.299060202621597, 52.502286925411816 ], [ 13.299069574864353, 52.502296576459308 ], [ 13.299072920477597, 52.502310898109577 ], [ 13.299070834482242, 52.502318523236454 ], [ 13.299075076044469, 52.502318644459926 ], [ 13.299069848682494, 52.502353064089306 ], [ 13.29899387539742, 52.502351971769365 ], [ 13.298994150802958, 52.502344838504968 ], [ 13.298947489112173, 52.502341789418104 ] ], [ [ 13.299019618324509, 52.502240531679831 ], [ 13.299002222380352, 52.502236713850081 ], [ 13.298994936010702, 52.502223525074662 ], [ 13.298989642251241, 52.502209175411295 ], [ 13.298995853989188, 52.502199748424765 ], [ 13.298996312960602, 52.502187860549064 ], [ 13.299008275915043, 52.502180896226697 ], [ 13.299026039001397, 52.502175203934449 ], [ 13.299041393441259, 52.5021813723828 ], [ 13.299060552475851, 52.502189973705292 ], [ 13.299063852180016, 52.502205484233428 ], [ 13.299061307351815, 52.502220910732049 ], [ 13.299055141558512, 52.502229147945968 ], [ 13.299039098642318, 52.50224081176242 ], [ 13.299019618324509, 52.502240531679831 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 76, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300226871979616, 52.503384179675166 ], [ 13.300231179757313, 52.503373536633177 ], [ 13.300231821891753, 52.503356893240408 ], [ 13.300212753861274, 52.503345914347143 ], [ 13.300186006476713, 52.503382402998561 ], [ 13.299121869153081, 52.503126837689692 ], [ 13.299142589045534, 52.503095019867942 ], [ 13.299130945642574, 52.503093662937069 ], [ 13.299117263698475, 52.503094655771633 ], [ 13.299105438172392, 52.503098054368721 ], [ 13.299090974530792, 52.50311925629611 ], [ 13.299074426428581, 52.50314399686458 ], [ 13.299073783861965, 52.503160640247962 ], [ 13.299083156289235, 52.50317029129463 ], [ 13.299094614674656, 52.503176402819733 ], [ 13.299111531443382, 52.503142151251232 ], [ 13.300179796292101, 52.503391830067663 ], [ 13.300156715960259, 52.503434319084946 ], [ 13.300180093283181, 52.503434654968565 ], [ 13.300201933697497, 52.503424263832237 ], [ 13.300208420539605, 52.503407704414201 ], [ 13.300226871979616, 52.503384179675166 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 77, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300286811186769, 52.502585894025806 ], [ 13.300286787840667, 52.502585582594534 ], [ 13.300282457180968, 52.502586395220447 ], [ 13.300260936768042, 52.502588465107323 ], [ 13.300257773061409, 52.502590307807637 ], [ 13.300277048970511, 52.502589550759367 ], [ 13.300286811186769, 52.502585894025806 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 78, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299833131533665, 52.503423398004514 ], [ 13.299842964419296, 52.503421161135222 ], [ 13.299859355484534, 52.503422511594813 ], [ 13.29967692090205, 52.503380002066322 ], [ 13.299676618232677, 52.503387844345163 ], [ 13.299668643280583, 52.503392486977297 ], [ 13.299665630157653, 52.503399168198008 ], [ 13.299734157172276, 52.503416028839943 ], [ 13.299771021898858, 52.503416558639316 ], [ 13.299795978953121, 52.503426432698845 ], [ 13.29981940362053, 52.503425579797202 ], [ 13.299833131533665, 52.503423398004514 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 79, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300169513602363, 52.502583582960362 ], [ 13.300167289354667, 52.502583186858288 ], [ 13.300169686407989, 52.502583724806996 ], [ 13.300169513602363, 52.502583582960362 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 80, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300414554469269, 52.503420181278756 ], [ 13.300436350430846, 52.503410978997799 ], [ 13.300461966681834, 52.503416945815168 ], [ 13.300508004635274, 52.503340288160523 ], [ 13.300500968778612, 52.503341405398587 ], [ 13.300487195094842, 52.503344775248785 ], [ 13.300475322268538, 52.503349362839948 ], [ 13.300473650652899, 52.503342202504072 ], [ 13.300471885874501, 52.503337419004616 ], [ 13.300480273495191, 52.503322076414463 ], [ 13.30048064035994, 52.503312566288123 ], [ 13.300478104171669, 52.503306925634426 ], [ 13.300477185454467, 52.503306831515872 ], [ 13.300465817093679, 52.503298342345538 ], [ 13.300458482779904, 52.503286342514315 ], [ 13.30045866623175, 52.503281587001865 ], [ 13.300461302366768, 52.503263782715955 ], [ 13.300477071587618, 52.503259251998493 ], [ 13.300477198925739, 52.503259082994774 ], [ 13.300280398871124, 52.503216648555572 ], [ 13.300276801218583, 52.503215874873909 ], [ 13.300274834670736, 52.503219415231413 ], [ 13.300279560570987, 52.503222871915391 ], [ 13.300296832818972, 52.503226662601222 ], [ 13.300327773597246, 52.503233054804149 ], [ 13.300333022043079, 52.503248593288063 ], [ 13.300315488019008, 52.503248341383177 ], [ 13.300296053110259, 52.503246872628964 ], [ 13.300285762863492, 52.503256153871057 ], [ 13.300354004541248, 52.503271817820334 ], [ 13.300333147868344, 52.50330720159554 ], [ 13.300437658314097, 52.503326546058119 ], [ 13.300407862218794, 52.503391538050359 ], [ 13.300340137171506, 52.503378670628877 ], [ 13.300333558684899, 52.503397606912046 ], [ 13.300414554469269, 52.503420181278756 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 81, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300113937708867, 52.502558995486503 ], [ 13.300106649481172, 52.502545806755847 ], [ 13.300099269549612, 52.502534994882183 ], [ 13.300099774100316, 52.502521918122348 ], [ 13.30010027865063, 52.502508841362463 ], [ 13.300108392481745, 52.502500632085848 ], [ 13.300122213306111, 52.502496072521758 ], [ 13.300122442659729, 52.502490128131448 ], [ 13.300116781535035, 52.502485289540239 ], [ 13.300125031497963, 52.502473513607015 ], [ 13.300129386583821, 52.502461680811038 ], [ 13.300136995862408, 52.50246654829251 ], [ 13.300156246942539, 52.502472772582458 ], [ 13.300162045644241, 52.502474045436131 ], [ 13.300164223182545, 52.502468129037446 ], [ 13.300168532353254, 52.502457486016688 ], [ 13.300176140197612, 52.50246235257584 ], [ 13.300193536301403, 52.502466171133115 ], [ 13.300201373458133, 52.502465094198747 ], [ 13.300219274079318, 52.502455835991483 ], [ 13.300231282819354, 52.50244768176799 ], [ 13.300248952618563, 52.50244436792569 ], [ 13.300258464264777, 52.50245045136932 ], [ 13.300257868028423, 52.502465905886702 ], [ 13.300257720477349, 52.502469730425958 ], [ 13.300262225636223, 52.502469511929725 ], [ 13.300255947159576, 52.502430340529685 ], [ 13.300238157016199, 52.502422490955254 ], [ 13.300201062624932, 52.502424383825392 ], [ 13.300200280496972, 52.502428425825656 ], [ 13.300054475823432, 52.50243659700881 ], [ 13.300053729732067, 52.502431876692015 ], [ 13.300019639913396, 52.502433606779356 ], [ 13.300018168815853, 52.502427438349905 ], [ 13.299985185377041, 52.502429018886929 ], [ 13.299971261604645, 52.502439988588101 ], [ 13.299977247621879, 52.502492765797825 ], [ 13.299983093635403, 52.502544436874253 ], [ 13.300032493589333, 52.502559311475288 ], [ 13.300036642234751, 52.502553822611901 ], [ 13.300133422851488, 52.502575574723529 ], [ 13.300125120857867, 52.502572240180534 ], [ 13.300113937708867, 52.502558995486503 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 82, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299499353695612, 52.50333862767426 ], [ 13.299102826353637, 52.503246230635348 ], [ 13.299100851847708, 52.50326020246213 ], [ 13.299468492888257, 52.50335066298998 ], [ 13.299469501586199, 52.503350372686839 ], [ 13.299489257699745, 52.50334352033309 ], [ 13.299499353695612, 52.50333862767426 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 83, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299894001453387, 52.502542151403063 ], [ 13.299895253850815, 52.502519508851961 ], [ 13.299891101383952, 52.502524867283071 ], [ 13.29988502745533, 52.502530726784983 ], [ 13.299884591292471, 52.502542029662024 ], [ 13.299894001453387, 52.502542151403063 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 84, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300114434843461, 52.503267186291794 ], [ 13.300129782779717, 52.503251656015422 ], [ 13.300116505680499, 52.503252618817811 ], [ 13.300105930935542, 52.503238914425197 ], [ 13.300095503026549, 52.503252640725172 ], [ 13.300114434843461, 52.503267186291794 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 85, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298588884141404, 52.502500100144658 ], [ 13.298587582392548, 52.502499967235288 ], [ 13.298587841341117, 52.502500472668984 ], [ 13.298588884141404, 52.502500100144658 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 86, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298995012436144, 52.503221108291932 ], [ 13.298800314841802, 52.503175739904009 ], [ 13.298707567524723, 52.503152504513039 ], [ 13.298704249416748, 52.503158571720384 ], [ 13.298703719631211, 52.503162485169128 ], [ 13.298805550823998, 52.50318754243159 ], [ 13.298808437446933, 52.503186238854539 ], [ 13.298816268159538, 52.503190179014283 ], [ 13.298982982937119, 52.503231200598108 ], [ 13.298985207581444, 52.503222084926001 ], [ 13.298995012436144, 52.503221108291932 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 87, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300271283779505, 52.502512250516929 ], [ 13.300271069039232, 52.502510254979505 ], [ 13.300268451140653, 52.502510438551397 ], [ 13.300271283779505, 52.502512250516929 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 88, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300163347203771, 52.503191423333313 ], [ 13.30014501013744, 52.503187468973387 ], [ 13.300109314838256, 52.503234459349819 ], [ 13.300122087892939, 52.503231552599438 ], [ 13.300136950873771, 52.503225077594159 ], [ 13.300141122804686, 52.503220857721608 ], [ 13.300160878792479, 52.503214004355527 ], [ 13.300169128884585, 52.503202228421699 ], [ 13.300169355013258, 52.503196367603856 ], [ 13.300163909254145, 52.50319171103547 ], [ 13.300163347203771, 52.503191423333313 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 89, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.30025810328145, 52.502479170402921 ], [ 13.300257502201546, 52.502475388156014 ], [ 13.300257317658589, 52.502480171525924 ], [ 13.3002549108311, 52.50249203141852 ], [ 13.300258348543313, 52.502503976180485 ], [ 13.300260780010751, 52.50250553152776 ], [ 13.30025810328145, 52.502479170402921 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 90, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298467881686207, 52.503092455136496 ], [ 13.298205318352514, 52.503029298803334 ], [ 13.298130014688574, 52.503011185312438 ], [ 13.297973486531466, 52.502973533314787 ], [ 13.29784874025151, 52.502938991684601 ], [ 13.297853036548386, 52.502941740072316 ], [ 13.297860599721352, 52.502947796574958 ], [ 13.297868302310826, 52.502956918389074 ], [ 13.29830571637785, 52.503064552598779 ], [ 13.298310280153201, 52.503063695744473 ], [ 13.298343260571833, 52.503067738732014 ], [ 13.298352279492159, 52.503076009097725 ], [ 13.298543208334484, 52.503122990099712 ], [ 13.298547783911046, 52.503121827704746 ], [ 13.298555194399448, 52.503114329513011 ], [ 13.298467881686207, 52.503092455136496 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 91, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.29852660504859, 52.502476703957768 ], [ 13.298520778152376, 52.502476620155548 ], [ 13.298529615325606, 52.502478492445732 ], [ 13.29852660504859, 52.502476703957768 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 92, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299523347324778, 52.503116822219475 ], [ 13.299529513159113, 52.503108584982634 ], [ 13.299519820819722, 52.50310889972674 ], [ 13.299511809061631, 52.503124116381834 ], [ 13.299516787852161, 52.503124378558127 ], [ 13.299523347324778, 52.503116822219475 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 93, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298481268678987, 52.502474700544845 ], [ 13.298484228190015, 52.502468876343265 ], [ 13.298511544937067, 52.502474663945264 ], [ 13.298503687362714, 52.502464479879713 ], [ 13.29850219831814, 52.502452563089847 ], [ 13.298496537377892, 52.502447724422652 ], [ 13.298461701050512, 52.502441275707348 ], [ 13.298448294756117, 52.502435136102562 ], [ 13.29845001258321, 52.502422435546123 ], [ 13.298444354942097, 52.502432504354424 ], [ 13.298427466299071, 52.502436151048471 ], [ 13.298414955148912, 52.502461081761361 ], [ 13.298481268678987, 52.502474700544845 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 94, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299493752797639, 52.50304700357767 ], [ 13.299073675403084, 52.502956415018822 ], [ 13.299045326682821, 52.502988948539198 ], [ 13.299065699911912, 52.503016599011268 ], [ 13.299121782103304, 52.503028110244763 ], [ 13.299142777300954, 52.502989160056558 ], [ 13.29947948739677, 52.503059677540442 ], [ 13.299479052217427, 52.503058164358364 ], [ 13.299483741214505, 52.503056734720452 ], [ 13.299482875557437, 52.503054388159441 ], [ 13.299493752797639, 52.50304700357767 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 95, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299381832303927, 52.502437853286622 ], [ 13.299407616141439, 52.502426329445413 ], [ 13.299408354276711, 52.502407320946006 ], [ 13.299378592515206, 52.50240864911563 ], [ 13.299370663567949, 52.50241210375065 ], [ 13.29934705733973, 52.502417712099664 ], [ 13.299340983354485, 52.50242357157326 ], [ 13.299325397855121, 52.502423347529735 ], [ 13.299304015159041, 52.502421850610133 ], [ 13.299282907789321, 52.502413221319927 ], [ 13.299273673120931, 52.502400004554858 ], [ 13.299287584311221, 52.502393067312354 ], [ 13.299272275621883, 52.502385710916293 ], [ 13.299270786336646, 52.50237379503286 ], [ 13.299280892935734, 52.502364424022964 ], [ 13.29929453022911, 52.502364620066125 ], [ 13.299321986877521, 52.502360257514873 ], [ 13.299341834383764, 52.502351026527208 ], [ 13.299357601914933, 52.502346495935342 ], [ 13.299349259989937, 52.502360649568992 ], [ 13.299348892893645, 52.502370159691239 ], [ 13.299368327381218, 52.50237162859775 ], [ 13.29937996912914, 52.502372985482694 ], [ 13.299376669302276, 52.502357474962736 ], [ 13.299379030467076, 52.502346803967356 ], [ 13.299404538947192, 52.502342412493427 ], [ 13.299427915681822, 52.502342748520789 ], [ 13.299453148823815, 52.502345490302346 ], [ 13.299472353898951, 52.502352902681473 ], [ 13.299470040082339, 52.502362385721042 ], [ 13.299481575967333, 52.502378624226672 ], [ 13.299508894980425, 52.502377827366374 ], [ 13.299518222926887, 52.50238866727809 ], [ 13.29951558647163, 52.502406471542727 ], [ 13.299542676105153, 52.502411618164743 ], [ 13.299568001065287, 52.502411982166713 ], [ 13.299573923651074, 52.502415770772679 ], [ 13.299579119849289, 52.502342255989497 ], [ 13.299580108033128, 52.502328257389692 ], [ 13.299525998031699, 52.502326975244507 ], [ 13.299525210179059, 52.502331889305978 ], [ 13.299254711630983, 52.502325138179614 ], [ 13.299254333117418, 52.502320554413124 ], [ 13.29919623384802, 52.502319180601312 ], [ 13.299192165094494, 52.502371335873271 ], [ 13.299160306749448, 52.502425599277331 ], [ 13.299213837618176, 52.502437420853973 ], [ 13.299216263821, 52.50243395905111 ], [ 13.299430427191718, 52.502479646146057 ], [ 13.299411579326495, 52.502475154745106 ], [ 13.299398356482614, 52.502464258837669 ], [ 13.299373444477855, 52.502453195799298 ], [ 13.299381832303927, 52.502437853286622 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 96, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298204920954273, 52.502412771037811 ], [ 13.298124495951715, 52.502395635838518 ], [ 13.298104490997925, 52.502430142254923 ], [ 13.298183687346743, 52.502446169159306 ], [ 13.298204920954273, 52.502412771037811 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 97, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.296692539544077, 52.502401935975186 ], [ 13.296693091066276, 52.502387670362168 ], [ 13.296706818801374, 52.502385488930472 ], [ 13.29671488730852, 52.502378467868937 ], [ 13.296717524857515, 52.502360664574859 ], [ 13.296720024552176, 52.50234642701006 ], [ 13.296706709020999, 52.502337908538294 ], [ 13.296685832026172, 52.502323334409361 ], [ 13.296644969149547, 52.502321556540011 ], [ 13.296630780347382, 52.502335625804328 ], [ 13.29662285120213, 52.502339080253485 ], [ 13.296608708352437, 52.502351960639061 ], [ 13.29661409328037, 52.502363932650397 ], [ 13.296609599402437, 52.502379331038327 ], [ 13.29659949223686, 52.502388701815136 ], [ 13.296608955922698, 52.502395974402575 ], [ 13.296620461163347, 52.502400898207362 ], [ 13.296634326799937, 52.502395150156339 ], [ 13.296642073569288, 52.502396451231483 ], [ 13.296653439448813, 52.502404941639696 ], [ 13.29666870320855, 52.502413487268122 ], [ 13.296696020806802, 52.502412691036788 ], [ 13.296692539544077, 52.502401935975186 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 98, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297741223801516, 52.502909220414637 ], [ 13.297626606606826, 52.502877482991543 ], [ 13.297635221733991, 52.50288864906728 ], [ 13.29763609266155, 52.502899779234276 ], [ 13.29772278459064, 52.50292111211904 ], [ 13.297735213697498, 52.502913876802737 ], [ 13.297741223801516, 52.502909220414637 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 99, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298356278654442, 52.502420479565728 ], [ 13.298437208113032, 52.502280620002026 ], [ 13.298446527015754, 52.502132718207044 ], [ 13.29833332604891, 52.502130804100595 ], [ 13.298316043802297, 52.502137892348003 ], [ 13.298315524730643, 52.502162126990847 ], [ 13.298326109594829, 52.50216257864799 ], [ 13.298316959155784, 52.50226616876602 ], [ 13.298261494202054, 52.50236588372988 ], [ 13.298252145752533, 52.50236359316667 ], [ 13.298240017877141, 52.502386456900325 ], [ 13.298253437570803, 52.502397665068457 ], [ 13.298356278654442, 52.502420479565728 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 100, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297089581990994, 52.502338271006813 ], [ 13.297092081564166, 52.502324033432998 ], [ 13.297092198615463, 52.502321005078613 ], [ 13.297093344332088, 52.502319685475683 ], [ 13.297092295382486, 52.502318501519113 ], [ 13.297092587003331, 52.502310956693087 ], [ 13.297086376680065, 52.502303944964972 ], [ 13.29708511742165, 52.502302522411298 ], [ 13.297071847749937, 52.502292816002495 ], [ 13.297058579556618, 52.502283109613472 ], [ 13.297043154596798, 52.502278924252316 ], [ 13.297040406492984, 52.502277292350527 ], [ 13.297025098260026, 52.502269934768485 ], [ 13.296988177727759, 52.502267024228495 ], [ 13.296966657330074, 52.502269093516212 ], [ 13.296963752109788, 52.502269849215772 ], [ 13.29693436303892, 52.502269426150939 ], [ 13.296920817705143, 52.502266852083757 ], [ 13.296917336398103, 52.502256097028123 ], [ 13.296884632436539, 52.502244921290703 ], [ 13.296871226459858, 52.502238780610803 ], [ 13.296851790650875, 52.502237311274072 ], [ 13.296834488293616, 52.502231115396143 ], [ 13.296824212375206, 52.502234067631527 ], [ 13.296810104583514, 52.50223472408981 ], [ 13.296799269600273, 52.50223456810356 ], [ 13.296799422591622, 52.502230610575054 ], [ 13.29678801076763, 52.502223309056824 ], [ 13.296768714288561, 52.502218273998174 ], [ 13.296749417848702, 52.502213238037776 ], [ 13.296722374674236, 52.502206901005152 ], [ 13.296714895920221, 52.502204270396589 ], [ 13.296713850637717, 52.50220360168597 ], [ 13.296712958276327, 52.502203588838363 ], [ 13.29670122196015, 52.502199460135202 ], [ 13.296677845334646, 52.502199123569852 ], [ 13.296656737157898, 52.502190492896219 ], [ 13.296643009481434, 52.502192674322693 ], [ 13.296617684685742, 52.502192309695829 ], [ 13.296590687474431, 52.502184784655995 ], [ 13.2965556232863, 52.502184279784188 ], [ 13.296538089720084, 52.502184027323189 ], [ 13.296537681111355, 52.50218438288654 ], [ 13.296530022728591, 52.502191047495515 ], [ 13.29652469540609, 52.502193629490314 ], [ 13.296523217047437, 52.502193720593773 ], [ 13.296517837536932, 52.502196852099225 ], [ 13.296488744795319, 52.502199969435154 ], [ 13.296476689505969, 52.502209311251022 ], [ 13.296462638594463, 52.502219813867697 ], [ 13.296446411125562, 52.502236232830164 ], [ 13.296453928014927, 52.502243478296599 ], [ 13.29646082436104, 52.502242271176783 ], [ 13.296460896027281, 52.502242780212377 ], [ 13.296464792380318, 52.502242836317635 ], [ 13.296486717045074, 52.502238999874677 ], [ 13.296498595261953, 52.502247689188557 ], [ 13.296511725416769, 52.502260962266924 ], [ 13.296530977354649, 52.50226718716312 ], [ 13.296585567879056, 52.502266783653411 ], [ 13.296593728279506, 52.502257385746788 ], [ 13.296618664152252, 52.502246241418199 ], [ 13.296620566680181, 52.502246268810964 ], [ 13.296638717270627, 52.502253275353063 ], [ 13.296640251804163, 52.502264002387811 ], [ 13.296624254173185, 52.502274477893828 ], [ 13.296631539837893, 52.502287666809146 ], [ 13.296648843660524, 52.502293862737879 ], [ 13.296669858485105, 52.502304871143906 ], [ 13.296699033742472, 52.502306480733722 ], [ 13.296728253491516, 52.502306901419153 ], [ 13.296720138557404, 52.502293057698935 ], [ 13.296720144567574, 52.502292902237471 ], [ 13.296722661794847, 52.50229071135464 ], [ 13.296733044047656, 52.502288972674684 ], [ 13.296739624885278, 52.502293182698772 ], [ 13.29675312278224, 52.502296945641469 ], [ 13.296770472552049, 52.502301953575156 ], [ 13.296774045748039, 52.502310330882679 ], [ 13.296789781685574, 52.502314437137755 ], [ 13.296790178688344, 52.50231849789116 ], [ 13.296803538752252, 52.50232582655773 ], [ 13.296830765848464, 52.50232740806814 ], [ 13.296829542677829, 52.502320021259372 ], [ 13.296846519309497, 52.502323248045037 ], [ 13.296846211944663, 52.502331199046964 ], [ 13.296855677097369, 52.502338472534767 ], [ 13.296877105617781, 52.50233878101978 ], [ 13.296871517729235, 52.502332286244375 ], [ 13.296872615780574, 52.502331971175153 ], [ 13.296880082457061, 52.502326758563107 ], [ 13.296889300239483, 52.502325871657149 ], [ 13.296904838254889, 52.502327284876152 ], [ 13.29691416554553, 52.502338124987702 ], [ 13.296921205069781, 52.502342628424358 ], [ 13.29692090378202, 52.502343371257055 ], [ 13.296920582118799, 52.502351692491288 ], [ 13.296930353261697, 52.502353032579819 ], [ 13.296932864693867, 52.502358614514172 ], [ 13.296969877209799, 52.502359147319709 ], [ 13.296983376619639, 52.502362910257617 ], [ 13.296989106316445, 52.502366118082698 ], [ 13.296989885093074, 52.502374100903907 ], [ 13.297017158181044, 52.502374493495566 ], [ 13.29702373446213, 52.502370665292268 ], [ 13.297041633860033, 52.502368507007432 ], [ 13.297053506769371, 52.502363919760953 ], [ 13.297073538520102, 52.502349934551738 ], [ 13.297089581990994, 52.502338271006813 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 101, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297902582082846, 52.502891630072604 ], [ 13.297978823667597, 52.502765702692116 ], [ 13.297979253797143, 52.502764980591948 ], [ 13.298003145486824, 52.502725523805729 ], [ 13.298002615077976, 52.502725409179668 ], [ 13.297897934417936, 52.50270283045613 ], [ 13.297883686928456, 52.502724832870207 ], [ 13.297941898875639, 52.502731617159441 ], [ 13.297884525303433, 52.502854496422941 ], [ 13.297820698256842, 52.502841683630116 ], [ 13.297816387008641, 52.502852326538402 ], [ 13.297817600312221, 52.502871375695442 ], [ 13.297840609879609, 52.502881222152631 ], [ 13.297863803151891, 52.502886313997713 ], [ 13.297889128362749, 52.50288667836125 ], [ 13.297902582082846, 52.502891630072604 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 102, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298542955647271, 52.502305655610982 ], [ 13.298568464147998, 52.502301265216467 ], [ 13.298571855510598, 52.502314398002348 ], [ 13.298581045510371, 52.50232880371879 ], [ 13.298596721320932, 52.502326650084534 ], [ 13.298600755420221, 52.50232313948807 ], [ 13.29861837941006, 52.502321013868404 ], [ 13.298637584296415, 52.502328427280439 ], [ 13.298643199292213, 52.502334455716792 ], [ 13.298662266493727, 52.502345434857844 ], [ 13.298676144955845, 52.502353426225326 ], [ 13.298684327033891, 52.502340212618215 ], [ 13.298692185648743, 52.502311261535354 ], [ 13.298727010935119, 52.502315437945114 ], [ 13.298729557992612, 52.502293813842478 ], [ 13.298720004326688, 52.502293365362014 ], [ 13.298725205707544, 52.502187930977193 ], [ 13.298742153534819, 52.502188166598749 ], [ 13.298743917393649, 52.502147066156603 ], [ 13.298752881995739, 52.502147468401418 ], [ 13.298753390822135, 52.502124599375925 ], [ 13.298525605926939, 52.502118774584545 ], [ 13.298522804729453, 52.502148960957499 ], [ 13.298523463337977, 52.50214896053928 ], [ 13.298524117613198, 52.502148996023593 ], [ 13.298524769652442, 52.502149051256403 ], [ 13.298525409926286, 52.502149144083113 ], [ 13.29852604217807, 52.502149253877732 ], [ 13.298526655371175, 52.502149399363113 ], [ 13.298527256055142, 52.502149563550098 ], [ 13.298527828949325, 52.502149760604951 ], [ 13.298528387965927, 52.502149973644343 ], [ 13.298528910426908, 52.502150217627246 ], [ 13.298529413154808, 52.502150476611448 ], [ 13.298529873575742, 52.502150762859934 ], [ 13.298530311387957, 52.502151062270052 ], [ 13.298530702614341, 52.502151385286489 ], [ 13.298531068391069, 52.502151718726346 ], [ 13.298531380323359, 52.502152072970702 ], [ 13.298531666944768, 52.502152434044035 ], [ 13.298531894005091, 52.502152811344047 ], [ 13.298532094351382, 52.502153193654614 ], [ 13.298532232434459, 52.502153585859169 ], [ 13.298532340927862, 52.502153981234635 ], [ 13.298532390241885, 52.502154382951929 ], [ 13.298532407194692, 52.502154783304746 ], [ 13.298532362231224, 52.502155184565297 ], [ 13.298532286448509, 52.502155582685269 ], [ 13.298532150430292, 52.502155976342436 ], [ 13.298531985134764, 52.50215636508289 ], [ 13.29853175991606, 52.502156741272998 ], [ 13.298530165100352, 52.502173201027993 ], [ 13.298542522028786, 52.502174807447837 ], [ 13.298541295294415, 52.502193334181698 ], [ 13.298554029201384, 52.502193801440548 ], [ 13.298550184033463, 52.502275209225587 ], [ 13.29853706706723, 52.50227474095297 ], [ 13.298534078908791, 52.502292256916967 ], [ 13.298546261668283, 52.502293719669282 ], [ 13.298507330606384, 52.502362322869743 ], [ 13.29848032150883, 52.502368485416142 ], [ 13.298471790660392, 52.502383670261842 ], [ 13.298481805796019, 52.502374955261431 ], [ 13.298507406205699, 52.502368186227699 ], [ 13.298519598854647, 52.502355277570537 ], [ 13.298527988429807, 52.50233993514167 ], [ 13.298532711166482, 52.502318593188079 ], [ 13.298542955647271, 52.502305655610982 ] ], [ [ 13.298661844027198, 52.502167587991181 ], [ 13.298673852937409, 52.502159434825799 ], [ 13.298691109622759, 52.502166819313963 ], [ 13.298706419575971, 52.502174176703832 ], [ 13.298707908702886, 52.502186092593377 ], [ 13.298701651031806, 52.50219670754295 ], [ 13.298691634695086, 52.5022037007272 ], [ 13.298675775330338, 52.502210608984754 ], [ 13.298656432758952, 52.502206762210847 ], [ 13.298660788175319, 52.502194930370173 ], [ 13.298665189498793, 52.502181909652073 ], [ 13.298661844027198, 52.502167587991181 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 103, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.296543871212537, 52.502365300638431 ], [ 13.296546279029613, 52.502353440829552 ], [ 13.296546738664336, 52.502341552969284 ], [ 13.296549284379724, 52.502326126532573 ], [ 13.296547749866546, 52.502315399496908 ], [ 13.296536658325913, 52.502299776700845 ], [ 13.296517315910311, 52.502295929578473 ], [ 13.296497697689107, 52.502299215708177 ], [ 13.296484198341407, 52.502295452715288 ], [ 13.29646625251763, 52.502305899248533 ], [ 13.29645057661677, 52.502308052599638 ], [ 13.296442645990115, 52.502311507015463 ], [ 13.2964303148398, 52.502327982081049 ], [ 13.296431757384662, 52.50234108686962 ], [ 13.296450824029769, 52.502352066362427 ], [ 13.296444657732573, 52.502360303435808 ], [ 13.296466178162516, 52.502358235142303 ], [ 13.296481900049308, 52.502354892913438 ], [ 13.29649345123325, 52.502358627854562 ], [ 13.296506857227127, 52.502364767678408 ], [ 13.29651655216921, 52.502366096813631 ], [ 13.296543871212537, 52.502365300638431 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 104, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297317273075336, 52.502795190901097 ], [ 13.297317632137171, 52.5027946305214 ], [ 13.297309257616179, 52.502799505512201 ], [ 13.297299057132228, 52.502811254079916 ], [ 13.297290312081705, 52.502814508015561 ], [ 13.297297175371815, 52.502816369072072 ], [ 13.297297261419613, 52.502816391889404 ], [ 13.297297833003904, 52.502816546672605 ], [ 13.297548603914814, 52.502878250499172 ], [ 13.297561929328152, 52.502868562708301 ], [ 13.297573156820613, 52.502862683075818 ], [ 13.297506779447499, 52.50284430291876 ], [ 13.297317273075336, 52.502795190901097 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 105, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297846792769501, 52.502331823908989 ], [ 13.297763022587304, 52.502315055726505 ], [ 13.297745298393075, 52.502349937465759 ], [ 13.297825394224073, 52.502367540227027 ], [ 13.297846792769501, 52.502331823908989 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 106, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.29641189032492, 52.50230035830247 ], [ 13.296412671753464, 52.502280149210819 ], [ 13.296396949888431, 52.502283491430717 ], [ 13.296382992349171, 52.502291616307012 ], [ 13.296371716986899, 52.502280748999169 ], [ 13.296370418791428, 52.502280410217644 ], [ 13.296345918565404, 52.502332753121813 ], [ 13.296350030154077, 52.502337530920038 ], [ 13.296357455135016, 52.502347153245452 ], [ 13.296373268934198, 52.502341434178135 ], [ 13.296393071067362, 52.502333392566115 ], [ 13.296399053521485, 52.50232991010003 ], [ 13.296413240922607, 52.50231584084289 ], [ 13.29641189032492, 52.50230035830247 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 107, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.29776095774306, 52.502723067059357 ], [ 13.297789327581437, 52.502679405720336 ], [ 13.297627116871199, 52.502644415610355 ], [ 13.297626738739606, 52.502654201600528 ], [ 13.297627906019319, 52.502674439635882 ], [ 13.297592931828646, 52.502671557294015 ], [ 13.29755809690117, 52.502665109232318 ], [ 13.297532863703719, 52.502662367046099 ], [ 13.29752761631347, 52.502646828445485 ], [ 13.29753619005322, 52.502626730585355 ], [ 13.297534423504372, 52.502624442075749 ], [ 13.297490723462323, 52.502615016207073 ], [ 13.297489620062221, 52.5026213031691 ], [ 13.29746026082703, 52.502624448379258 ], [ 13.297455059409545, 52.502607720899192 ], [ 13.297453004083916, 52.502606881212657 ], [ 13.297130473611634, 52.502537300191996 ], [ 13.29708711545865, 52.502607507084413 ], [ 13.297100429672119, 52.502616025493069 ], [ 13.297115601687995, 52.502626948817216 ], [ 13.29711699849261, 52.502641242474795 ], [ 13.297108378641775, 52.502662528280375 ], [ 13.297101237663508, 52.502667204323807 ], [ 13.297103643832303, 52.502672842282607 ], [ 13.297104993209164, 52.502688324795159 ], [ 13.297128186326793, 52.502693415884394 ], [ 13.297145903871716, 52.502688913653529 ], [ 13.297173269057923, 52.502686928458495 ], [ 13.297192519838696, 52.502693152329115 ], [ 13.297209914291983, 52.502696971302321 ], [ 13.297231021347178, 52.50270560185831 ], [ 13.297241175875254, 52.502695042172476 ], [ 13.297245439824481, 52.502685588140999 ], [ 13.29726881671492, 52.502685924596385 ], [ 13.297276194534446, 52.502696735720832 ], [ 13.297275597246969, 52.502712190213678 ], [ 13.297292901292943, 52.50271838694853 ], [ 13.297294390052169, 52.502730302851781 ], [ 13.297280385112199, 52.502739616688643 ], [ 13.297282011677252, 52.502747966861811 ], [ 13.297311554829232, 52.50274006529299 ], [ 13.297317721020146, 52.502731828173971 ], [ 13.297318272346535, 52.502717562557152 ], [ 13.297332827120121, 52.50269398307745 ], [ 13.297333332529325, 52.502680905438105 ], [ 13.297370253433074, 52.502683815873297 ], [ 13.297397296995289, 52.50269015276065 ], [ 13.297395027220375, 52.502698445958977 ], [ 13.29737522510071, 52.502706487738365 ], [ 13.297380426498576, 52.502723215221543 ], [ 13.297381869336585, 52.502736320000196 ], [ 13.297381383808952, 52.502739428468139 ], [ 13.297416121908764, 52.502749031104081 ], [ 13.297435262157897, 52.502707675438998 ], [ 13.297686446190344, 52.502764815587646 ], [ 13.297664004981035, 52.502810881943645 ], [ 13.297699391244196, 52.502803064355696 ], [ 13.29772726173894, 52.502788002289812 ], [ 13.29776095774306, 52.502723067059357 ] ], [ [ 13.29738868741039, 52.502610334334733 ], [ 13.297400742620152, 52.502600992425819 ], [ 13.297421665927583, 52.5026143775547 ], [ 13.297421344355769, 52.502622698790731 ], [ 13.297446347807433, 52.502631385382379 ], [ 13.297442039435962, 52.502642028319151 ], [ 13.297432022798837, 52.502649021393729 ], [ 13.297420563345383, 52.502642908790399 ], [ 13.297396451398441, 52.502661593488135 ], [ 13.297371631685918, 52.5026481522792 ], [ 13.297370603832489, 52.502624347634438 ], [ 13.297374958164401, 52.502612515823984 ], [ 13.29738868741039, 52.502610334334733 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 108, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300324930541557, 52.502389776039685 ], [ 13.30032014557905, 52.502363791929305 ], [ 13.300273280023886, 52.502366520886603 ], [ 13.300276519729577, 52.5023925727102 ], [ 13.300324930541557, 52.502389776039685 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 109, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300012963891158, 52.502347009107282 ], [ 13.30001953514021, 52.502338531311494 ], [ 13.300019742933411, 52.502338262762756 ], [ 13.300021169454276, 52.502336422080248 ], [ 13.300031093867375, 52.502331806538109 ], [ 13.300033151926757, 52.502331010717697 ], [ 13.30003702869257, 52.502329512742499 ], [ 13.300049103779564, 52.50233025449559 ], [ 13.300062307739942, 52.50233106551633 ], [ 13.300063423107947, 52.502329575515198 ], [ 13.300068473360856, 52.502322828248587 ], [ 13.30008025420638, 52.502320618451655 ], [ 13.300100147374906, 52.502310199355769 ], [ 13.300102691845732, 52.502294772831938 ], [ 13.300103242261933, 52.502280507193461 ], [ 13.300099758737005, 52.502269751305136 ], [ 13.300086442531823, 52.502261234106967 ], [ 13.300071133786691, 52.502253876914757 ], [ 13.30004413593011, 52.502246352657806 ], [ 13.300009025756772, 52.502247037686899 ], [ 13.299999479979927, 52.502247988455601 ], [ 13.299977718722159, 52.502250156430037 ], [ 13.299962670453304, 52.502255599258241 ], [ 13.299961905412927, 52.502255875983479 ], [ 13.299942331856231, 52.502257973787593 ], [ 13.299941919014064, 52.502268672790962 ], [ 13.299926151534569, 52.502273204360151 ], [ 13.299908847619845, 52.502272955699354 ], [ 13.299894982105476, 52.502272756447226 ], [ 13.299889506012486, 52.502263162354708 ], [ 13.299874058196536, 52.50225937175005 ], [ 13.299848641564607, 52.502261385570193 ], [ 13.299841392404776, 52.502268721621064 ], [ 13.299840529203136, 52.502269594850439 ], [ 13.299832415331412, 52.502277805007559 ], [ 13.299818365188603, 52.502288308029264 ], [ 13.29980064950758, 52.502292811584134 ], [ 13.299794208600389, 52.502308181206331 ], [ 13.299813415155931, 52.502315594450295 ], [ 13.299815042222697, 52.502323943694506 ], [ 13.299826362823808, 52.50233362268095 ], [ 13.299831682628565, 52.502336347043247 ], [ 13.299835920265496, 52.502338517280037 ], [ 13.299870892881572, 52.502341398936814 ], [ 13.299896217805665, 52.502341762868198 ], [ 13.299913749992665, 52.50234201481102 ], [ 13.299937219956721, 52.502339973003004 ], [ 13.299952849799379, 52.502339008965194 ], [ 13.299970290271295, 52.502341637756999 ], [ 13.29997993944496, 52.502344155487208 ], [ 13.299999419818272, 52.502344435412105 ], [ 13.300012963891158, 52.502347009107282 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 110, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.296941469338972, 52.502593516072835 ], [ 13.29690881108017, 52.502581151464454 ], [ 13.296872395839948, 52.502565163249699 ], [ 13.296826147719848, 52.502551413441189 ], [ 13.296785606316186, 52.502541314385205 ], [ 13.296738852700887, 52.502540641280177 ], [ 13.296713527704009, 52.502540276672164 ], [ 13.296712976185388, 52.502554542284926 ], [ 13.296609773945656, 52.502551866871684 ], [ 13.296612135790868, 52.502541195937617 ], [ 13.29660244080732, 52.502539866809933 ], [ 13.296571930328891, 52.502540358100568 ], [ 13.296591907288992, 52.502548907763639 ], [ 13.296605129491304, 52.502559803080281 ], [ 13.296617985449526, 52.502580209423776 ], [ 13.296645258659781, 52.502580602102206 ], [ 13.296701659909758, 52.502583792321111 ], [ 13.296760148683097, 52.502583444852974 ], [ 13.296786962288776, 52.502595726258548 ], [ 13.2968101093595, 52.502602006285485 ], [ 13.296828946412916, 52.502618930098635 ], [ 13.296842352498064, 52.502625070783893 ], [ 13.296861512758722, 52.502633672481053 ], [ 13.296890778685336, 52.502632904252117 ], [ 13.296904553918161, 52.502629533943271 ], [ 13.296910812083922, 52.502618919093763 ], [ 13.296934464641767, 52.502612123257137 ], [ 13.296964006289919, 52.502604221753494 ], [ 13.296967141480799, 52.502603425307321 ], [ 13.296941469338972, 52.502593516072835 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 111, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299345245074996, 52.502662951010755 ], [ 13.299369666786765, 52.502622364186223 ], [ 13.299371663784818, 52.502622807387482 ], [ 13.299372326719737, 52.502621473630043 ], [ 13.29935674262267, 52.502621249611224 ], [ 13.299348261495266, 52.502638969855255 ], [ 13.299323074085153, 52.502635039172546 ], [ 13.299341799506369, 52.502604382181993 ], [ 13.299254549619707, 52.502592422993764 ], [ 13.29923990411462, 52.502618380474274 ], [ 13.299272561597119, 52.502630744423669 ], [ 13.299262133709348, 52.502648436675813 ], [ 13.299345245074996, 52.502662951010755 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 112, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298097200539562, 52.502329454474491 ], [ 13.298143493256894, 52.502245324281795 ], [ 13.298149149452373, 52.502164119684025 ], [ 13.298135750724478, 52.502147870431294 ], [ 13.298062248694688, 52.50214647139417 ], [ 13.298060397011373, 52.502173695325858 ], [ 13.298020948864128, 52.502173208742093 ], [ 13.297989390184064, 52.502187148760306 ], [ 13.297956497544135, 52.502172499098499 ], [ 13.297866477375516, 52.50216252564212 ], [ 13.297856832168542, 52.502172456130943 ], [ 13.297830225171147, 52.502171987000203 ], [ 13.297807828217005, 52.502156018243305 ], [ 13.297683038305186, 52.50214219244792 ], [ 13.297681475515574, 52.502189808109556 ], [ 13.297649297604931, 52.502241391633937 ], [ 13.297768902893297, 52.502259148450655 ], [ 13.297801669699334, 52.502247233611378 ], [ 13.297831982614859, 52.502252269657774 ], [ 13.297838278284571, 52.502265083705133 ], [ 13.297913897780528, 52.50227677861313 ], [ 13.297943176812566, 52.502270515783209 ], [ 13.297928516936327, 52.502267080622929 ], [ 13.297905461736359, 52.502258423061107 ], [ 13.297899984607904, 52.502248828859017 ], [ 13.297906426199139, 52.502233458446682 ], [ 13.297918527159815, 52.502222926707205 ], [ 13.297924417586071, 52.50222182281491 ], [ 13.297938696326959, 52.502205375616604 ], [ 13.297937482982775, 52.502186326458492 ], [ 13.297955108420007, 52.502184200958595 ], [ 13.297976261308991, 52.502191642504698 ], [ 13.29801535966177, 52.502188636379344 ], [ 13.298029272451886, 52.502181699306874 ], [ 13.298058583990178, 52.502179741909444 ], [ 13.298074215283922, 52.502178778140838 ], [ 13.298101534191057, 52.502177981604717 ], [ 13.298106963958048, 52.502188764653567 ], [ 13.298095046751252, 52.502194540905315 ], [ 13.298104512118273, 52.502201813396269 ], [ 13.298119636848854, 52.502213925449027 ], [ 13.298132675638394, 52.502229576105428 ], [ 13.298132216433329, 52.502241463975707 ], [ 13.298135469889617, 52.502258163403724 ], [ 13.298127034270433, 52.5022746946808 ], [ 13.29811316886285, 52.502280442908848 ], [ 13.298095544866626, 52.502282568453168 ], [ 13.298077873505411, 52.50228588195192 ], [ 13.29806586441657, 52.502294035954073 ], [ 13.298048332265463, 52.502293783737656 ], [ 13.298019065105393, 52.502294552233828 ], [ 13.298013892938965, 52.502295522604612 ], [ 13.298019824596604, 52.502296402762312 ], [ 13.29800410566015, 52.502323013598328 ], [ 13.298076899566114, 52.502340424824894 ], [ 13.298097200539562, 52.502329454474491 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 113, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299072762241492, 52.502264506574853 ], [ 13.299069555800203, 52.502246618313293 ], [ 13.2990559185449, 52.50224642224449 ], [ 13.299034168782175, 52.502254435397859 ], [ 13.299020257596897, 52.502261372610732 ], [ 13.299004901659902, 52.502255204138329 ], [ 13.298999424291214, 52.502245609985088 ], [ 13.298978087594856, 52.502242924129931 ], [ 13.298958148294272, 52.502254531912655 ], [ 13.298942288917328, 52.502261441104686 ], [ 13.29894364040404, 52.502276923623846 ], [ 13.29895672540834, 52.502291385314535 ], [ 13.298973890390423, 52.502301147516903 ], [ 13.29898276967981, 52.502304714323259 ], [ 13.298989246338884, 52.502307315993413 ], [ 13.298989062769621, 52.502312070604354 ], [ 13.298994492735847, 52.502322854512919 ], [ 13.299021765820452, 52.50232324664232 ], [ 13.299035403099287, 52.502323442715252 ], [ 13.299046907159862, 52.502328365366459 ], [ 13.299070651038537, 52.502319191343418 ], [ 13.299070834482242, 52.502318523236454 ], [ 13.299072920477597, 52.502310898109577 ], [ 13.299069574864353, 52.502296576459308 ], [ 13.299060202621597, 52.502286925411816 ], [ 13.29906840840068, 52.502276338452596 ], [ 13.299072762241492, 52.502264506574853 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 114, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299727455257582, 52.50234638577696 ], [ 13.29972823133431, 52.502343188866412 ], [ 13.299778806898116, 52.502344352690173 ], [ 13.299779349622664, 52.50234349913184 ], [ 13.299780020613857, 52.502342681583464 ], [ 13.299780755668982, 52.502341883837289 ], [ 13.299781612684404, 52.50234113279997 ], [ 13.299782526192924, 52.50234040685077 ], [ 13.299783548027319, 52.502339737304673 ], [ 13.299784623166998, 52.502339099094812 ], [ 13.299785790122362, 52.502338525142925 ], [ 13.299787001374291, 52.502337986893345 ], [ 13.299788292384127, 52.50233751992144 ], [ 13.299789617209253, 52.502337092996804 ], [ 13.299791003878932, 52.502336743386238 ], [ 13.29979241549397, 52.502336434594596 ], [ 13.299793868129791, 52.502336208212469 ], [ 13.299795338244188, 52.502336025239359 ], [ 13.299796828590241, 52.502335928872569 ], [ 13.299798326107062, 52.502335875766647 ], [ 13.299799824712441, 52.502335908991974 ], [ 13.299831682628565, 52.502336347043247 ], [ 13.299826362823808, 52.50233362268095 ], [ 13.299815042222697, 52.502323943694506 ], [ 13.299813415155931, 52.502315594450295 ], [ 13.299794208600389, 52.502308181206331 ], [ 13.29980064950758, 52.502292811584134 ], [ 13.299818365188603, 52.502288308029264 ], [ 13.299832415331412, 52.502277805007559 ], [ 13.299840529203136, 52.502269594850439 ], [ 13.299841392404776, 52.502268721621064 ], [ 13.299841584583875, 52.502236938652935 ], [ 13.299864442777285, 52.502236660235695 ], [ 13.299851738348815, 52.502230083114569 ], [ 13.299852193856665, 52.502216369971343 ], [ 13.299843970717115, 52.502211108825385 ], [ 13.299844239873581, 52.502162860458775 ], [ 13.299826685652983, 52.502153025355497 ], [ 13.29982251909454, 52.502154129839745 ], [ 13.299818213708054, 52.50215501384227 ], [ 13.299813849336726, 52.502155784606835 ], [ 13.299809393675041, 52.502156324783421 ], [ 13.299804904200551, 52.502156748487245 ], [ 13.299800372237868, 52.502156936909714 ], [ 13.299795831599717, 52.502157006523333 ], [ 13.299791297136805, 52.502156839756616 ], [ 13.299786777593937, 52.50215655362112 ], [ 13.299782315661465, 52.502156034541855 ], [ 13.299777893578339, 52.502155399149345 ], [ 13.299773576088397, 52.502154535084806 ], [ 13.299769324780288, 52.50215355958111 ], [ 13.299765224805414, 52.502152365967461 ], [ 13.299761211385315, 52.50215106750133 ], [ 13.299757395969122, 52.502149563284554 ], [ 13.29972105482898, 52.502149085056452 ], [ 13.299714714690658, 52.502142219957761 ], [ 13.299703842878635, 52.502142675110647 ], [ 13.29968354155981, 52.502155919606729 ], [ 13.299688776350784, 52.502158899003412 ], [ 13.299681963340056, 52.502237548813135 ], [ 13.299674069470127, 52.502241661229974 ], [ 13.299669019456649, 52.502344759203716 ], [ 13.299727455257582, 52.50234638577696 ] ], [ [ 13.29975281232551, 52.502207060944109 ], [ 13.29976296610176, 52.50219650193349 ], [ 13.299795577545204, 52.502210054615837 ], [ 13.29980055041535, 52.502232726390829 ], [ 13.299799724662662, 52.502254124396089 ], [ 13.299795231907487, 52.502269522914816 ], [ 13.299790509752384, 52.50229086582285 ], [ 13.299764817853319, 52.502300011993512 ], [ 13.299747606813273, 52.502291438782287 ], [ 13.299748891396632, 52.502258151998944 ], [ 13.299752261762652, 52.502221327478807 ], [ 13.29975281232551, 52.502207060944109 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 115, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298019065105393, 52.502294552233828 ], [ 13.298048332265463, 52.502293783737656 ], [ 13.29806586441657, 52.502294035954073 ], [ 13.298077873505411, 52.50228588195192 ], [ 13.298095544866626, 52.502282568453168 ], [ 13.29811316886285, 52.502280442908848 ], [ 13.298127034270433, 52.5022746946808 ], [ 13.298135469889617, 52.502258163403724 ], [ 13.298132216433329, 52.502241463975707 ], [ 13.298132675638394, 52.502229576105428 ], [ 13.298119636848854, 52.502213925449027 ], [ 13.298104512118273, 52.502201813396269 ], [ 13.298095046751252, 52.502194540905315 ], [ 13.298106963958048, 52.502188764653567 ], [ 13.298101534191057, 52.502177981604717 ], [ 13.298074215283922, 52.502178778140838 ], [ 13.298058583990178, 52.502179741909444 ], [ 13.298029272451886, 52.502181699306874 ], [ 13.29801535966177, 52.502188636379344 ], [ 13.297976261308991, 52.502191642504698 ], [ 13.297955108420007, 52.502184200958595 ], [ 13.297937482982775, 52.502186326458492 ], [ 13.297938696326959, 52.502205375616604 ], [ 13.297924417586071, 52.50222182281491 ], [ 13.297918527159815, 52.502222926707205 ], [ 13.297906426199139, 52.502233458446682 ], [ 13.297899984607904, 52.502248828859017 ], [ 13.297905461736359, 52.502258423061107 ], [ 13.297928516936327, 52.502267080622929 ], [ 13.297943176812566, 52.502270515783209 ], [ 13.297955560327305, 52.502273417381559 ], [ 13.297965070108988, 52.50227950188421 ], [ 13.297978339975835, 52.502289208194433 ], [ 13.298007284168161, 52.502296761824695 ], [ 13.298013892938965, 52.502295522604612 ], [ 13.298019065105393, 52.502294552233828 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 116, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297065618216452, 52.502598871784784 ], [ 13.297103425457363, 52.502531466684623 ], [ 13.296827003258365, 52.50247184631656 ], [ 13.29682025566391, 52.502476331997919 ], [ 13.296791173648037, 52.502472345604843 ], [ 13.296775634113024, 52.502470932349347 ], [ 13.296770204674822, 52.502460148343694 ], [ 13.296762802831402, 52.502458004371391 ], [ 13.296762481579693, 52.502457929614899 ], [ 13.296448632062456, 52.502390236674046 ], [ 13.296463189403992, 52.502399560686719 ], [ 13.296478453124461, 52.502408106339345 ], [ 13.29647595334789, 52.502422343898253 ], [ 13.296451657283658, 52.502445783006408 ], [ 13.296439692350775, 52.502452747943323 ], [ 13.296429539196653, 52.502463306682188 ], [ 13.296424861361654, 52.502483460565379 ], [ 13.296434954387625, 52.502491870626493 ], [ 13.296492298183942, 52.502504975644214 ], [ 13.296552275369566, 52.502516545088561 ], [ 13.296634140846026, 52.502516534273539 ], [ 13.296706171980938, 52.502518760889366 ], [ 13.296780152785814, 52.502521015530121 ], [ 13.296826815969249, 52.502524065493269 ], [ 13.296882895420108, 52.502535578656193 ], [ 13.296909800981508, 52.502545481383301 ], [ 13.296950250505628, 52.502557958149183 ], [ 13.29697887450927, 52.502573833286014 ], [ 13.296996039155198, 52.502583595772123 ], [ 13.297013791420927, 52.502593441341389 ], [ 13.297017064069461, 52.502593091038541 ], [ 13.297017427805413, 52.502595459165491 ], [ 13.29702466319201, 52.502599470898083 ], [ 13.297065618216452, 52.502598871784784 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 117, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300019755104996, 52.502338520086298 ], [ 13.300019742933411, 52.502338262762756 ], [ 13.30001953514021, 52.502338531311494 ], [ 13.300019755104996, 52.502338520086298 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 118, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.30016377136913, 52.502176703918977 ], [ 13.300160150229493, 52.50216951466664 ], [ 13.300138675885162, 52.502170395654915 ], [ 13.300111402879351, 52.502170003783711 ], [ 13.300095773065056, 52.502170968741694 ], [ 13.300085987781317, 52.502172017676578 ], [ 13.30007597166734, 52.502179010980008 ], [ 13.300065772097907, 52.502190758896312 ], [ 13.300065313414928, 52.502202646778535 ], [ 13.300056971744416, 52.502216800464602 ], [ 13.300029377636285, 52.502224729822132 ], [ 13.300027154219848, 52.502231835096573 ], [ 13.300042508857061, 52.502238002517657 ], [ 13.300063845620301, 52.502240688181359 ], [ 13.300073357162574, 52.502246772538122 ], [ 13.300084631964879, 52.502257639479701 ], [ 13.300097994004936, 52.50226496869859 ], [ 13.300109360559356, 52.502273457881657 ], [ 13.300110941810239, 52.502282996000481 ], [ 13.300114333597691, 52.502296129644684 ], [ 13.300133538765216, 52.502303541917314 ], [ 13.300164983400816, 52.502296857397418 ], [ 13.300175183003896, 52.502285108574156 ], [ 13.300194984397303, 52.502277067218238 ], [ 13.300197482964476, 52.502262829570157 ], [ 13.300209445787097, 52.502255864226342 ], [ 13.300227116983143, 52.502252550407739 ], [ 13.300229568198436, 52.502239501615875 ], [ 13.30023117504075, 52.502238104090978 ], [ 13.300245704009086, 52.50222545990006 ], [ 13.300242358054366, 52.502211137381003 ], [ 13.300237201455465, 52.502193222034016 ], [ 13.30023375876446, 52.502187975651175 ], [ 13.300231677991961, 52.502184815915065 ], [ 13.300212289422429, 52.502182158272632 ], [ 13.300192900820663, 52.502179501525703 ], [ 13.30016377136913, 52.502176703918977 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 119, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.296418064555114, 52.502530941505476 ], [ 13.296487308473955, 52.502531938581498 ], [ 13.296498767802058, 52.502538050375406 ], [ 13.296527989174168, 52.50253847113278 ], [ 13.296532386961939, 52.502536648098854 ], [ 13.296509117428849, 52.502533765832077 ], [ 13.29644719351541, 52.502522168334004 ], [ 13.296434925227569, 52.502519221482551 ], [ 13.296431091224317, 52.50252399185932 ], [ 13.296418064555114, 52.502530941505476 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 120, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300049103779564, 52.50233025449559 ], [ 13.30003702869257, 52.502329512742499 ], [ 13.300033151926757, 52.502331010717697 ], [ 13.300049103779564, 52.50233025449559 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 121, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.296801398761433, 52.502179491599783 ], [ 13.296817351794271, 52.502170205864907 ], [ 13.296842676579855, 52.502170570448932 ], [ 13.296850329926887, 52.502174249241179 ], [ 13.296864105018484, 52.502170878935623 ], [ 13.296874397346251, 52.502156753550267 ], [ 13.296892159218679, 52.502151061559438 ], [ 13.296907972879112, 52.502145342419325 ], [ 13.296910288706151, 52.502135859455329 ], [ 13.296902679810339, 52.502130992668214 ], [ 13.296900915466439, 52.502126209118238 ], [ 13.296887693317915, 52.502115313834359 ], [ 13.296874333337533, 52.502107984278858 ], [ 13.296870896538771, 52.50209604032441 ], [ 13.296843991258493, 52.502086137590162 ], [ 13.296824648908279, 52.502082290518445 ], [ 13.296791623414414, 52.502079435990439 ], [ 13.296781560879815, 52.502087616988277 ], [ 13.296742278696991, 52.502095378210221 ], [ 13.296715005785675, 52.502094985556681 ], [ 13.296699421896284, 52.50209476118949 ], [ 13.296685646816425, 52.502098131476004 ], [ 13.296677763681558, 52.502100397053354 ], [ 13.296673132000134, 52.502119361174216 ], [ 13.296676752564348, 52.50212655052848 ], [ 13.296688348196017, 52.502129096553119 ], [ 13.296670402488605, 52.502139543118567 ], [ 13.296660156063577, 52.502152479609713 ], [ 13.296644112549105, 52.502164143096373 ], [ 13.296657104832757, 52.502180982786918 ], [ 13.296668288314232, 52.502194227820112 ], [ 13.29670530068702, 52.502194760707241 ], [ 13.296724644548046, 52.502198607819906 ], [ 13.296749831465084, 52.502202539052043 ], [ 13.296771581642652, 52.502194525423285 ], [ 13.296795142072089, 52.502190107364427 ], [ 13.296801398761433, 52.502179491599783 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 122, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.296418064555114, 52.502530941505476 ], [ 13.296415229888282, 52.502530900686892 ], [ 13.296407208783652, 52.502536731973983 ], [ 13.296418064555114, 52.502530941505476 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 123, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299069848682485, 52.502353064089306 ], [ 13.29907507604446, 52.50231864445994 ], [ 13.299070834482238, 52.50231852323644 ], [ 13.299070651038534, 52.50231919134341 ], [ 13.299046907159859, 52.502328365366445 ], [ 13.29903540309928, 52.502323442715245 ], [ 13.299021765820447, 52.502323246642312 ], [ 13.298994492735842, 52.502322854512904 ], [ 13.298989062769618, 52.502312070604361 ], [ 13.298989246338881, 52.502307315993406 ], [ 13.298982769679803, 52.502304714323259 ], [ 13.298951179996971, 52.502296642764229 ], [ 13.29894748911217, 52.502341789418118 ], [ 13.298994150802955, 52.502344838504975 ], [ 13.298993875397416, 52.502351971769365 ], [ 13.299069848682485, 52.502353064089306 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 124, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300063423107947, 52.502329575515198 ], [ 13.300200825791286, 52.502323059369225 ], [ 13.300203029069836, 52.502329688775909 ], [ 13.300241643582043, 52.502327422132588 ], [ 13.300239496382929, 52.502307080137925 ], [ 13.300230724572355, 52.502306453298374 ], [ 13.30022252374553, 52.502260437139803 ], [ 13.300235246092123, 52.502260670279838 ], [ 13.30023117504075, 52.502238104090978 ], [ 13.300229568198436, 52.502239501615875 ], [ 13.300227116983143, 52.502252550407739 ], [ 13.300209445787097, 52.502255864226342 ], [ 13.300197482964476, 52.502262829570157 ], [ 13.300194984397303, 52.502277067218238 ], [ 13.300175183003896, 52.502285108574156 ], [ 13.300164983400816, 52.502296857397418 ], [ 13.300133538765216, 52.502303541917314 ], [ 13.300114333597691, 52.502296129644684 ], [ 13.300110941810239, 52.502282996000481 ], [ 13.300109360559356, 52.502273457881657 ], [ 13.300097994004936, 52.50226496869859 ], [ 13.300084631964879, 52.502257639479701 ], [ 13.300073357162574, 52.502246772538122 ], [ 13.300063845620301, 52.502240688181359 ], [ 13.300042508857061, 52.502238002517657 ], [ 13.300027154219848, 52.502231835096573 ], [ 13.300029377636285, 52.502224729822132 ], [ 13.300056971744416, 52.502216800464602 ], [ 13.300065313414928, 52.502202646778535 ], [ 13.300065772097907, 52.502190758896312 ], [ 13.30007597166734, 52.502179010980008 ], [ 13.300085987781317, 52.502172017676578 ], [ 13.300095773065056, 52.502170968741694 ], [ 13.300111402879351, 52.502170003783711 ], [ 13.300138675885162, 52.502170395654915 ], [ 13.300160150229493, 52.50216951466664 ], [ 13.30016377136913, 52.502176703918977 ], [ 13.300192900820663, 52.502179501525703 ], [ 13.300212289422429, 52.502182158272632 ], [ 13.300231677991961, 52.502184815915065 ], [ 13.30023375876446, 52.502187975651175 ], [ 13.300239781824484, 52.502184997982951 ], [ 13.300235808692392, 52.502158709940367 ], [ 13.300228708633119, 52.502155042015616 ], [ 13.300111058124687, 52.502153621339211 ], [ 13.300110644615701, 52.502161627465817 ], [ 13.300052203305556, 52.502160684333376 ], [ 13.300052972197223, 52.502156336442511 ], [ 13.299998273003011, 52.50215517012478 ], [ 13.299996629285197, 52.502180319199141 ], [ 13.300001501690302, 52.502180548357785 ], [ 13.299999479979927, 52.502247988455601 ], [ 13.300009025756772, 52.502247037686899 ], [ 13.30004413593011, 52.502246352657806 ], [ 13.300071133786691, 52.502253876914757 ], [ 13.300086442531823, 52.502261234106967 ], [ 13.300099758737005, 52.502269751305136 ], [ 13.300103242261933, 52.502280507193461 ], [ 13.300102691845732, 52.502294772831938 ], [ 13.300100147374906, 52.502310199355769 ], [ 13.30008025420638, 52.502320618451655 ], [ 13.300068473360856, 52.502322828248587 ], [ 13.300063423107947, 52.502329575515198 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 125, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297383481432673, 52.502147428579512 ], [ 13.297378143736704, 52.502134267745994 ], [ 13.297356623420709, 52.502136337105405 ], [ 13.29733542324743, 52.502130084304738 ], [ 13.297310054005161, 52.502130908723984 ], [ 13.297297556271626, 52.502135120161604 ], [ 13.297292247885519, 52.502136908539029 ], [ 13.297286354309653, 52.502138894392708 ], [ 13.297276017677797, 52.502154207814023 ], [ 13.297271615913159, 52.502167229375644 ], [ 13.297271018635486, 52.502182683869833 ], [ 13.297294257454693, 52.502186586048467 ], [ 13.297307710894422, 52.502191537825688 ], [ 13.297321254756232, 52.502194111827414 ], [ 13.297352332198168, 52.502196938170542 ], [ 13.297370048082296, 52.502192434984096 ], [ 13.297376443904822, 52.502178253478384 ], [ 13.297378851486792, 52.502166393650157 ], [ 13.297383481432673, 52.502147428579512 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 126, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299795577545204, 52.502210054615823 ], [ 13.299762966101754, 52.502196501933483 ], [ 13.299752812325506, 52.502207060944095 ], [ 13.299752261762649, 52.502221327478814 ], [ 13.299748891396629, 52.502258151998923 ], [ 13.299747606813272, 52.502291438782279 ], [ 13.299764817853315, 52.502300011993505 ], [ 13.299790509752382, 52.502290865822843 ], [ 13.299795231907485, 52.502269522914808 ], [ 13.299799724662659, 52.502254124396089 ], [ 13.29980055041535, 52.502232726390815 ], [ 13.299795577545204, 52.502210054615823 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 127, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299962670453304, 52.502255599258241 ], [ 13.29990985739871, 52.502254614652635 ], [ 13.299908847619845, 52.502272955699354 ], [ 13.299926151534569, 52.502273204360151 ], [ 13.299941919014064, 52.502268672790962 ], [ 13.299942331856231, 52.502257973787593 ], [ 13.299961905412927, 52.502255875983479 ], [ 13.299962670453304, 52.502255599258241 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 128, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.30035928141019, 52.502025821610133 ], [ 13.3003528183384, 52.501935597262815 ], [ 13.300333152602526, 52.50193531473203 ], [ 13.300329760740398, 52.501922181991617 ], [ 13.300347430355508, 52.501918867233847 ], [ 13.300351300378582, 52.501914411043188 ], [ 13.300349385745676, 52.501887695823669 ], [ 13.300346720440219, 52.501886741327802 ], [ 13.300319676894309, 52.501880406013115 ], [ 13.300310258597285, 52.501871943940735 ], [ 13.300306087080786, 52.501879021232178 ], [ 13.300284704611116, 52.50187752449164 ], [ 13.300274688596344, 52.501884517813068 ], [ 13.300289997283796, 52.501891874079952 ], [ 13.300260777755881, 52.501891454276262 ], [ 13.30024936393, 52.501884153964859 ], [ 13.300220005368727, 52.501887299866127 ], [ 13.300215881176046, 52.501893188297153 ], [ 13.300217185762557, 52.501909859663478 ], [ 13.300192044553944, 52.501904740289923 ], [ 13.300180723945084, 52.501895062236748 ], [ 13.300183222492386, 52.50188082458795 ], [ 13.300165965697593, 52.501873439417281 ], [ 13.300134979990105, 52.501868236955737 ], [ 13.300120792442199, 52.50188230665033 ], [ 13.30012214433477, 52.501897789160388 ], [ 13.300135185256917, 52.501913438724657 ], [ 13.300144603528683, 52.501921900810721 ], [ 13.300159913679398, 52.501929257115449 ], [ 13.300181708948406, 52.501920054873402 ], [ 13.300184825557508, 52.501940320886966 ], [ 13.30019211224012, 52.501953509592312 ], [ 13.300201715474644, 52.501957216182198 ], [ 13.300215672247225, 52.501949089949434 ], [ 13.300223281448226, 52.501953957425087 ], [ 13.300197589834895, 52.501963103692646 ], [ 13.300194816079488, 52.50198447461085 ], [ 13.300217825729995, 52.501994320607693 ], [ 13.300233181750293, 52.502000488923585 ], [ 13.300241477455238, 52.501987524102042 ], [ 13.300252845439804, 52.501996013292519 ], [ 13.300267603769877, 52.502017636101414 ], [ 13.300280874072286, 52.502027342157412 ], [ 13.300319834545235, 52.502027901902551 ], [ 13.30033931478216, 52.502028181770292 ], [ 13.300358886746581, 52.50202608387827 ], [ 13.30035928141019, 52.502025821610133 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 129, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299041393441252, 52.5021813723828 ], [ 13.29902603900139, 52.502175203934463 ], [ 13.299008275915037, 52.502180896226704 ], [ 13.298996312960593, 52.502187860549064 ], [ 13.298995853989181, 52.502199748424758 ], [ 13.298989642251232, 52.502209175411288 ], [ 13.298994936010695, 52.502223525074662 ], [ 13.299002222380349, 52.502236713850067 ], [ 13.299019618324502, 52.502240531679831 ], [ 13.299039098642314, 52.502240811762412 ], [ 13.29905514155851, 52.502229147945989 ], [ 13.299061307351812, 52.502220910732049 ], [ 13.29906385218001, 52.502205484233414 ], [ 13.299060552475849, 52.502189973705285 ], [ 13.299041393441252, 52.5021813723828 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 130, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299500647133863, 52.502232653598391 ], [ 13.29951305347943, 52.502225804392268 ], [ 13.299517241518817, 52.502147605981982 ], [ 13.299502299851609, 52.502138910704431 ], [ 13.299299627923359, 52.502134467972866 ], [ 13.299284591676994, 52.502142460795731 ], [ 13.299279664997172, 52.502219058036459 ], [ 13.299292348057694, 52.502228438372278 ], [ 13.299344764699706, 52.502229500274595 ], [ 13.299345955264425, 52.502223887092136 ], [ 13.29944747680339, 52.502226000985587 ], [ 13.299447818308462, 52.502231582243951 ], [ 13.299500647133863, 52.502232653598391 ] ], [ [ 13.299417037052713, 52.502170120026484 ], [ 13.299477884977874, 52.502159099293216 ], [ 13.299507060257133, 52.502160708190637 ], [ 13.299506739049654, 52.502169030334876 ], [ 13.29950224620168, 52.502184428841922 ], [ 13.29950119087056, 52.502211771232744 ], [ 13.299499150918185, 52.502214120985599 ], [ 13.29945517171627, 52.502192078058727 ], [ 13.299417037052713, 52.502170120026484 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 131, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.300102915226448, 52.50194152304632 ], [ 13.30009567300519, 52.501927144537774 ], [ 13.300074428098773, 52.501922082023235 ], [ 13.30006636171424, 52.501929102442169 ], [ 13.300046880051317, 52.501928822508695 ], [ 13.300025405822614, 52.501929703477423 ], [ 13.300003656355129, 52.501937717710902 ], [ 13.299989561962132, 52.501949409654017 ], [ 13.299992770231205, 52.501967297915634 ], [ 13.299996206396584, 52.501979241765547 ], [ 13.29998990466396, 52.50199104568572 ], [ 13.299989354216819, 52.502005311324083 ], [ 13.300022057336459, 52.502016486187635 ], [ 13.3000277642294, 52.502020136803658 ], [ 13.300049376117906, 52.502015689200526 ], [ 13.300065053187238, 52.50201353628988 ], [ 13.300071171452677, 52.502006486979511 ], [ 13.300077520509589, 52.501993494197905 ], [ 13.300077703993988, 52.501988738685348 ], [ 13.300092075038636, 52.501969914382585 ], [ 13.300084557591344, 52.501962669142316 ], [ 13.300084970394327, 52.501951970137618 ], [ 13.300102915226448, 52.50194152304632 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 132, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297917681350963, 52.502105046021221 ], [ 13.297918953488349, 52.502066892125569 ], [ 13.297829148794206, 52.502065609949398 ], [ 13.297829664323721, 52.502102986649923 ], [ 13.297917681350963, 52.502105046021221 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 133, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299798653379156, 52.501946666212262 ], [ 13.299787331374489, 52.501936988100958 ], [ 13.299786025477626, 52.501920316710184 ], [ 13.299757033864106, 52.501913952370955 ], [ 13.299733427951645, 52.501919560800218 ], [ 13.299737003156233, 52.501927938044439 ], [ 13.29975824799063, 52.50193300151787 ], [ 13.299759829154851, 52.501942539641121 ], [ 13.299732556295373, 52.501942147683785 ], [ 13.299712892579173, 52.501946623228179 ], [ 13.299710210352041, 52.501965616377205 ], [ 13.299723526395441, 52.501974133616748 ], [ 13.299734892803125, 52.501982622834959 ], [ 13.299744450124582, 52.501987518339632 ], [ 13.2997460312875, 52.501997056462955 ], [ 13.299726367547949, 52.502001532009345 ], [ 13.299727948707384, 52.502011070132866 ], [ 13.299763104539993, 52.502009197206313 ], [ 13.299782630642145, 52.502008288287705 ], [ 13.299792646762659, 52.502001295009002 ], [ 13.2997947784469, 52.501996567494849 ], [ 13.299802936643898, 52.501987169339024 ], [ 13.299814809168321, 52.501982581811838 ], [ 13.299824595853703, 52.5019815338188 ], [ 13.299821020662961, 52.501973155678449 ], [ 13.299809331654984, 52.501972987694408 ], [ 13.299795879466343, 52.501968036220902 ], [ 13.299804221171362, 52.501953882553003 ], [ 13.299798653379156, 52.501946666212262 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 134, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298298940510316, 52.502109105528746 ], [ 13.298300348894051, 52.50207233643934 ], [ 13.298212809537452, 52.50207118604235 ], [ 13.298210610441876, 52.502107521170025 ], [ 13.298298940510316, 52.502109105528746 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 135, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299422883831545, 52.501986465057527 ], [ 13.299446306265713, 52.501985612201935 ], [ 13.299457719982632, 52.501992912589756 ], [ 13.299490835902786, 52.501993388597732 ], [ 13.29951402890806, 52.501998480117656 ], [ 13.299531837707855, 52.501991599768957 ], [ 13.299563097229672, 52.501989669999013 ], [ 13.299558789434084, 52.50200031301889 ], [ 13.299576229712899, 52.502002942766239 ], [ 13.299595893467732, 52.501998467244952 ], [ 13.299603961412009, 52.501991446880083 ], [ 13.299588880755737, 52.501978146114858 ], [ 13.299569997015759, 52.501962411610592 ], [ 13.299533581262777, 52.501946425108152 ], [ 13.299516185422595, 52.501942606455749 ], [ 13.299492487726219, 52.5019505916935 ], [ 13.299449401565232, 52.501955920058698 ], [ 13.299424076868693, 52.501955556033536 ], [ 13.299418737110441, 52.501942395265566 ], [ 13.29943264960473, 52.501935458025258 ], [ 13.299417386936783, 52.50192691277082 ], [ 13.299402352248213, 52.501912423104585 ], [ 13.29937132075281, 52.501908408422715 ], [ 13.299347806577304, 52.501911639019191 ], [ 13.299347393595962, 52.501922338019902 ], [ 13.29934517155875, 52.501929443302252 ], [ 13.299331121414088, 52.501939946266617 ], [ 13.299319571564794, 52.501936211622386 ], [ 13.29929048822639, 52.50193222492468 ], [ 13.299272725271978, 52.501937917257983 ], [ 13.299278156771148, 52.501948700276884 ], [ 13.299291426797938, 52.501958407340609 ], [ 13.299318378455185, 52.501967120644792 ], [ 13.299343519593521, 52.501972240203379 ], [ 13.299354931817245, 52.501979540579839 ], [ 13.299366161956128, 52.501991596487123 ], [ 13.29939302036232, 52.502002688407444 ], [ 13.299410645660597, 52.502000562689418 ], [ 13.299422883831545, 52.501986465057527 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 136, "id": 1, "name": "impervious" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.3003528183384, 52.501935597262815 ], [ 13.300351300378582, 52.501914411043188 ], [ 13.300347430355508, 52.501918867233847 ], [ 13.300329760740398, 52.501922181991617 ], [ 13.300333152602526, 52.50193531473203 ], [ 13.3003528183384, 52.501935597262815 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 137, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298787194488989, 52.501897631162848 ], [ 13.298762007553945, 52.501893700364221 ], [ 13.298742114229691, 52.501904120133304 ], [ 13.298720502304306, 52.501908566598431 ], [ 13.298702785195868, 52.501913069968325 ], [ 13.298700287641337, 52.501927307602891 ], [ 13.298699644961639, 52.501943950986444 ], [ 13.298697100023245, 52.501959377476979 ], [ 13.29871621298601, 52.501969167731424 ], [ 13.298721736132686, 52.501977573918673 ], [ 13.2987352800153, 52.501980147758786 ], [ 13.298764271553559, 52.501986511448827 ], [ 13.298779855421378, 52.501986735543788 ], [ 13.298793676217249, 52.501982176134334 ], [ 13.298793859805407, 52.501977421523556 ], [ 13.298788428421895, 52.501966638483438 ], [ 13.298802293643483, 52.501960890174381 ], [ 13.298814349848483, 52.501951548137541 ], [ 13.29881109615444, 52.501934848725327 ], [ 13.298801952063675, 52.501919254148547 ], [ 13.298780936879472, 52.501908246117601 ], [ 13.298787194488989, 52.501897631162848 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 138, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298380807792133, 52.501922712876116 ], [ 13.298381404675032, 52.501907258372349 ], [ 13.298360114099571, 52.501903383530177 ], [ 13.298344528792104, 52.501903159359003 ], [ 13.298348103695673, 52.501911537541623 ], [ 13.29832472720287, 52.501911201302867 ], [ 13.298307332957902, 52.501907382494878 ], [ 13.298280106060181, 52.501905801325961 ], [ 13.298264981388835, 52.501893689293645 ], [ 13.298254965088338, 52.501900681542686 ], [ 13.29823915165246, 52.501906401763939 ], [ 13.298217126386856, 52.501921548029785 ], [ 13.298220470224219, 52.501935869681255 ], [ 13.298219781441967, 52.501953701937673 ], [ 13.298242744705865, 52.501964737191827 ], [ 13.298265891626414, 52.501971017832119 ], [ 13.298295112649365, 52.501971438150015 ], [ 13.298314501003611, 52.501974096104995 ], [ 13.298324241836303, 52.501974236214821 ], [ 13.298345670195955, 52.501974544432429 ], [ 13.298362972670796, 52.501980740090538 ], [ 13.298380643923362, 52.501977425649159 ], [ 13.298384677972352, 52.501973915958381 ], [ 13.298412270771824, 52.501965986065528 ], [ 13.298416442564875, 52.5019589097422 ], [ 13.298407162334, 52.501946881788641 ], [ 13.298401639251397, 52.501938475587181 ], [ 13.298389950261225, 52.501938307463021 ], [ 13.298370516000753, 52.501936838397576 ], [ 13.298380807792133, 52.501922712876116 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 139, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299133751202419, 52.501902614076691 ], [ 13.299114546380034, 52.501895201644189 ], [ 13.299104761136181, 52.50189625049827 ], [ 13.299091169886598, 52.501894865556906 ], [ 13.299089772459135, 52.501880571915663 ], [ 13.299105679044576, 52.501872473845701 ], [ 13.29911379146702, 52.501864264615833 ], [ 13.299100293494021, 52.5018605019416 ], [ 13.299077008814946, 52.501857788096565 ], [ 13.299053402849294, 52.501863396389723 ], [ 13.299048818079529, 52.501881172634292 ], [ 13.299028649501684, 52.501898723920071 ], [ 13.299023927019746, 52.501920066795975 ], [ 13.299011733168213, 52.501932975487122 ], [ 13.299012855173162, 52.501954402393906 ], [ 13.29902199934593, 52.501969996954998 ], [ 13.29905080732545, 52.501981116085126 ], [ 13.299075856677808, 52.501988612556801 ], [ 13.299113236066315, 52.501979634567732 ], [ 13.299133221097142, 52.501966837879394 ], [ 13.299149448944117, 52.501950418559474 ], [ 13.299140304729887, 52.501934824007847 ], [ 13.299126852629726, 52.501929872458916 ], [ 13.299135010955514, 52.5019204743502 ], [ 13.299133751202419, 52.501902614076691 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 140, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298063846666837, 52.501953837849442 ], [ 13.298066254050438, 52.501941978005142 ], [ 13.298080394905794, 52.501929096519916 ], [ 13.298090549014018, 52.501918537655214 ], [ 13.298081221483587, 52.501907698532577 ], [ 13.298081772546443, 52.501893432908076 ], [ 13.298031169187839, 52.501891515390739 ], [ 13.29799239259048, 52.501886199385623 ], [ 13.297985905155251, 52.501902758681055 ], [ 13.297972545018277, 52.501895430146909 ], [ 13.297931681003549, 52.501893652703657 ], [ 13.297931267678686, 52.501904351696545 ], [ 13.297920039429773, 52.501892295677514 ], [ 13.297912660143782, 52.501881483671838 ], [ 13.29792263056447, 52.501875680329022 ], [ 13.297929026161501, 52.501861498790824 ], [ 13.297923732791116, 52.501847149082209 ], [ 13.297898913368373, 52.501833707984602 ], [ 13.297879616962559, 52.501828672208624 ], [ 13.297868249482956, 52.501820182794063 ], [ 13.297827341083977, 52.501819594213352 ], [ 13.297805820975979, 52.50182166275745 ], [ 13.297807126120954, 52.501838334163949 ], [ 13.297796880099497, 52.501851270757584 ], [ 13.297802127496622, 52.5018668093483 ], [ 13.297782188028448, 52.501878417830959 ], [ 13.297789657709242, 52.501886851171214 ], [ 13.297796991068026, 52.501898851162487 ], [ 13.297794583614685, 52.501910711000768 ], [ 13.297781131641836, 52.501905760198788 ], [ 13.297761925612873, 52.50189834662882 ], [ 13.297753261730232, 52.501920822284369 ], [ 13.297762358042245, 52.501937605794602 ], [ 13.297785458979865, 52.501945074506345 ], [ 13.297799463557993, 52.501935760605427 ], [ 13.297806979183864, 52.501943005067893 ], [ 13.297796688677302, 52.501957130559028 ], [ 13.297813991031642, 52.501963327195341 ], [ 13.297823869654941, 52.501959900716081 ], [ 13.297849378057574, 52.501955509576611 ], [ 13.297862967810984, 52.501956894639278 ], [ 13.297880178314081, 52.501965469019538 ], [ 13.297911393422755, 52.501964728584547 ], [ 13.297934769938937, 52.501965064905178 ], [ 13.297941073688365, 52.501953261120242 ], [ 13.297954893085709, 52.501948701788251 ], [ 13.297974191025183, 52.501953737573558 ], [ 13.297983700776189, 52.501959821175866 ], [ 13.297995068278993, 52.501968311478201 ], [ 13.298012508481307, 52.501970940556546 ], [ 13.298031896817264, 52.501973598557676 ], [ 13.298047480674594, 52.501973822749036 ], [ 13.298049796212265, 52.501964340658944 ], [ 13.298063846666837, 52.501953837849442 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 141, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297631910908644, 52.501903613068336 ], [ 13.297618550865932, 52.501896283596118 ], [ 13.297596477984326, 52.501912618600073 ], [ 13.297595834888115, 52.501929261974183 ], [ 13.297585406502828, 52.501946954076246 ], [ 13.297602662919704, 52.501954338721205 ], [ 13.297615932574718, 52.501964045969714 ], [ 13.297635412756124, 52.501964326282348 ], [ 13.297655214492652, 52.50195628445519 ], [ 13.297655857567063, 52.501939641080675 ], [ 13.297654276859074, 52.501930102933159 ], [ 13.297670457830343, 52.50191487267606 ], [ 13.297639427973909, 52.501910857562379 ], [ 13.297631910908644, 52.501903613068336 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 142, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.297094452627164, 52.501941077780998 ], [ 13.297102429087634, 52.501936434443998 ], [ 13.29711996108797, 52.501936686799361 ], [ 13.297123995267372, 52.501933176253679 ], [ 13.297124592591222, 52.50191772176003 ], [ 13.297130942500116, 52.501904729143646 ], [ 13.297141095355704, 52.501894170341082 ], [ 13.297117810788356, 52.501891456111316 ], [ 13.29710050856225, 52.501885259374163 ], [ 13.297069339453236, 52.501884810719304 ], [ 13.297057514100711, 52.501888209113766 ], [ 13.297024580642043, 52.501882976899218 ], [ 13.297014335819483, 52.501895914342747 ], [ 13.297025288134405, 52.501915102804624 ], [ 13.297032529382161, 52.501929480594008 ], [ 13.297033926142847, 52.501943774253753 ], [ 13.297053406309372, 52.501944054662097 ], [ 13.297070662599449, 52.501951440282525 ], [ 13.297086246441166, 52.501951664600405 ], [ 13.297094452627164, 52.501941077780998 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 143, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.2968449020247, 52.501892285716117 ], [ 13.296837660844275, 52.501877907915741 ], [ 13.296814146545566, 52.501881137108562 ], [ 13.296806261983646, 52.501883402673748 ], [ 13.296788775967006, 52.501881961395966 ], [ 13.296787241408481, 52.501871234362277 ], [ 13.296755612743976, 52.501882673487458 ], [ 13.296747362000032, 52.501894449180149 ], [ 13.296735443226074, 52.501900225273154 ], [ 13.296729185124304, 52.501910840115194 ], [ 13.296728541700608, 52.501927483481751 ], [ 13.296737776977642, 52.501940700460402 ], [ 13.296760969624875, 52.501945792517169 ], [ 13.296807722609651, 52.501946465608164 ], [ 13.296842834044, 52.501945781548741 ], [ 13.296845240282687, 52.501933921710844 ], [ 13.296839948730794, 52.501919571978021 ], [ 13.296836374114447, 52.501911193752292 ], [ 13.296838827783635, 52.501898145059528 ], [ 13.2968449020247, 52.501892285716117 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 144, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.2973902343267, 52.501953660794996 ], [ 13.297411754551828, 52.501951591428686 ], [ 13.297431234725661, 52.501951871774878 ], [ 13.297441343051405, 52.501942501843779 ], [ 13.297459104765515, 52.501936809766306 ], [ 13.297476682710419, 52.501935873192295 ], [ 13.297483308129738, 52.501915748195472 ], [ 13.297470223721561, 52.501901286345998 ], [ 13.297464838648498, 52.50188931347266 ], [ 13.297465298027753, 52.501877425605784 ], [ 13.297452671546331, 52.501851075866341 ], [ 13.297441627210452, 52.501834264296839 ], [ 13.297438465929279, 52.501815187098003 ], [ 13.29743704397443, 52.501814276504007 ], [ 13.297369737639228, 52.50181360996762 ], [ 13.297366342536304, 52.501815338669033 ], [ 13.297357998606712, 52.501829492137723 ], [ 13.297346494867586, 52.501824569324597 ], [ 13.297325020646893, 52.501825449803107 ], [ 13.297297472254149, 52.501832189645391 ], [ 13.297296874992764, 52.501847644140661 ], [ 13.297306018533847, 52.501863238829309 ], [ 13.297315344390455, 52.50187407799028 ], [ 13.297330928206764, 52.501874302275731 ], [ 13.297340806811899, 52.501870876735502 ], [ 13.297358292844068, 52.501872317931841 ], [ 13.297371424623281, 52.501885590938095 ], [ 13.297334456969747, 52.501883869363787 ], [ 13.297316189931204, 52.501902637266433 ], [ 13.297310023824863, 52.501910875286072 ], [ 13.29730761623993, 52.501922735113325 ], [ 13.297315179165347, 52.501928791649689 ], [ 13.297328768892342, 52.501930176773548 ], [ 13.297344032622705, 52.50193872231516 ], [ 13.29735131992318, 52.501951911209474 ], [ 13.2973902343267, 52.501953660794996 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 145, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299261200523388, 52.501883036457116 ], [ 13.299271492017967, 52.501868910855514 ], [ 13.299266152315395, 52.501855750080949 ], [ 13.299248849793878, 52.501849553655511 ], [ 13.299225060312919, 52.501859916595087 ], [ 13.299218848673345, 52.501869343595004 ], [ 13.299195885244476, 52.501858307629853 ], [ 13.299186511557664, 52.501848657469687 ], [ 13.299162997401433, 52.501851888029606 ], [ 13.299141293750282, 52.501858712327419 ], [ 13.299134623164338, 52.501880027199981 ], [ 13.299143905054001, 52.501892055119306 ], [ 13.29915912176042, 52.501901789286116 ], [ 13.299176379866642, 52.501909174623442 ], [ 13.299183987506643, 52.501914041245158 ], [ 13.299183436765469, 52.501928307776382 ], [ 13.299200555764267, 52.501939258823285 ], [ 13.299225972236448, 52.501937245140923 ], [ 13.299238028345972, 52.501927903060071 ], [ 13.29924414820311, 52.501920853814568 ], [ 13.299252260609082, 52.501912644575164 ], [ 13.299246647054282, 52.501906616188208 ], [ 13.299237135700027, 52.501900531766239 ], [ 13.299258977036578, 52.501890140817984 ], [ 13.299261200523388, 52.501883036457116 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 146, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298974461361928, 52.501843229676467 ], [ 13.298962911568863, 52.501839494997832 ], [ 13.298945333689025, 52.501840431793298 ], [ 13.29893132934612, 52.501849746730947 ], [ 13.298909212574186, 52.501867269984295 ], [ 13.298912603973422, 52.50188040366055 ], [ 13.298900547808843, 52.501889745707842 ], [ 13.298927591148999, 52.501896081345272 ], [ 13.298940815180478, 52.501906977325035 ], [ 13.298938362132311, 52.501920026066685 ], [ 13.298953624703586, 52.501928571382102 ], [ 13.298967765262814, 52.50191569068717 ], [ 13.298981861385734, 52.501903998889105 ], [ 13.298986124882433, 52.501894544789835 ], [ 13.298996185529157, 52.501886362680622 ], [ 13.298994742232596, 52.501873257916017 ], [ 13.298987410022535, 52.501861258017406 ], [ 13.29897423185882, 52.501849174063871 ], [ 13.298974461361928, 52.501843229676467 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 147, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.299897294413253, 52.501915968057837 ], [ 13.299909165436194, 52.501911380499699 ], [ 13.299924337883267, 52.501922304367376 ], [ 13.299941915778302, 52.501921367426519 ], [ 13.299942145151483, 52.501915423036209 ], [ 13.299954383129339, 52.501901325347475 ], [ 13.299948907067675, 52.501891731257473 ], [ 13.29994950340002, 52.501876276741022 ], [ 13.299936233250882, 52.50186656975 ], [ 13.299905293452241, 52.501860178350476 ], [ 13.299881595869262, 52.501868163667226 ], [ 13.299861932200798, 52.501872639236822 ], [ 13.299857531263008, 52.50188566000336 ], [ 13.299853223549016, 52.501896303034798 ], [ 13.299854527998271, 52.50191297440395 ], [ 13.299857782087045, 52.501929673790826 ], [ 13.299861311439292, 52.501939239909518 ], [ 13.299876941141015, 52.501938275880491 ], [ 13.299892847605252, 52.50193017770367 ], [ 13.299897294413253, 52.501915968057837 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 148, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.29956800662638, 52.501862467259564 ], [ 13.299552698071713, 52.501855110900372 ], [ 13.299523385335178, 52.501857068649272 ], [ 13.299501865246764, 52.501859138401059 ], [ 13.29948048281736, 52.501857641515471 ], [ 13.299449405465026, 52.501854815732074 ], [ 13.29944138488244, 52.501860647229933 ], [ 13.299431322850999, 52.501868829356653 ], [ 13.299423346679529, 52.501873471954312 ], [ 13.299407349845625, 52.501883947844156 ], [ 13.299408976821262, 52.501892297093761 ], [ 13.299430405153098, 52.501892605114634 ], [ 13.299429946303666, 52.501904492993603 ], [ 13.299423872366971, 52.501910353371258 ], [ 13.299437325967924, 52.501915304907818 ], [ 13.29944901348583, 52.501915472907321 ], [ 13.299474338159628, 52.501915836926955 ], [ 13.29951320825251, 52.501918773814943 ], [ 13.299530648496251, 52.501921403568701 ], [ 13.29954433006716, 52.501920410682963 ], [ 13.299554896756224, 52.501899152687734 ], [ 13.299563377651662, 52.501881432424859 ], [ 13.29956800662638, 52.501862467259564 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 149, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.296865421073189, 52.50181526565067 ], [ 13.296853966483106, 52.501808501196777 ], [ 13.29681366959689, 52.501808101792115 ], [ 13.296788500233259, 52.501784399141805 ], [ 13.296765409564136, 52.501780520573874 ], [ 13.296735821003768, 52.501789610888267 ], [ 13.296723719919243, 52.501800141607475 ], [ 13.296707537194175, 52.501815371711743 ], [ 13.296705129453141, 52.501827231525581 ], [ 13.296720393025458, 52.501835777146511 ], [ 13.296729534886559, 52.501851371856795 ], [ 13.29674688447929, 52.501856379792791 ], [ 13.296754170114626, 52.501869568701196 ], [ 13.296785660890892, 52.501861696204664 ], [ 13.296805232936981, 52.501859598901277 ], [ 13.296828701301019, 52.50185755768765 ], [ 13.296848595014461, 52.501847139142519 ], [ 13.296866450151898, 52.501839069423049 ], [ 13.296867001606602, 52.501824803807402 ], [ 13.296865421073189, 52.50181526565067 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 150, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298185616236259, 52.501879463645899 ], [ 13.298186259108586, 52.501862820266574 ], [ 13.298188758297401, 52.501848582665261 ], [ 13.298167835112215, 52.501835197671511 ], [ 13.298168248378614, 52.501824498677166 ], [ 13.298138797874058, 52.50183002181506 ], [ 13.298109393251526, 52.501834356967215 ], [ 13.298101187328278, 52.50184494385941 ], [ 13.298090988787996, 52.501856691623424 ], [ 13.298106388908243, 52.501861671314465 ], [ 13.298115578663928, 52.501876077066051 ], [ 13.298129122478487, 52.501878650974966 ], [ 13.298144937390786, 52.501872930789617 ], [ 13.298145212894259, 52.501865798426394 ], [ 13.298158710784818, 52.501869561208892 ], [ 13.298165906509112, 52.501885126910445 ], [ 13.298177502166938, 52.501887672789415 ], [ 13.298185616236259, 52.501879463645899 ] ] ] } }, +{ "type": "Feature", "properties": { "fid": 151, "id": 2, "name": "vegetation" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.298681841715108, 52.501849726897255 ], [ 13.298658648852939, 52.501844636114157 ], [ 13.298642927342366, 52.501847977736901 ], [ 13.298642192823619, 52.501866998874753 ], [ 13.298653422737935, 52.501879054848068 ], [ 13.298676294256449, 52.501892467773374 ], [ 13.29868992992699, 52.501892663863828 ], [ 13.298709869131303, 52.501881056124844 ], [ 13.298713994960716, 52.501875168667802 ], [ 13.298714316314486, 52.501866846526561 ], [ 13.298706844986157, 52.501858413221996 ], [ 13.298705126363856, 52.501852440819825 ], [ 13.298681841715108, 52.501849726897255 ] ] ] } } +] +} diff --git a/eotimeseriesviewer/externals/qps/ui/subdatasetselectiondialog.ui b/eotimeseriesviewer/externals/qps/ui/subdatasetselectiondialog.ui new file mode 100644 index 0000000000000000000000000000000000000000..a2b31edba90b264938cbca6aef318e8030158cc6 --- /dev/null +++ b/eotimeseriesviewer/externals/qps/ui/subdatasetselectiondialog.ui @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Dialog</class> + <widget class="QDialog" name="Dialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>504</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QTableView" name="tableView"/> + <widget class="QTableView" name="tableView_2"/> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>Dialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>Dialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/eotimeseriesviewer/externals/qps/utils.py b/eotimeseriesviewer/externals/qps/utils.py index 4aa5f198f17eabd09611e456b4e8b31f9a640581..aa0b24c0d76219e72c4fd55e8de2cf1a29f97c4c 100644 --- a/eotimeseriesviewer/externals/qps/utils.py +++ b/eotimeseriesviewer/externals/qps/utils.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -import os, sys, importlib, re, fnmatch, io, zipfile, pathlib, warnings, collections, copy, shutil +import os, sys, importlib, re, fnmatch, io, zipfile, pathlib, warnings, collections, copy, shutil, typing from qgis.core import * from qgis.core import QgsFeature, QgsPointXY, QgsRectangle @@ -17,12 +17,17 @@ from qgis.PyQt import uic from osgeo import gdal, ogr import numpy as np +from qgis.PyQt.QtWidgets import QAction + +REMOVE_setShortcutVisibleInContextMenu = hasattr(QAction, 'setShortcutVisibleInContextMenu') + from . import resourcemockup try: - import qps -except: + from .. import qps +except: + import qps jp = os.path.join dn = os.path.dirname @@ -63,7 +68,12 @@ def mkDir(d, delete=False): # for python development only. try to find a qgisresources directory DIR_QGISRESOURCES = None -MAP_LAYER_STORES = [QgsProject.instance()] + +# a QPS internal map layer store +QPS_MAPLAYER_STORE = QgsMapLayerStore() + +# a list of all known maplayer stores. +MAP_LAYER_STORES = [QPS_MAPLAYER_STORE, QgsProject.instance()] def findUpwardPath(basepath, name, isDirectory=True): @@ -281,6 +291,29 @@ def findMapLayer(layer)->QgsMapLayer: return None +def gdalFileSize(path) -> int: + """ + Returns the size of a local gdal readible file (including metadata files etc.) + :param path: str + :return: int + """ + ds = gdal.Open(path) + if not isinstance(ds, gdal.Dataset): + return 0 + else: + size = 0 + for file in ds.GetFileList(): + size += os.stat(file).st_size + + # recursively inspect VRT sources + if file.endswith('.vrt') and file != path: + size += gdalFileSize(file) + + return size + + + s = "" + def qgisLayerTreeLayers() -> list: """ @@ -316,6 +349,8 @@ def createQgsField(name : str, exampleValue, comment:str=None): return QgsField(name, QVariant.Double, 'double', comment=comment) elif isinstance(exampleValue, np.ndarray): return QgsField(name, QVariant.String, 'varchar', comment=comment) + elif isinstance(exampleValue, np.datetime64): + return QgsField(name, QVariant.String, 'varchar', comment=comment) elif isinstance(exampleValue, list): assert len(exampleValue) > 0, 'need at least one value in provided list' v = exampleValue[0] @@ -419,15 +454,16 @@ def showMessage(message:str, title:str, level): v.showMessage(True) -def gdalDataset(pathOrDataset, eAccess=gdal.GA_ReadOnly)->gdal.Dataset: +def gdalDataset(pathOrDataset:typing.Union[str, QgsRasterLayer, QgsRasterDataProvider, gdal.Dataset], eAccess=gdal.GA_ReadOnly)->gdal.Dataset: """ Returns a gdal.Dataset object instance - :param pathOrDataset: path | gdal.Dataset | QgsRasterLayer + :param pathOrDataset: path | gdal.Dataset | QgsRasterLayer | QgsRasterDataProvider :return: gdal.Dataset """ - if isinstance(pathOrDataset, QgsRasterLayer): return gdalDataset(pathOrDataset.source()) + elif isinstance(pathOrDataset, QgsRasterDataProvider): + return gdalDataset(pathOrDataset.dataSourceUri()) if not isinstance(pathOrDataset, gdal.Dataset): pathOrDataset = gdal.Open(pathOrDataset, eAccess) @@ -453,6 +489,37 @@ def ogrDataSource(pathOrDataSource)->ogr.DataSource: return pathOrDataSource +def qgsVectorLayer(source)->QgsVectorLayer: + """ + Returns a QgsVectorLayer from different source types + :param source: QgsVectorLayer | ogr.DataSource | file path + :return: QgsVectorLayer + :rtype: QgsVectorLayer + """ + if isinstance(source, QgsVectorLayer): + return source + if isinstance(source, str): + return QgsVectorLayer(source) + if isinstance(source, ogr.DataSource): + return QgsVectorLayer(source.GetDescription()) + + raise Exception('Unable to transform {} into QgsVectorLayer'.format(source)) + +def qgsRasterLayer(source)->QgsRasterLayer: + """ + Returns a QgsVectorLayer from different source types + :param source: QgsVectorLayer | ogr.DataSource | file path + :return: QgsVectorLayer + :rtype: QgsVectorLayer + """ + if isinstance(source, QgsRasterLayer): + return source + if isinstance(source, str): + return QgsRasterLayer(source) + if isinstance(source, gdal.Dataset): + return QgsRasterLayer(source.GetDescription()) + + raise Exception('Unable to transform {} into QgsRasterLayer'.format(source)) def loadUI(basename: str): @@ -548,6 +615,20 @@ def loadUIFormClass(pathUi:str, from_imports=False, resourceSuffix:str='', fixQG doc = QDomDocument() doc.setContent(txt) + if REMOVE_setShortcutVisibleInContextMenu and 'shortcutVisibleInContextMenu' in txt: + toRemove = [] + actions = doc.elementsByTagName('action') + for iAction in range(actions.count()): + properties = actions.item(iAction).toElement().elementsByTagName('property') + for iProperty in range(properties.count()): + prop = properties.item(iProperty).toElement() + if prop.attribute('name') == 'shortcutVisibleInContextMenu': + toRemove.append(prop) + for prop in toRemove: + prop.parentNode().removeChild(prop) + del toRemove + + elem = doc.elementsByTagName('customwidget') for child in [elem.item(i) for i in range(elem.count())]: child = child.toElement() @@ -855,7 +936,7 @@ def displayBandNames(rasterSource, bands=None, leadingBandNumber=True): return None -def defaultBands(dataset): +def defaultBands(dataset)->list: """ Returns a list of 3 default bands :param dataset: @@ -902,13 +983,13 @@ def defaultBands(dataset): raise Exception() -def bandClosestToWavelength(dataset, wl, wl_unit='nm'): +def bandClosestToWavelength(dataset, wl, wl_unit='nm')->int: """ Returns the band index of an image dataset closest to wavelength `wl`. :param dataset: str | gdal.Dataset :param wl: wavelength to search the closed band for :param wl_unit: unit of wavelength. Default = nm - :return: band index | 0 of wavelength information is not provided + :return: band index | 0 if wavelength information is not provided """ if isinstance(wl, str): assert wl.upper() in LUT_WAVELENGTH.keys(), wl @@ -929,6 +1010,38 @@ def bandClosestToWavelength(dataset, wl, wl_unit='nm'): pass return 0 +def parseBadBandList(dataset)->typing.List[int]: + """ + Returns the bad-band-list if it is specified explicitly + :param dataset: + :type dataset: + :return: list of booleans. True = valid band, False = excluded / bad band + :rtype: + """ + bbl = None + + try: + dataset = gdalDataset(dataset) + except: + pass + + if not isinstance(dataset, gdal.Dataset): + return None + + + + # 1. search for ENVI style definition of band band list + bblStr1 = dataset.GetMetadataItem('bbl') + bblStr2 = dataset.GetMetadataItem('bbl', 'ENVI') + + for bblStr in [bblStr1, bblStr2]: + if isinstance(bblStr, str) and len(bblStr) > 0: + parts = bblStr.split(',') + if len(parts) == dataset.RasterCount: + bbl = [int(p) for p in parts] + + return bbl + def parseWavelength(dataset): """ @@ -939,18 +1052,12 @@ def parseWavelength(dataset): wl = None wlu = None + try: + dataset = gdalDataset(dataset) + except: + pass - if isinstance(dataset, str): - return parseWavelength(gdal.Open(dataset)) - elif isinstance(dataset, QgsRasterDataProvider): - return parseWavelength(dataset.dataSourceUri()) - elif isinstance(dataset, QgsRasterLayer): - if dataset.dataProvider().name() == 'gdal': - return parseWavelength(gdal.Open(dataset.source())) - else: - return None, None - elif isinstance(dataset, gdal.Dataset): - + if isinstance(dataset, gdal.Dataset): for domain in dataset.GetMetadataDomainList(): # see http://www.harrisgeospatial.com/docs/ENVIHeaderFiles.html for supported wavelength units @@ -969,7 +1076,7 @@ def parseWavelength(dataset): .format(key, len(tmp), dataset.RasterCount), file=sys.stderr) if re.search(r'wavelength.units?', key): - if re.search(r'(Micrometers?|um)', values, re.I): + if re.search(r'(Micrometers?|um|μm)', values, re.I): wlu = 'um' # fix with python 3 UTF elif re.search(r'(Nanometers?|nm)', values, re.I): wlu = 'nm' @@ -1022,7 +1129,7 @@ def qgisAppQgisInterface()->QgisInterface: return None -def getDOMAttributes(elem): +def getDOMAttributes(elem)->dict: assert isinstance(elem, QDomElement) values = dict() attributes = elem.attributes() @@ -1032,7 +1139,7 @@ def getDOMAttributes(elem): return values -def fileSizeString(num, suffix='B', div=1000): +def fileSizeString(num, suffix='B', div=1000)->str: """ Returns a human-readable file size string. thanks to Fred Cirera @@ -1049,7 +1156,7 @@ def fileSizeString(num, suffix='B', div=1000): return "{:.1f} {}{}".format(num, unit, suffix) -def geo2pxF(geo, gt): +def geo2pxF(geo, gt)->QPointF: """ Returns the pixel position related to a Geo-Coordinate in floating point precision. :param geo: Geo-Coordinate as QgsPoint @@ -1062,7 +1169,7 @@ def geo2pxF(geo, gt): py = (geo.y() - gt[3]) / gt[5] # y pixel return QPointF(px, py) -def geo2px(geo, gt): +def geo2px(geo, gt)->QPoint: """ Returns the pixel position related to a Geo-Coordinate as integer number. Floating-point coordinate are casted to integer coordinate, e.g. the pixel coordinate (0.815, 23.42) is returned as (0,23) @@ -1115,7 +1222,7 @@ def check_vsimem()->bool: return False return result -def layerGeoTransform(rasterLayer:QgsRasterLayer)->tuple: +def layerGeoTransform(rasterLayer:QgsRasterLayer)->typing.Tuple[float, float, float, float, float, float]: """ Returns the geo-transform vector from a QgsRasterLayer. See https://www.gdal.org/gdal_datamodel.html @@ -1131,7 +1238,7 @@ def layerGeoTransform(rasterLayer:QgsRasterLayer)->tuple: 0, -1 * rasterLayer.rasterUnitsPerPixelY()) return gt -def px2geo(px, gt, pxCenter=True): +def px2geo(px:QPoint, gt, pxCenter=True)->QgsPointXY: """ Converts a pixel coordinate into a geo-coordinate :param px: QPoint() with pixel coordinates @@ -1652,3 +1759,9 @@ class SelectMapLayersDialog(QgsDialog): +class QgsTaskMock(QgsTask): + """ + A mocked QgsTask + """ + def __init__(self): + super(QgsTaskMock, self).__init__() \ No newline at end of file diff --git a/eotimeseriesviewer/main.py b/eotimeseriesviewer/main.py index 9dcde82d130cb6591348af70d933dd5526683026..5d3609ca74edfda8ee45b90f0fd3a10111159c99 100644 --- a/eotimeseriesviewer/main.py +++ b/eotimeseriesviewer/main.py @@ -44,8 +44,11 @@ from qgis.gui import * import qgis.utils from eotimeseriesviewer.utils import * from eotimeseriesviewer.timeseries import * +from eotimeseriesviewer.mapcanvas import MapCanvas from eotimeseriesviewer.profilevisualization import SpectralTemporalVisualization +from eotimeseriesviewer.temporalprofiles import TemporalProfileLayer from eotimeseriesviewer.mapvisualization import MapView, MapWidget +import eotimeseriesviewer.settings as eotsvSettings from eotimeseriesviewer import SpectralProfile, SpectralLibrary, SpectralLibraryPanel from eotimeseriesviewer.externals.qps.maptools import MapTools, CursorLocationMapTool, QgsMapToolSelect, QgsMapToolSelectionHandler from eotimeseriesviewer.externals.qps.cursorlocationvalue import CursorLocationInfoModel, CursorLocationInfoDock @@ -339,9 +342,7 @@ class TimeSeriesViewer(QgisInterface, QObject): QgisInterface.__init__(self) QApplication.processEvents() - self.mMapLayerStore = QgsMapLayerStore() - import eotimeseriesviewer.utils - eotimeseriesviewer.utils.MAP_LAYER_STORES.insert(0, self.mapLayerStore()) + TimeSeriesViewer._instance = self self.ui = TimeSeriesViewerUI() @@ -349,6 +350,12 @@ class TimeSeriesViewer(QgisInterface, QObject): mvd = self.ui.dockMapViews dts = self.ui.dockTimeSeries mw = self.ui.mMapWidget + + self.mMapLayerStore = self.mapWidget().mMapLayerStore + import eotimeseriesviewer.utils + eotimeseriesviewer.utils.MAP_LAYER_STORES.insert(0, self.mapLayerStore()) + + from eotimeseriesviewer.timeseries import TimeSeriesDock from eotimeseriesviewer.mapvisualization import MapViewDock, MapWidget assert isinstance(mvd, MapViewDock) @@ -359,6 +366,8 @@ class TimeSeriesViewer(QgisInterface, QObject): TimeSeriesViewer._instance = None self.ui.sigAboutToBeClosed.connect(onClosed) + QgsApplication.instance().messageLog().messageReceived.connect(self.logMessage) + # Save reference to the QGIS interface import qgis.utils @@ -369,6 +378,8 @@ class TimeSeriesViewer(QgisInterface, QObject): self.mTimeSeries.setDateTimePrecision(DateTimePrecision.Day) self.mSpatialMapExtentInitialized = False self.mTimeSeries.sigTimeSeriesDatesAdded.connect(self.onTimeSeriesChanged) + self.mTimeSeries.sigTimeSeriesDatesRemoved.connect(self.onTimeSeriesChanged) + self.mTimeSeries.sigSensorAdded.connect(self.onSensorAdded) dts.setTimeSeries(self.mTimeSeries) self.ui.dockSensors.setTimeSeries(self.mTimeSeries) @@ -378,9 +389,8 @@ class TimeSeriesViewer(QgisInterface, QObject): self.spectralTemporalVis = SpectralTemporalVisualization(self.mTimeSeries, self.ui.dockProfiles) - self.spectralTemporalVis.pixelLoader.sigLoadingFinished.connect( - lambda dt: self.ui.dockSystemInfo.addTimeDelta('Pixel Profile', dt)) assert isinstance(self, TimeSeriesViewer) + self.spectralTemporalVis.sigMoveToDate.connect(self.setCurrentDate) mw.sigSpatialExtentChanged.connect(self.timeSeries().setCurrentSpatialExtent) mw.sigVisibleDatesChanged.connect(self.timeSeries().setVisibleDates) @@ -547,7 +557,7 @@ class TimeSeriesViewer(QgisInterface, QObject): w.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)) w.setMinimumSize(0, 0) - def sensors(self)->list: + def sensors(self)->typing.List[SensorInstrument]: """ Returns the list of Sensors :return: [list-of-Sensors] @@ -654,7 +664,7 @@ class TimeSeriesViewer(QgisInterface, QObject): mapView.addLayer(self.spectralTemporalVis.temporalProfileLayer()) mapView.addLayer(self.spectralLibrary()) - def temporalProfileLayer(self)->QgsVectorLayer: + def temporalProfileLayer(self)->TemporalProfileLayer: """ Returns the TemporalProfileLayer :return: @@ -689,15 +699,17 @@ class TimeSeriesViewer(QgisInterface, QObject): return self.ui.actionZoomOut - def setCurrentDate(self, tsd:TimeSeriesDate): + def setCurrentDate(self, tsd): """ Moves the viewport of the scroll window to a specific TimeSeriesDate - :param tsd: TimeSeriesDate + :param tsd: TimeSeriesDate or numpy.datetime64 """ - self.ui.mMapWidget.setCurrentDate(tsd) + tsd = self.timeSeries().findDate(tsd) + if isinstance(tsd, TimeSeriesDate): + self.ui.mMapWidget.setCurrentDate(tsd) - def mapCanvases(self)->list: + def mapCanvases(self)->typing.List[MapCanvas]: """ Returns all MapCanvases of the spatial visualization :return: [list-of-MapCanvases] @@ -959,12 +971,13 @@ class TimeSeriesViewer(QgisInterface, QObject): assert isinstance(spatialPoint, SpatialPoint) assert isinstance(mapCanvas, QgsMapCanvas) from eotimeseriesviewer.mapcanvas import MapTools - assert mapToolKey in MapTools.mapToolKeys() + assert mapToolKey in MapTools.mapToolValues() if mapToolKey == MapTools.TemporalProfile: + self.spectralTemporalVis.loadCoordinate(spatialPoint) - elif mapToolKey == MapTools.SpectralProfile: + elif mapToolKey == MapTools.SpectralProfile: tsd = None from .mapcanvas import MapCanvas if isinstance(mapCanvas, MapCanvas): @@ -1007,7 +1020,7 @@ class TimeSeriesViewer(QgisInterface, QObject): return self.ui.mMapWidget.messageBar() - def loadTimeSeriesDefinition(self, path:str=None, n_max:int=None): + def loadTimeSeriesDefinition(self, path:str=None, n_max:int=None, runAsync=True): """ Loads a time series definition file :param path: @@ -1031,17 +1044,17 @@ class TimeSeriesViewer(QgisInterface, QObject): s.setValue('file_ts_definition', path) self.clearTimeSeries() progressDialog = self._createProgressDialog() - self.mTimeSeries.loadFromFile(path, n_max=n_max, progressDialog=progressDialog) + self.mTimeSeries.loadFromFile(path, n_max=n_max, progressDialog=progressDialog, runAsync=runAsync) - def createMapView(self, name:str=None): + def createMapView(self, name:str=None)->MapView: """ Creates a new MapView. :return: MapView """ return self.ui.dockMapViews.createMapView(name=name) - def mapViews(self)->list: + def mapViews(self)->typing.List[MapView]: """ Returns all MapViews :return: [list-of-MapViews] @@ -1064,34 +1077,49 @@ class TimeSeriesViewer(QgisInterface, QObject): """ return self.spectralTemporalVis.temporalProfileLayer()[:] - def logMessage(self, message, tag, level): - m = message.split('\n') - if '' in message.split('\n'): - m = m[0:m.index('')] - m = '\n'.join(m) - if DEBUG: print(message) - if not re.search('timeseriesviewer', m): - return + def logMessage(self, message:str, tag:str, level): + """ - if level in [Qgis.Critical, Qgis.Warning]: + """ + if tag == LOG_MESSAGE_TAG and level in [Qgis.Critical, Qgis.Warning]: + self.messageBar().pushMessage(tag, message, level) - self.ui.messageBar.pushMessage(tag, message, level=level) - print(r'{}({}): {}'.format(tag, level, message)) + def onSensorAdded(self, sensor:SensorInstrument): + knownName = eotsvSettings.sensorName(sensor.id()) + if isinstance(knownName, str) and len(knownName) > 0: + sensor.setName(knownName) - def onTimeSeriesChanged(self, *args): + sensor.sigNameChanged.connect(lambda *args, s=sensor: self.onSensorNameChanged(sensor)) + def onSensorNameChanged(self, sensor:SensorInstrument): + # save changed names to settings + from eotimeseriesviewer.settings import saveSensorName + if sensor in self.sensors(): + saveSensorName(sensor) + + def onTimeSeriesChanged(self, *args): if not self.mSpatialMapExtentInitialized: if len(self.mTimeSeries) > 0: if len(self.ui.dockMapViews) == 0: self.ui.dockMapViews.createMapView() - extent = self.mTimeSeries.maxSpatialExtent() + extent = self.timeSeries().maxSpatialExtent() + if isinstance(extent, SpatialExtent): + self.ui.dockMapViews.setCrs(extent.crs()) + self.ui.mMapWidget.setSpatialExtent(extent) + self.mSpatialMapExtentInitialized = True + else: + self.mSpatialMapExtentInitialized = False + print('Failed to calculate max. spatial extent of TimeSeries with length {}'.format(len(self.timeSeries()))) + lastDate = self.ui.mMapWidget.currentDate() + if lastDate: + tsd = self.timeSeries().findDate(lastDate) + else: + tsd = self.timeSeries()[0] + self.setCurrentDate(tsd) - self.ui.mMapWidget.setCrs(extent.crs()) - self.ui.mMapWidget.setSpatialExtent(extent) - self.mSpatialMapExtentInitialized = True @@ -1263,15 +1291,8 @@ class TimeSeriesViewer(QgisInterface, QObject): progressDialog.setRange(0, len(files)) progressDialog.setLabelText('Start loading {} images....'.format(len(files))) - if loadAsync: - self.mTimeSeries.addSourcesAsync(files, progressDialog=progressDialog) - else: - self.mTimeSeries.addSources(files, progressDialog=progressDialog) - - + self.mTimeSeries.addSources(files, progressDialog=progressDialog, runAsync=loadAsync) - #QCoreApplication.processEvents() - #self.mTimeSeries.addSources(files) def clearTimeSeries(self): diff --git a/eotimeseriesviewer/mapcanvas.py b/eotimeseriesviewer/mapcanvas.py index bc856e34775886d9f64f1a56fa1c0ae424e921be..ecdf440d641c891ac1904400b65d445faee61698 100644 --- a/eotimeseriesviewer/mapcanvas.py +++ b/eotimeseriesviewer/mapcanvas.py @@ -529,16 +529,38 @@ class MapCanvas(QgsMapCanvas): """ Updates map-canvas TSD variables """ - scope = self.expressionContextScope() + from .mapvisualization import MapView + from .main import TimeSeriesViewer + + + varMVNumber = None + varMVName = None + varDate = None + varDOY = None + varSensor = None + + tsd = self.tsd() if isinstance(tsd, TimeSeriesDate): - scope.setVariable('map_date', str(tsd.date()), isStatic=False) - scope.setVariable('map_doy', tsd.doy(), isStatic=False) - scope.setVariable('map_sensor', tsd.sensor().name(), isStatic=False) - else: - scope.setVariable('map_date', None, isStatic=False) - scope.setVariable('map_doy', None, isStatic=False) - scope.setVariable('map_sensor', None, isStatic=False) + varDate = str(tsd.date()) + varDOY = tsd.doy() + varSensor = tsd.sensor().name() + + mv = self.mapView() + if isinstance(mv, MapView): + varMVName = mv.name() + if isinstance(TimeSeriesViewer.instance(), TimeSeriesViewer): + mapViews = TimeSeriesViewer.instance().mapViews() + if mv in mapViews: + varMVNumber = mapViews.index(mv) + 1 + + scope = self.expressionContextScope() + scope.setVariable('map_view_num', varMVNumber, isStatic=False) + scope.setVariable('map_view', varMVName, isStatic=False) + scope.setVariable('map_date', varDate, isStatic=False) + scope.setVariable('map_doy', varDOY, isStatic=False) + scope.setVariable('map_sensor', varSensor, isStatic=False) + def tsd(self)->TimeSeriesDate: @@ -642,7 +664,7 @@ class MapCanvas(QgsMapCanvas): self.mTimedRefreshPipeLine[MapCanvas.Command].append(a) else: - raise NotImplementedError('Unsupported argument: {}'.format(str(a))) + print('Unsupported argument: {} {}'.format(type(a), str(a)), file=sys.stderr) def timedRefresh(self): @@ -671,7 +693,8 @@ class MapCanvas(QgsMapCanvas): if source in existingSources: sourceLayer = existing[existingSources.index(source)] else: - sourceLayer = SensorProxyLayer(source, sensor=self.tsd().sensor()) + loptions = QgsRasterLayer.LayerOptions(loadDefaultStyle=False) + sourceLayer = SensorProxyLayer(source, sensor=self.tsd().sensor(), options=loptions) sourceLayer.setName(lyr.name()) sourceLayer.setCustomProperty('eotsv/sensorid', self.tsd().sensor().id()) try: @@ -822,7 +845,7 @@ class MapCanvas(QgsMapCanvas): def contextMenu(self, pos:QPoint)->QMenu: """ - Create the MapCanvas context menu with options relevant for pixel position ``pos``. + Creates the MapCanvas context menu with options relevant for pixel position ``pos``. :param pos: QPoint :return: QMenu """ @@ -864,34 +887,37 @@ class MapCanvas(QgsMapCanvas): lyrWithSelectedFeatures = [l for l in quickLabelLayers() if l.isEditable() and l.selectedFeatureCount() > 0] layerNames = ', '.join([l.name() for l in lyrWithSelectedFeatures]) - m = menu.addMenu('Quick Labels'.format(self.tsd().date())) + m = menu.addMenu('Quick Labels') m.setToolTipsVisible(True) nQuickLabelLayers = len(lyrWithSelectedFeatures) m.setEnabled(nQuickLabelLayers > 0) a = m.addAction('Set Date/Sensor attributes') - a.setToolTip('Writes the date ate and sensor quick labels of selected features in {}.'.format(layerNames)) + a.setToolTip('Writes the dates and sensor quick labels of selected features in {}.'.format(layerNames)) a.triggered.connect(lambda *args, tsd = self.tsd(), tss=tss: setQuickTSDLabelsForRegisteredLayers(tsd, tss)) - from .labeling import EDITOR_WIDGET_REGISTRY_KEY as QUICK_LABEL_KEY from .labeling import CONFKEY_CLASSIFICATIONSCHEME, layerClassSchemes, setQuickClassInfo + if len(lyrWithSelectedFeatures) == 0: + a = m.addAction('No features selected.') + a.setToolTip('Select feature in the labeling panel to apply Quick label value on.') + a.setEnabled(False) + else: + for layer in lyrWithSelectedFeatures: + assert isinstance(layer, QgsVectorLayer) - for layer in lyrWithSelectedFeatures: - assert isinstance(layer, QgsVectorLayer) - - csf = layerClassSchemes(layer) - if len(csf) > 0: - m.addSection(layer.name()) - for (cs, field) in csf: - assert isinstance(cs, ClassificationScheme) - assert isinstance(field, QgsField) + csf = layerClassSchemes(layer) + if len(csf) > 0: + m.addSection(layer.name()) + for (cs, field) in csf: + assert isinstance(cs, ClassificationScheme) + assert isinstance(field, QgsField) - classMenu = m.addMenu('"{}" ({})'.format(field.name(), field.typeName())) - for classInfo in cs: - assert isinstance(classInfo, ClassInfo) - a = classMenu.addAction('{} "{}"'.format(classInfo.label(), classInfo.name())) - a.setIcon(classInfo.icon()) - a.triggered.connect(lambda _, vl=layer, f=field, c=classInfo: setQuickClassInfo(vl, f, c)) + classMenu = m.addMenu('"{}" ({})'.format(field.name(), field.typeName())) + for classInfo in cs: + assert isinstance(classInfo, ClassInfo) + a = classMenu.addAction('{} "{}"'.format(classInfo.label(), classInfo.name())) + a.setIcon(classInfo.icon()) + a.triggered.connect(lambda _, vl=layer, f=field, c=classInfo: setQuickClassInfo(vl, f, c)) if isinstance(refSensorLayer, SensorProxyLayer): @@ -906,6 +932,7 @@ class MapCanvas(QgsMapCanvas): action.triggered.connect(lambda *args, lyr=refSensorLayer: self.stretchToExtent(self.spatialExtent(), 'gaussian', layer=lyr, n=3)) + menu.addSeparator() from .externals.qps.layerproperties import pasteStyleFromClipboard, pasteStyleToClipboard @@ -929,7 +956,11 @@ class MapCanvas(QgsMapCanvas): for mapLayer in visibleLayers: #sub = m.addMenu(mapLayer.name()) - sub = m.addMenu(os.path.basename(mapLayer.source())) + if isinstance(mapLayer, SensorProxyLayer): + name = os.path.basename(mapLayer.source()) + else: + name = mapLayer.name() + sub = m.addMenu(name) if isinstance(mapLayer, SensorProxyLayer): sub.setIcon(QIcon(':/timeseriesviewer/icons/icon.svg')) @@ -945,11 +976,8 @@ class MapCanvas(QgsMapCanvas): sub.setIcon(QIcon(r':/images/themes/default/mIconPointLayer.svg')) a = sub.addAction('Properties...') - a.triggered.connect(lambda *args, - lyr = mapLayer, - c = self, - b = isinstance(mapLayer, SensorProxyLayer) == False: - showLayerPropertiesDialog(lyr, c, useQGISDialog=b)) + a.triggered.connect(lambda *args, lyr = mapLayer: self.onSetLayerProperties(lyr)) + a = sub.addAction('Zoom to Layer') a.setIcon(QIcon(':/images/themes/default/mActionZoomToLayer.svg')) @@ -981,12 +1009,12 @@ class MapCanvas(QgsMapCanvas): action.setChecked(self.mCrosshairItem.visibility()) action.toggled.connect(self.setCrosshairVisibility) - action = m.addAction('Style') + action = m.addAction('Set Style') def onCrosshairChange(*args): style = CrosshairDialog.getCrosshairStyle(parent=self, mapCanvas=self, - crosshairStyle=self.mCrosshairItem.crosshairStyle) + crosshair=self.mCrosshairItem.crosshairStyle) if isinstance(style, CrosshairStyle): self.setCrosshairStyle(style) @@ -999,15 +1027,22 @@ class MapCanvas(QgsMapCanvas): action = m.addAction('Date') action.triggered.connect(lambda: QApplication.clipboard().setText(str(tsd.date()))) action.setToolTip('Sends "{}" to the clipboard.'.format(str(tsd.date()))) + action = m.addAction('Sensor') action.triggered.connect(lambda: QApplication.clipboard().setText(tsd.sensor().name())) action.setToolTip('Sends "{}" to the clipboard.'.format(tsd.sensor().name())) - action = m.addAction('Path') + action = m.addAction('Path') paths = [QDir.toNativeSeparators(p) for p in tsd.sourceUris()] - action.triggered.connect(lambda _, paths=paths: QApplication.clipboard().setText('\n'.join(paths))) action.setToolTip('Sends {} source URI(s) to the clipboard.'.format(len(tsd))) + + extent = self.extent() + assert isinstance(extent, QgsRectangle) + action = m.addAction('Extent') + action.triggered.connect(lambda _, extent=extent: QApplication.clipboard().setText(extent.toString())) + action.setToolTip('Sends the map extent to the clipboard.') + action = m.addAction('Map') action.triggered.connect(lambda: QApplication.clipboard().setPixmap(self.pixmap())) action.setToolTip('Copies this map into the clipboard.') @@ -1100,12 +1135,27 @@ class MapCanvas(QgsMapCanvas): return menu + def onSetLayerProperties(self, lyr:QgsRasterLayer): + # b = isinstance(mapLayer, SensorProxyLayer) == False: + + result = showLayerPropertiesDialog(lyr, self, useQGISDialog=True) + + if result == QDialog.Accepted and isinstance(lyr, SensorProxyLayer): + r = lyr.renderer().clone() + proxyLayer = self.mMapView.sensorProxyLayer(lyr.sensor()) + r.setInput(proxyLayer.dataProvider()) + proxyLayer.setRenderer(r) + def onPasteStyleFromClipboard(self, lyr): from .externals.qps.layerproperties import pasteStyleFromClipboard pasteStyleFromClipboard(lyr) if isinstance(lyr, SensorProxyLayer): - self.mMapView.sensorProxyLayer(lyr.sensor()).setRenderer(lyr.renderer().clone()) + r = lyr.renderer().clone() + proxyLayer = self.mMapView.sensorProxyLayer(lyr.sensor()) + r.setInput(proxyLayer.dataProvider()) + proxyLayer.setRenderer(r) + def contextMenuEvent(self, event:QContextMenuEvent): """ @@ -1132,9 +1182,11 @@ class MapCanvas(QgsMapCanvas): lqgis.setRenderer(l.renderer().clone()) def stretchToCurrentExtent(self): - + """ + Stretches the top-raster layer band to the current spatial extent + """ se = self.spatialExtent() - self.stretchToExtent(se) + self.stretchToExtent(se, stretchType='linear_minmax', p=0.05) def stretchToExtent(self, spatialExtent:SpatialExtent, stretchType='linear_minmax', layer:QgsRasterLayer=None, **stretchArgs): """ @@ -1157,6 +1209,9 @@ class MapCanvas(QgsMapCanvas): if not isinstance(layer, QgsRasterLayer): return + if not isinstance(spatialExtent, SpatialExtent): + spatialExtent = SpatialExtent.fromLayer(layer) + r = layer.renderer() dp = layer.dataProvider() newRenderer = None @@ -1224,7 +1279,7 @@ class MapCanvas(QgsMapCanvas): if isinstance(layer, SensorProxyLayer): self.mMapView.sensorProxyLayer(layer.sensor()).setRenderer(newRenderer) elif isinstance(layer, QgsRasterLayer): - layer.setRenderer(layer) + layer.setRenderer(newRenderer) def saveMapImageDialog(self, fileType): diff --git a/eotimeseriesviewer/mapvisualization.py b/eotimeseriesviewer/mapvisualization.py index dba3a04d9f27b5bd734cf9ae6a429b5592a0c048..9683f186ba73314433a23936d51a984d4c91d2f4 100644 --- a/eotimeseriesviewer/mapvisualization.py +++ b/eotimeseriesviewer/mapvisualization.py @@ -108,7 +108,8 @@ class MapViewLayerTreeViewMenuProvider(QgsLayerTreeViewMenuProvider): menu = QMenu(view) isSensorGroup = isinstance(g, QgsLayerTreeGroup) and g.customProperty(KEY_SENSOR_GROUP) in [True, 'true'] - isSensorLayer = isinstance(l, QgsRasterLayer) and l.customProperty(KEY_SENSOR_LAYER) in [True, 'true'] + + if isinstance(self.mapView(), MapView):isSensorLayer = isinstance(l, QgsRasterLayer) and l.customProperty(KEY_SENSOR_LAYER) in [True, 'true'] self.actionRemove.setEnabled(not (isSensorGroup or isSensorLayer)) self.actionAddGroup.setEnabled(not (isSensorGroup or isSensorLayer)) menu.addAction(self.actionAddGroup) @@ -127,26 +128,22 @@ class MapViewLayerTreeViewMenuProvider(QgsLayerTreeViewMenuProvider): menu.addSeparator() centerCanvas = None - if isinstance(self.mapView(), MapView): - visibleCanvases = self.mapView().visibleMapCanvases() - if len(visibleCanvases) > 0: - i = int(len(visibleCanvases) / 2) - centerCanvas = visibleCanvases[i] + visibleCanvases = self.mapView().visibleMapCanvases() + if len(visibleCanvases) > 0: + i = int(len(visibleCanvases) / 2) + centerCanvas = visibleCanvases[i] a = menu.addAction('Set Properties') - a.triggered.connect(lambda *args, - canvas = centerCanvas, - lyr = l, - b = not isinstance(l, SensorProxyLayer): - showLayerPropertiesDialog(lyr, canvas, useQGISDialog=b)) + a.triggered.connect(lambda *args, canvas = centerCanvas, lyr = l: self.onSetLayerProperties(lyr, canvas)) - a.setEnabled(isinstance(centerCanvas, QgsMapCanvas)) + a.setEnabled(isinstance(centerCanvas, MapCanvas)) from .externals.qps.layerproperties import pasteStyleFromClipboard, pasteStyleToClipboard a = menu.addAction('Copy Style') a.setToolTip('Copy the current layer style to clipboard') - a.triggered.connect(lambda *args, lyr=l: pasteStyleToClipboard(lyr)) + + a.triggered.connect(lambda *args, c=centerCanvas, lyr=l: pasteStyleToClipboard(lyr)) a = menu.addAction('Paste Style') a.setEnabled('application/qgis.style' in QApplication.clipboard().mimeData().formats()) @@ -158,6 +155,10 @@ class MapViewLayerTreeViewMenuProvider(QgsLayerTreeViewMenuProvider): return menu + def onSetLayerProperties(self, lyr:QgsRasterLayer, canvas:QgsMapCanvas): + if isinstance(canvas, MapCanvas): + canvas.onSetLayerProperties(lyr) + class MapViewLayerTreeModel(QgsLayerTreeModel): """ Layer Tree as shown in a MapView @@ -469,7 +470,7 @@ class MapView(QFrame, loadUIFormClass(jp(DIR_UI, 'mapview.ui'))): """ return not self.actionToggleMapViewHidden.isChecked() - def mapCanvases(self)->list: + def mapCanvases(self)->typing.List[MapCanvas]: """ Returns the MapCanvases related to this map view. Requires that this mapview was added to a MapWidget :return: [list-of-MapCanvases] @@ -595,7 +596,7 @@ class MapView(QFrame, loadUIFormClass(jp(DIR_UI, 'mapview.ui'))): self.sigCrosshairChanged.emit() - def sensorProxyLayers(self)->list: + def sensorProxyLayers(self)->typing.List[SensorProxyLayer]: layers = [n.layer() for n in self.mLayerTreeSensorNode.findLayers()] return [l for l in layers if isinstance(l, SensorProxyLayer)] @@ -676,14 +677,14 @@ class MapView(QFrame, loadUIFormClass(jp(DIR_UI, 'mapview.ui'))): :param sensor: :return: """ - pair = None - for i, t in enumerate(self.mSensorLayerList): + toRemove = [] + for t in self.mSensorLayerList: if t[0] == sensor: - pair = t - break - assert pair is not None, 'Sensor "{}" not found'.format(sensor.name()) - self.mLayerTreeSensorNode.removeLayer(pair[1]) - self.mSensorLayerList.remove(pair) + toRemove.append(t) + + for t in toRemove: + self.mLayerTreeSensorNode.removeLayer(t[1]) + self.mSensorLayerList.remove(t) def hasSensor(self, sensor)->bool: @@ -850,10 +851,8 @@ class MapWidget(QFrame, loadUIFormClass(jp(DIR_UI, 'mapwidget.ui'))): self.mCanvasSignals = dict() self.mTimeSeries = None - self.mMapToolKey = MapTools.Pan - self.mViewMode = MapWidget.ViewMode.MapViewByRows self.mMpMV = 3 @@ -870,9 +869,6 @@ class MapWidget(QFrame, loadUIFormClass(jp(DIR_UI, 'mapwidget.ui'))): self.mMapRefreshTimer.setInterval(500) self.mMapRefreshTimer.start() - #self.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) - - self.btnFirst.setDefaultAction(self.actionFirstDate) self.btnLast.setDefaultAction(self.actionLastDate) self.btnBackward.setDefaultAction(self.actionBackward) @@ -957,7 +953,7 @@ class MapWidget(QFrame, loadUIFormClass(jp(DIR_UI, 'mapwidget.ui'))): :param extent: SpatialExtent :return: SpatialExtent the current SpatialExtent """ - + assert isinstance(extent, SpatialExtent) if self.mSpatialExtent != extent: self.mSpatialExtent = extent @@ -1052,20 +1048,26 @@ class MapWidget(QFrame, loadUIFormClass(jp(DIR_UI, 'mapwidget.ui'))): def _updateSliderRange(self): + slider = self.timeSlider() + assert isinstance(slider, QSlider) n = len(self.timeSeries()) - self.mTimeSlider.setRange(0, n) - self.mTimeSlider.setEnabled(n > 0) + slider.setRange(0, n) + slider.setEnabled(n > 0) + if n > 10: pageStep = int(n/100)*10 + slider.setTickInterval(pageStep) else: pageStep = 5 - self.timeSlider().setPageStep(pageStep) + slider.setTickInterval(0) + + slider.setPageStep(pageStep) + if n > 0: tsd = self.currentDate() - if isinstance(tsd, TimeSeriesDate) and tsd in self.timeSeries(): i = self.timeSeries()[:].index(tsd) - self.mTimeSlider.setValue(i+1) + slider.setValue(i+1) def onSliderReleased(self): @@ -1087,7 +1089,12 @@ class MapWidget(QFrame, loadUIFormClass(jp(DIR_UI, 'mapwidget.ui'))): self._updateGrid() self.sigViewModeChanged.emit(self.mViewMode) - def setMapsPerMapView(self, n:int): + def setMapsPerMapView(self, n:int)->int: + """ + Sets the number of maps per map viewe + :param n: int + :return: int, number of maps per map view + """ assert n >= 0 if n != self.mMpMV: @@ -1095,6 +1102,15 @@ class MapWidget(QFrame, loadUIFormClass(jp(DIR_UI, 'mapwidget.ui'))): self._updateGrid() self.timeSlider().setPageStep(max(1, n)) self.sigMapsPerMapViewChanged.emit(n) + return self.mapsPerMapView() + + + def mapsPerMapView(self)->int: + """ + Returns the number of maps per map view + :return: int + """ + return self.mMpMV def setMapSize(self, size:QSize)->QSize: """ @@ -1120,14 +1136,14 @@ class MapWidget(QFrame, loadUIFormClass(jp(DIR_UI, 'mapwidget.ui'))): """ return self.mMapSize - def mapCanvases(self)->list: + def mapCanvases(self)->typing.List[MapCanvas]: """ Returns all MapCanvases :return: [list-of-MapCanvases] """ return self.findChildren(MapCanvas) - def mapViewCanvases(self, mapView:MapView)->list: + def mapViewCanvases(self, mapView:MapView)->typing.List[MapCanvas]: """ Returns the MapCanvases related to a MapView :param mapView: MapView @@ -1188,7 +1204,7 @@ class MapWidget(QFrame, loadUIFormClass(jp(DIR_UI, 'mapwidget.ui'))): """ assert isinstance(tsd, TimeSeriesDate) - b = tsd != self.mCurrentDate + b = tsd != self.mCurrentDate or (len(self.mapCanvases()) > 0 and self.mapCanvases()[0].tsd() is None) self.mCurrentDate = tsd if b: @@ -1324,10 +1340,6 @@ class MapWidget(QFrame, loadUIFormClass(jp(DIR_UI, 'mapwidget.ui'))): mapTools = mapCanvas.mapTools() mapTools.activate(self.mMapToolKey) - mt = mapCanvas.mapTool() - if isinstance(mt, QgsMapToolSelect): - mt.setSelectionMode(self.mMapToolMode) - # connect signals self._connectCanvasSignals(mapCanvas) return mapCanvas @@ -1655,6 +1667,13 @@ class MapViewDock(QgsDockWidget, loadUI('mapviewdock.ui')): self.btnAddMapView.setDefaultAction(self.actionAddMapView) self.btnRemoveMapView.setDefaultAction(self.actionRemoveMapView) + self.btnCrs.setOptionVisible(QgsProjectionSelectionWidget.LayerCrs, True) + self.btnCrs.setOptionVisible(QgsProjectionSelectionWidget.ProjectCrs, True) + self.btnCrs.setOptionVisible(QgsProjectionSelectionWidget.CurrentCrs, True) + self.btnCrs.setOptionVisible(QgsProjectionSelectionWidget.DefaultCrs, True) + self.btnCrs.setOptionVisible(QgsProjectionSelectionWidget.RecentCrs, True) + #self.btnCrs.setOptionVisible(QgsProjectionSelectionWidget.CrsNotSet, True) + self.btnCrs.crsChanged.connect(self.sigCrsChanged) self.btnMapCanvasColor.colorChanged.connect(self.sigMapCanvasColorChanged) self.btnTextFormat.changed.connect(lambda *args: self.sigMapTextFormatChanged.emit(self.mapTextFormat())) @@ -1710,7 +1729,7 @@ class MapViewDock(QgsDockWidget, loadUI('mapviewdock.ui')): return self.mMapWidget - def mapWidget(self): + def mapWidget(self)->MapWidget: """ Returns the connected MapWidget :return: MapWidget @@ -2033,7 +2052,8 @@ class MapViewDock(QgsDockWidget, loadUI('mapviewdock.ui')): Removes a Sensor :param sensor: SensorInstrument """ - for mapView in self.mMapViews: + for mapView in self.mapViews(): + assert isinstance(mapView, MapView) mapView.removeSensor(sensor) def applyStyles(self): diff --git a/eotimeseriesviewer/pixelloader.py b/eotimeseriesviewer/pixelloader.py index 48fea99e181a70ac1dc7335d17c56ae82613c44c..c16434939ae871e0677aa478e63c78f419314bdc 100644 --- a/eotimeseriesviewer/pixelloader.py +++ b/eotimeseriesviewer/pixelloader.py @@ -38,10 +38,6 @@ if DEBUG: logger = multiprocessing.log_to_stderr() logger.setLevel(multiprocessing.SUBDEBUG) -class TaskMock(QgsTask): - def __init__(self): - super(TaskMock, self).__init__() - def dprint(msg): if DEBUG: print('PixelLoader: {}'.format(msg)) @@ -60,29 +56,31 @@ def isOutOfImage(ds, px): return False - class PixelLoaderTask(object): """ - An object to store the results of an loading from a single raster source. + An object to store the loading results from a *single* raster source. """ - @staticmethod - def fromDump(byte_object): - return pickle.loads(byte_object) + def __init__(self, source:str, temporalProfiles:list, bandIndices=None): - def __init__(self, source:str, geometries, bandIndices=None, **kwargs): + assert isinstance(temporalProfiles, list) - if not isinstance(geometries, list): - geometries = [geometries] - assert isinstance(geometries, list) geometries = [g for g in geometries if isinstance(g, (SpatialExtent, SpatialPoint))] self.mId = '' # assert isinstance(source, str) or isinstance(source, unicode) - self.sourcePath = source - self.geometries = geometries - self.bandIndices = bandIndices + self.mSourcePath = source + self.mGeometries = [] + self.mTemporalProfileIDs = [] + + from .temporalprofiles import TemporalProfile + for tp in temporalProfiles: + assert isinstance(tp, TemporalProfile) + self.mTemporalProfileIDs.append(tp.id()) + self.mGeometries.append(tp.geometry()) + + self.mBandIndices = bandIndices # for internal use only self.mIsDone = False @@ -92,16 +90,10 @@ class PixelLoaderTask(object): self.resGeoTransformation = None self.resProfiles = None self.resNoDataValues = None + self.resPixelCoordinates = None self.exception = None self.info = None - # other, free keywords - for k in kwargs.keys(): - assert isinstance(k, str) - assert not k.startswith('_') - if not k in self.__dict__.keys(): - self.__dict__[k] = kwargs[k] - def setId(self, idStr:str): self.mId = idStr @@ -154,15 +146,17 @@ class PixelLoaderTask(object): def __repr__(self): info = ['PixelLoaderTask:'] + info.append('\t' + self.mSourcePath) + info.append('\t' + ','.join(str(g) for g in self.mGeometries)) if not self.mIsDone: info.append('not started...') else: - if self.bandIndices: - info.append('\tBandIndices {}:{}'.format(len(self.bandIndices), self.bandIndices)) + if self.mBandIndices: + info.append('\tBandIndices {}:{}'.format(len(self.mBandIndices), self.mBandIndices)) if self.resProfiles: info.append('\tProfileData: {}'.format(len(self.resProfiles))) for i, p in enumerate(self.resProfiles): - g = self.geometries[i] + g = self.mGeometries[i] d = self.resProfiles[i] if d in [INFO_OUT_OF_IMAGE, INFO_NO_DATA]: info.append('\t{}: {}:{}'.format(i + 1, g, d)) @@ -187,24 +181,27 @@ def transformPoint2Px(trans, pt, gt): return geo2px(QgsPointXY(x, y), gt) -def doLoaderTask(taskWrapper:QgsTask, dump): +def doLoaderTask(qgsTask:QgsTask, dump): - assert isinstance(taskWrapper, QgsTask) + assert isinstance(qgsTask, QgsTask) tasks = pickle.loads(dump) results = [] - for task in tasks: + nTasks = len(tasks) + qgsTask.setProgress(0) + + for iTask, task in enumerate(tasks): assert isinstance(task, PixelLoaderTask) result = task - ds = gdal.Open(task.sourcePath, gdal.GA_ReadOnly) + ds = gdal.Open(task.mSourcePath, gdal.GA_ReadOnly) nb, ns, nl = ds.RasterCount, ds.RasterXSize, ds.RasterYSize - bandIndices = list(range(nb)) if task.bandIndices is None else list(task.bandIndices) + bandIndices = list(range(nb)) if task.mBandIndices is None else list(task.mBandIndices) # ensure to load valid indices only bandIndices = [i for i in bandIndices if i >= 0 and i < nb] - task.bandIndices = bandIndices + task.mBandIndices = bandIndices gt = ds.GetGeoTransform() result.resGeoTransformation = gt @@ -216,7 +213,7 @@ def doLoaderTask(taskWrapper:QgsTask, dump): # convert Geometries into pixel indices to be extracted PX_SUBSETS = [] - for geom in task.geometries: + for geom in task.mGeometries: crsRequest = osr.SpatialReference() if geom.crs().isValid(): @@ -225,6 +222,12 @@ def doLoaderTask(taskWrapper:QgsTask, dump): crsRequest.ImportFromWkt(crsSrc.ExportToWkt()) trans = osr.CoordinateTransformation(crsRequest, crsSrc) + trans2 = QgsCoordinateTransform() + trans2.setSourceCrs(QgsCoordinateReferenceSystem(crsRequest.ExportToWkt())) + trans2.setDestinationCrs(QgsCoordinateReferenceSystem(crsSrc.ExportToWkt())) + lyr = QgsRasterLayer(task.mSourcePath) + geom2 = trans2.transform(geom) + if isinstance(geom, QgsPointXY): ptUL = ptLR = QgsPointXY(geom) elif isinstance(geom, QgsRectangle): @@ -241,6 +244,7 @@ def doLoaderTask(taskWrapper:QgsTask, dump): bLR = isOutOfImage(ds, pxLR) if all([bUL, bLR]): + PX_SUBSETS.append(INFO_OUT_OF_IMAGE) continue @@ -266,8 +270,10 @@ def doLoaderTask(taskWrapper:QgsTask, dump): size_x = abs(pxUL.x() - pxLR.x()) size_y = abs(pxUL.y() - pxLR.y()) - if size_x < 1: size_x = 1 - if size_y < 1: size_y = 1 + if size_x < 1: + size_x = 1 + if size_y < 1: + size_y = 1 PX_SUBSETS.append((pxUL, pxUL, size_x, size_y)) @@ -328,8 +334,6 @@ def doLoaderTask(taskWrapper:QgsTask, dump): #PROFILE_DATA[i] = np.dstack(pd).transpose(2,0,1) PROFILE_DATA[i] = np.vstack(pd) - - # finally, ensure that there is on 2D array only for i in range(len(PROFILE_DATA)): d = PROFILE_DATA[i] @@ -353,44 +357,13 @@ def doLoaderTask(taskWrapper:QgsTask, dump): s = "" task.resProfiles = PROFILE_DATA + task.resPixelCoordinates = PX_SUBSETS task.mIsDone = True results.append(task) + qgsTask.setProgress(100 * (iTask + 1) / nTasks) return pickle.dumps(results) - -class LoadingProgress(object): - - def __init__(self, id, nFiles): - assert isinstance(nFiles, int) - assert isinstance(id, int) - self.mID = id - self.mSuccess = 0 - self.mTotal = nFiles - self.mFailed = 0 - - def addResult(self, success=True): - assert self.done() <= self.mTotal - if success: - self.mSuccess += 1 - else: - self.mFailed += 1 - - def id(self): - return self.mID - - def failed(self): - return self.mFailed - - - def done(self): - return self.mSuccess + self.mFailed - - def total(self): - return self.mTotal - - - class PixelLoader(QObject): """ Loads pixel from raster images @@ -399,7 +372,7 @@ class PixelLoader(QObject): sigPixelLoaded = pyqtSignal(PixelLoaderTask) sigLoadingStarted = pyqtSignal() sigLoadingFinished = pyqtSignal() - + sigProgressChanged = pyqtSignal(float) def __init__(self, *args, **kwds): super(PixelLoader, self).__init__(*args, **kwds) @@ -423,26 +396,24 @@ class PixelLoader(QObject): dump = pickle.dumps(tasks) + self.sigLoadingStarted.emit() + qgsTask = QgsTask.fromFunction('Load band values', doLoaderTask, dump, on_finished=self.onLoadingFinished) + assert isinstance(qgsTask, QgsTask) - if False: - qgsTask = TaskMock() - resultDump = doLoaderTask(qgsTask, dump) - self.onLoadingFinished(TaskMock(), resultDump) + tid = id(qgsTask) - else: - qgsTask = QgsTask.fromFunction('Load band values', doLoaderTask, dump, on_finished=self.onLoadingFinished) - tid = id(qgsTask) - qgsTask.taskCompleted.connect(lambda *args, tid=tid: self.onRemoveTask(tid)) - qgsTask.taskTerminated.connect(lambda *args, tid=tid: self.onRemoveTask(tid)) - # todo: progress changed? - self.mTasks[tid] = qgsTask - tm = self.taskManager() - tm.addTask(qgsTask) + qgsTask.taskCompleted.connect(lambda *args, tid=tid: self.onRemoveTask(tid)) + qgsTask.taskTerminated.connect(lambda *args, tid=tid: self.onRemoveTask(tid)) + qgsTask.progressChanged.connect(self.sigProgressChanged.emit) + self.mTasks[tid] = qgsTask + tm = self.taskManager() + tm.addTask(qgsTask) def onRemoveTask(self, tid): if tid in self.mTasks.keys(): - del self.mTasks[tid] + pass + #del self.mTasks[tid] def onLoadingFinished(self, *args, **kwds): @@ -454,6 +425,7 @@ class PixelLoader(QObject): assert isinstance(plt, PixelLoaderTask) #self.mTasks.pop(plt.id()) self.sigPixelLoaded.emit(plt) + self.sigLoadingFinished.emit() def status(self)->tuple: diff --git a/eotimeseriesviewer/profilevisualization.py b/eotimeseriesviewer/profilevisualization.py index 82467bcf5f97b42f40385f8c55170e91f13878c7..38ebedbf9246040a61e2a654a1899f332e9473d7 100644 --- a/eotimeseriesviewer/profilevisualization.py +++ b/eotimeseriesviewer/profilevisualization.py @@ -32,11 +32,13 @@ from qgis.PyQt.QtGui import * from .timeseries import * from .utils import SpatialExtent, SpatialPoint, px2geo, loadUI, nextColor from .externals.qps.plotstyling.plotstyling import PlotStyle, PlotStyleButton, PlotStyleDialog +from .externals.pyqtgraph import ScatterPlotItem, SpotItem, GraphicsScene +from .externals.qps.externals.pyqtgraph.GraphicsScene.mouseEvents import MouseClickEvent, MouseDragEvent from .externals import pyqtgraph as pg from .sensorvisualization import SensorListModel -from .temporalprofiles2d import * +from .temporalprofiles import * from .temporalprofiles3d import * - +from .pixelloader import PixelLoaderTask, doLoaderTask import numpy as np @@ -519,7 +521,7 @@ class PlotSettingsModel2D(QAbstractTableModel): - def requiredBandsIndices(self, sensor): + def requiredBandsIndices(self, sensor)->list: """ Returns the band indices required to calculate the values for the different PlotStyle expressions making use of sensor @@ -1330,10 +1332,10 @@ class SpectralTemporalVisualization(QObject): assert isinstance(profileDock, ProfileViewDockUI) self.ui = profileDock - - import eotimeseriesviewer.pixelloader - if DEBUG: - eotimeseriesviewer.pixelloader.DEBUG = True + self.mTasks = dict() + #import eotimeseriesviewer.pixelloader + #if DEBUG: + # eotimeseriesviewer.pixelloader.DEBUG = True #the timeseries. will be set later assert isinstance(timeSeries, TimeSeries) @@ -1341,13 +1343,15 @@ class SpectralTemporalVisualization(QObject): self.plot_initialized = False self.plot2D = self.ui.plotWidget2D + assert isinstance(self.plot2D, DateTimePlotWidget) self.plot2D.getViewBox().sigMoveToDate.connect(self.sigMoveToDate) + self.plot2D.getViewBox().scene().sigMouseClicked.connect(self.onPointsClicked2D) self.plot3D = self.ui.plotWidget3D + self.mLast2DMouseClickPosition = None # temporal profile collection to store loaded values self.mTemporalProfileLayer = TemporalProfileLayer(self.TS) self.mTemporalProfileLayer.sigTemporalProfilesAdded.connect(self.onTemporalProfilesAdded) - #self.mTemporalProfileLayer.startEditing() self.mTemporalProfileLayer.selectionChanged.connect(self.onTemporalProfileSelectionChanged) # fix to not loose C++ reference on temporal profile layer in case it is removed from QGIS mapcanvas @@ -1374,11 +1378,11 @@ class SpectralTemporalVisualization(QObject): self.mTemporalProfilesTableConfig = config self.mTemporalProfileLayer.setAttributeTableConfig(self.mTemporalProfilesTableConfig) - self.pixelLoader = PixelLoader() - self.pixelLoader.sigPixelLoaded.connect(self.onPixelLoaded) - self.pixelLoader.sigLoadingStarted.connect(lambda: self.ui.progressInfo.setText('Start loading...')) - # self.pixelLoader.sigLoadingStarted.connect(self.tpCollection.prune) - self.pixelLoader.sigLoadingFinished.connect(lambda: self.plot2D.enableAutoRange('x', False)) + #self.pixelLoader = PixelLoader() + #self.pixelLoader.sigPixelLoaded.connect(self.onPixelLoaded) + #self.pixelLoader.sigLoadingStarted.connect(self.onLoadingStarted) + #self.pixelLoader.sigLoadingFinished.connect(self.onLoadingFinished) + #self.pixelLoader.sigProgressChanged.connect(self.onLoadingProgressChanged) # set the plot models for 2D self.plotSettingsModel2D = PlotSettingsModel2D() @@ -1409,7 +1413,6 @@ class SpectralTemporalVisualization(QObject): self.ui.page3D.setHidden(True) - def onTemporalProfilesRemoved(removedProfiles): #set to valid temporal profile @@ -1462,6 +1465,23 @@ class SpectralTemporalVisualization(QObject): #self.ui.stackedWidget.setCurrentPage(self.ui.pagePixel) self.ui.onStackPageChanged(self.ui.stackedWidget.currentIndex()) + def onLoadingStarted(self): + self.ui.progressInfo.setText('Start loading...') + self.ui.progressBar.setRange(0, 0) + self.ui.progressBar.setValue(0) + + def onLoadingProgressChanged(self, progress): + if self.ui.progressBar.maximum() == 0: + self.ui.progressBar.setRange(0, 100) + + value = int(progress) + self.ui.progressBar.setValue(value) + + if value == 100: + self.plot2D.enableAutoRange('x', False) + self.ui.progressInfo.setText('') + self.ui.progressBar.setValue(0) + def plotStyles(self): return self.plotSettingsModel2D[:] @@ -1473,43 +1493,6 @@ class SpectralTemporalVisualization(QObject): """ return self.mTemporalProfileLayer - def onTemporalProfilesContextMenu(self, event): - assert isinstance(event, QContextMenuEvent) - tableView = self.ui.tableViewTemporalProfiles - selectionModel = self.ui.tableViewTemporalProfiles.selectionModel() - assert isinstance(selectionModel, QItemSelectionModel) - - model = self.ui.tableViewTemporalProfiles.model() - assert isinstance(model, TemporalProfileLayer) - - temporalProfiles = [] - - if len(selectionModel.selectedIndexes()) > 0: - for idx in selectionModel.selectedIndexes(): - tp = model.idx2tp(idx) - if isinstance(tp, TemporalProfile) and not tp in temporalProfiles: - temporalProfiles.append(tp) - else: - temporalProfiles = model[:] - - spatialPoints = [tp.coordinate() for tp in temporalProfiles] - - - menu = QMenu() - - a = menu.addAction('Load missing') - a.setToolTip('Loads missing band-pixels.') - a.triggered.connect(lambda : self.loadCoordinate(spatialPoints=spatialPoints, mode='all')) - s = "" - - a = menu.addAction('Reload') - a.setToolTip('Reloads all band-pixels.') - a.triggered.connect(lambda: self.loadCoordinate(spatialPoints=spatialPoints, mode='reload')) - - menu.popup(tableView.viewport().mapToGlobal(event.pos())) - self.menu = menu - - def selected2DPlotStyles(self): result = [] @@ -1518,8 +1501,6 @@ class SpectralTemporalVisualization(QObject): result.append(m.data(idx, Qt.UserRole)) return result - - def removePlotStyles2D(self, plotStyles): m = self.ui.tableView2DProfiles.model() if isinstance(m.sourceModel(), PlotSettingsModel2D): @@ -1557,8 +1538,8 @@ class SpectralTemporalVisualization(QObject): pdi = plotStyle.createPlotItem(self.plot2D) assert isinstance(pdi, TemporalProfilePlotDataItem) - pdi.sigClicked.connect(self.onProfileClicked2D) - pdi.sigPointsClicked.connect(self.onPointsClicked2D) + #pdi.sigClicked.connect(self.onProfileClicked2D) + #pdi.sigPointsClicked.connect(self.onPointsClicked2D) self.plot2D.plotItem.addItem(pdi) #self.plot2D.getPlotItem().addItem(pg.PlotDataItem(x=[1, 2, 3], y=[1, 2, 3])) #plotItem.addDataItem(pdi) @@ -1592,38 +1573,40 @@ class SpectralTemporalVisualization(QObject): self.plot3D.addItems(plotItems) #self.updatePlot3D() - def onProfileClicked2D(self, pdi): - if isinstance(pdi, TemporalProfilePlotDataItem): - sensor = pdi.mPlotStyle.sensor() - tp = pdi.mPlotStyle.temporalProfile() - if isinstance(tp, TemporalProfile) and isinstance(sensor, SensorInstrument): - c = tp.coordinate() - info = ['Sensor:{}'.format(sensor.name()), - 'Coordinate:{}, {}'.format(c.x(), c.y())] - self.ui.tbInfo2D.setPlainText('\n'.join(info)) - - - def onPointsClicked2D(self, pdi, spottedItems): + def onPointsClicked2D(self, event: MouseClickEvent): info = [] - if isinstance(pdi, TemporalProfilePlotDataItem) and isinstance(spottedItems, list): - sensor = pdi.mPlotStyle.sensor() - tp = pdi.mPlotStyle.temporalProfile() - if isinstance(tp, TemporalProfile) and isinstance(sensor, SensorInstrument): + assert isinstance(event, MouseClickEvent) + for item in self.plot2D.scene().itemsNearEvent(event): + + if isinstance(item, ScatterPlotItem) and isinstance(item.parentItem(), TemporalProfilePlotDataItem): + pdi = item.parentItem() + assert isinstance(pdi, TemporalProfilePlotDataItem) + tp = pdi.mPlotStyle.temporalProfile() + assert isinstance(tp, TemporalProfile) c = tp.coordinate() - info.append('Sensor: {}'.format(sensor.name())) - info.append('Coordinate: {}, {}'.format(c.x(), c.y())) - for item in spottedItems: - pos = item.pos() - x = pos.x() - y = pos.y() - date = num2date(x) - info.append('Date: {}\nValue: {}'.format(date, y)) + spottedItems = item.pointsAt(event.pos()) + if len(spottedItems) > 0: + info.append('Sensor: {}'.format(pdi.mPlotStyle.sensor().name())) + info.append('Coordinate: {}, {}'.format(c.x(), c.y())) + for item in spottedItems: + if isinstance(item, SpotItem): + brush1 = item.brush() + brush2 = item.brush() + brush2.setColor(QColor('yellow')) + item.setBrush(brush2) + QTimer.singleShot(500, lambda *args, spotItem=item, brush=brush1: spotItem.setBrush(brush)) + pos = item.pos() + self.mLast2DMouseClickPosition = pos + x = pos.x() + y = pos.y() + date = num2date(x) + info.append('{};{}'.format(date, y)) + + self.ui.tbInfo2D.setPlainText('\n'.join(info)) - else: - self.ui.tbInfo2D.setPlainText('\n'.join(info)) def onTemporalProfilesAdded(self, profiles): # self.mTemporalProfileLayer.prune() @@ -1669,7 +1652,7 @@ class SpectralTemporalVisualization(QObject): self.ui.actionReset3DCamera.triggered.connect(self.reset3DCamera) self.ui.actionLoadTPFromOgr.triggered.connect(self.onLoadFromVector) self.ui.actionLoadMissingValues.triggered.connect(lambda: self.loadMissingData()) - self.ui.actionSaveTemporalProfiles.triggered.connect(lambda *args : self.mTemporalProfileLayer.saveTemporalProfiles) + self.ui.actionSaveTemporalProfiles.triggered.connect(lambda *args : self.mTemporalProfileLayer.saveTemporalProfiles(None)) #set actions to be shown in the TemporalProfileTableView context menu ma = [self.ui.actionSaveTemporalProfiles, self.ui.actionLoadMissingValues] @@ -1732,23 +1715,39 @@ class SpectralTemporalVisualization(QObject): self.sigMoveToTSD.emit(self.TS[i]) - def onPixelLoaded(self, d): + def onPixelLoaded(self, qgsTask ,dump)->typing.List[TemporalProfile]: + """ + Updates TemporalProfiles + :param qgsTask: + :param dump: + :return: [updated TemporalProfiles] + """ + tasks = pickle.loads(dump) + assert isinstance(tasks, list) + s = "" + t0 = time.time() + updatedTemporalProfiles = [] + for task in tasks: + assert isinstance(task, TemporalProfileLoaderTask) + + if len(task.mRESULTS) > 0: + for tpId, data in task.mRESULTS.items(): + tp = self.mTemporalProfileLayer.mProfiles.get(tpId) - if isinstance(d, PixelLoaderTask): + if isinstance(tp, TemporalProfile): + tsd = tp.timeSeries().getTSD(task.mSourcePath) + if isinstance(tsd, TimeSeriesDate): + tp.updateData(tsd, data) + if tp not in updatedTemporalProfiles: + updatedTemporalProfiles.append(tp) - bn = os.path.basename(d.sourcePath) - if d.success(): + if len(task.mERRORS) > 0: - t = 'Loaded {} pixel from {}.'.format(len(d.resProfiles), bn) - self.mTemporalProfileLayer.addPixelLoaderResult(d) - self.updateRequested = True - else: - t = 'Failed loading from {}.'.format(bn) - if d.info and d.info != '': - t += '({})'.format(d.info) - # QgsApplication.processEvents() - self.ui.progressInfo.setText(t) + s = "" + return updatedTemporalProfiles + if False: + print('WRESULTS {}'.format(time.time() - t0)) def requestUpdate(self, *args): self.updateRequested = True @@ -1778,13 +1777,7 @@ class SpectralTemporalVisualization(QObject): self.ui.tableView3DProfiles.openPersistentEditor(idxExpr) start += 1 - def onObservationClicked(self, plotDataItem, points): - for p in points: - tsd = p.data() - #print(tsd) - - - def loadMissingData(self, backgroundProcess=False): + def loadMissingData(self, backgroundProcess=True): """ Loads all band values of collected locations that have not been loaded until now """ @@ -1810,9 +1803,6 @@ class SpectralTemporalVisualization(QObject): :param spatialPoints: SpatialPoint | [list-of-SpatialPoints] """ assert mode in SpectralTemporalVisualization.LOADING_MODES - - - if isinstance(spatialPoints, SpatialPoint): spatialPoints = [spatialPoints] @@ -1827,10 +1817,8 @@ class SpectralTemporalVisualization(QObject): assert isinstance(self.TS, TimeSeries) # Get or create the TimeSeriesProfiles which will store the loaded values - tasks = [] - TPs = [] - theGeometries = [] + TemporalProfiles = [] # Define which (new) bands need to be loaded for each sensor if LUT_bandIndices is None: @@ -1847,14 +1835,11 @@ class SpectralTemporalVisualization(QObject): # update new / existing points - - - for spatialPoint in spatialPoints: assert isinstance(spatialPoint, SpatialPoint) TP = self.mTemporalProfileLayer.fromSpatialPoint(spatialPoint) - # if no TemporaProfile existed before, create an empty one + # if no TemporalProfile existed before, create an empty one if not isinstance(TP, TemporalProfile): TP = self.mTemporalProfileLayer.createTemporalProfiles(spatialPoint)[0] @@ -1876,20 +1861,8 @@ class SpectralTemporalVisualization(QObject): if len(self.plotSettingsModel3D) == 0: self.createNewPlotStyle3D() + TemporalProfiles.append(TP) - - TPs.append(TP) - theGeometries.append(TP.coordinate()) - - if len(theGeometries) == 0: - return - - bbox = QgsRectangle() - for i, g in enumerate(theGeometries): - bbox.include(g) - - - TP_ids = [TP.id() for TP in TPs] # each TSD is a Task s = "" # a Task defines which bands are to be loaded @@ -1908,7 +1881,7 @@ class SpectralTemporalVisualization(QObject): if mode == 'missing': missingIndices = set() - for TP in TPs: + for TP in TemporalProfiles: assert isinstance(TP, TemporalProfile) need2load = TP.missingBandIndices(tsd, requiredIndices=requiredIndices) missingIndices = missingIndices.union(need2load) @@ -1920,34 +1893,58 @@ class SpectralTemporalVisualization(QObject): if len(missingIndices) > 0: for tss in tsd.sources(): assert isinstance(tss, TimeSeriesSource) - if bbox.intersects(tss.spatialExtent().toCrs(TPs[0].coordinate().crs())): - task = PixelLoaderTask(tss.uri(), theGeometries, - # bandIndices=missingIndices, load all indices! - temporalProfileIDs=TP_ids) + + intersectingTPs = [] + tssExtent = tss.spatialExtent() + for TP in TemporalProfiles: + assert isinstance(TP, TemporalProfile) + if tssExtent.contains(TP.coordinate().toCrs(tssExtent.crs())): + intersectingTPs.append(TP) + + if len(intersectingTPs) > 0: + task = TemporalProfileLoaderTask(tss, intersectingTPs) tasks.append(task) - break + # pickle.loads(doLoaderTask(mock, pickle.dumps([t])))[0] if len(tasks) > 0: - - if DEBUG: - print('Start loading for {} geometries from {} sources...'.format( - len(theGeometries), len(tasks) - )) - if backgroundProcess: - self.pixelLoader.startLoading(tasks) - else: - import eotimeseriesviewer.pixelloader - tasks = [PixelLoaderTask.fromDump(eotimeseriesviewer.pixelloader.doLoaderTask(None, task.toDump())) for task in tasks] - l = len(tasks) - for i, task in enumerate(tasks): - self.pixelLoader.sigPixelLoaded.emit(task) + self.loadTemporalProfileTasks(tasks, runAsync=backgroundProcess) else: if DEBUG: print('Data for geometries already loaded') + def loadTemporalProfileTasks(self, tasks:typing.Iterable[TemporalProfileLoaderTask], runAsync=True)->typing.List[TemporalProfile]: + """ + Loads data into TemporalProfiles + :param tasks: + :param runAsync: + :return: [list-of-updated TemporalProfiles], empty if runAsyn + """ + dump = pickle.dumps(tasks) + if runAsync: + qgsTask = QgsTask.fromFunction('Load Profiles', doLoadTemporalProfileTasks, dump, + on_finished=self.onPixelLoaded) + else: + qgsTask = TaskMock() + + tid = id(qgsTask) + qgsTask.progressChanged.connect(self.onLoadingProgressChanged) + qgsTask.taskCompleted.connect(lambda *args, tid=tid: self.onRemoveTask(tid)) + qgsTask.taskTerminated.connect(lambda *args, tid=tid: self.onRemoveTask(tid)) + self.mTasks[tid] = qgsTask + + if runAsync: + tm = QgsApplication.taskManager() + assert isinstance(tm, QgsTaskManager) + tm.addTask(qgsTask, 1000) + return [] + else: + return self.onPixelLoaded(qgsTask, doLoadTemporalProfileTasks(qgsTask, dump)) + def onRemoveTask(self, tid): + if tid in self.mTasks.keys(): + del self.mTasks[tid] @QtCore.pyqtSlot() def onDataUpdate(self): diff --git a/eotimeseriesviewer/sensorvisualization.py b/eotimeseriesviewer/sensorvisualization.py index 5994a3b3af891a78688a8e3b962735bdee2738ef..7bda62c15e72f968cfff748aa2c8830a98bd646e 100644 --- a/eotimeseriesviewer/sensorvisualization.py +++ b/eotimeseriesviewer/sensorvisualization.py @@ -231,7 +231,7 @@ class SensorTableModel(QAbstractTableModel): if orientation == Qt.Horizontal and role == Qt.DisplayRole: return self.mColumNames[col] elif orientation == Qt.Vertical and role == Qt.DisplayRole: - return col + return col + 1 return None diff --git a/eotimeseriesviewer/settings.py b/eotimeseriesviewer/settings.py index 81b5bf9e17fe4b89d785d0713e3f9565a7f9855e..87078fe700ec2bdbd8ff67519052c6f8cb1b2e3e 100644 --- a/eotimeseriesviewer/settings.py +++ b/eotimeseriesviewer/settings.py @@ -1,5 +1,6 @@ -import os, enum, pathlib, re +import os, enum, pathlib, re, json +from collections import namedtuple from qgis.core import * from qgis.gui import * from qgis.PyQt.QtCore import * @@ -8,6 +9,9 @@ from qgis.PyQt.QtGui import * from eotimeseriesviewer import * from eotimeseriesviewer.utils import loadUI +from eotimeseriesviewer.timeseries import SensorMatching, SensorInstrument + + class Keys(enum.Enum): @@ -19,13 +23,15 @@ class Keys(enum.Enum): MapUpdateInterval = 'map_update_interval' MapBackgroundColor = 'map_background_color' MapTextFormat = 'map_text_format' - SensorNames = 'sensor_names' + SensorSpecs = 'sensor_specs' + SensorMatching = 'sensor_matching' ScreenShotDirectory = 'screen_shot_directory' RasterSourceDirectory = 'raster_source_directory' VectorSourceDirectory = 'vector_source_directory' MapImageExportDirectory = 'map_image_export_directory' + def defaultValues() -> dict: """ Returns the official hard-coded dictionary of default values. @@ -40,7 +46,8 @@ def defaultValues() -> dict: d[Keys.RasterSourceDirectory] = str(home) d[Keys.VectorSourceDirectory] = str(home) d[Keys.DateTimePrecision] = DateTimePrecision.Day - d[Keys.SensorNames] = dict() + d[Keys.SensorSpecs] = dict() + d[Keys.SensorMatching] = SensorMatching.DIMS_WL_Name # map visualization d[Keys.MapUpdateInterval] = 500 # milliseconds @@ -100,6 +107,14 @@ def value(key:Keys, default=None): if value and key == Keys.MapTextFormat: s = "" + if isinstance(value, str) and key == Keys.SensorSpecs: + value = json.loads(value) + assert isinstance(value, dict) + for k in value.keys(): + if not isinstance(value[k], dict): + value[k] = {'name': None} + + except TypeError as error: value = None settings().setValue(key.value, None) @@ -107,6 +122,41 @@ def value(key:Keys, default=None): return value +def saveSensorName(sensor:SensorInstrument): + """ + Saves the sensor name + :param sensor: SensorInstrument + :return: + """ + assert isinstance(sensor, SensorInstrument) + + sensorSpecs = value(Keys.SensorSpecs, default=dict()) + assert isinstance(sensorSpecs, dict) + + sSpecs = sensorSpecs.get(sensor.id(), dict()) + sSpecs['name'] = sensor.name() + + sensorSpecs[sensor.id()] = sSpecs + + setValue(Keys.SensorSpecs, sensorSpecs) + +def sensorName(id:str)->str: + """ + Retuns the sensor name stored for a certain sensor id + :param id: str + :return: str + """ + if isinstance(id, SensorInstrument): + id = id.id() + + sensorSpecs = value(Keys.SensorSpecs, default=dict()) + assert isinstance(sensorSpecs, dict) + sSpecs = sensorSpecs.get(id, dict()) + return sSpecs.get('name', None) + + + + def setValue(key:Keys, value): """ Shortcut to save a value into the EOTSV settings @@ -118,6 +168,9 @@ def setValue(key:Keys, value): if isinstance(value, QgsTextFormat): value = value.toMimeData() + if isinstance(value, dict) and key == Keys.SensorSpecs: + settings().setValue(key.value, json.dumps(value)) + settings().setValue(key.value, value) @@ -132,6 +185,148 @@ def setValues(values: dict): for key, val in values.items(): setValue(key, val) +class SensorSettingsTableModel(QAbstractTableModel): + """ + A table to visualize sensor-specific settings + """ + def __init__(self): + super(SensorSettingsTableModel, self).__init__() + + self.mSensors = [] + self.mCNKey = 'Key' + self.mCNName = 'Name' + self.loadSettings() + + def clear(self): + """Removes all entries""" + self.removeRows(0, self.rowCount()) + assert len(self.mSensors) == 0 + + def reload(self): + """ + Reloads the entire table + :return: + """ + self.clear() + self.loadSettings() + + def removeRows(self, row: int, count: int, parent: QModelIndex = QModelIndex()) -> bool: + + if count > 0: + self.beginRemoveRows(parent, row, row+count-1) + + for i in reversed(range(row, row+count)): + del self.mSensors[i] + + self.endRemoveRows() + + def loadSettings(self): + sensorSpecs = value(Keys.SensorSpecs, default={}) + + sensors = [] + for id, specs in sensorSpecs.items(): + sensor = SensorInstrument(id) + sensor.setName(specs['name']) + sensors.append(sensor) + self.addSensors(sensors) + + def removeSensors(self, sensors:typing.List[SensorInstrument]): + assert isinstance(sensors, list) + n = len(sensors) + + if n > 0: + self.beginInsertRows(QModelIndex(), self.rowCount() - 1, self.rowCount() - 1 + n) + self.mSensors.extend(sensors) + self.endInsertRows() + + + def addSensors(self, sensors:typing.List[SensorInstrument]): + assert isinstance(sensors, list) + n = len(sensors) + + if n > 0: + self.beginInsertRows(QModelIndex(), self.rowCount()-1, self.rowCount() -1 + n) + self.mSensors.extend(sensors) + self.endInsertRows() + + def saveSettings(self): + + specs = dict() + for sensor in self.mSensors: + assert isinstance(sensor, SensorInstrument) + specs[sensor.id()] = sensor.name() + setValue(Keys.SensorSpecs, specs) + + def rowCount(self, parent: QModelIndex = QModelIndex())->int: + return len(self.mSensors) + + def columnNames(self)->typing.List[str]: + return [self.mCNKey, self.mCNName] + + def columnCount(self, parent: QModelIndex): + return len(self.columnNames()) + + def flags(self, index: QModelIndex): + flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable + cn = self.columnNames()[index.column()] + if cn == self.mCNName: + flags = flags | Qt.ItemIsEditable + + return flags + + def headerData(self, section, orientation, role): + assert isinstance(section, int) + if orientation == Qt.Horizontal and role == Qt.DisplayRole: + return self.columnNames()[section] + elif orientation == Qt.Vertical and role == Qt.DisplayRole: + return section + 1 + else: + return None + + def sensor(self, index)->SensorInstrument: + if isinstance(index, int): + return self.mSensors[index] + else: + return self.mSensors[index.row()] + + def data(self, index: QModelIndex, role: int): + + if not index.isValid(): + return None + + sensor = self.sensor(index) + cn = self.columnNames()[index.column()] + + if role in [Qt.DisplayRole, Qt.EditRole]: + if cn == self.mCNName: + return sensor.name() + if cn == self.mCNKey: + return sensor.id() + + if role == Qt.BackgroundColorRole and not (self.flags(index) & Qt.ItemIsEditable): + return QColor('gray') + + + return None + + def setData(self, index: QModelIndex, value: typing.Any, role: int = ...) -> bool: + + if not index.isValid(): + return False + + changed = False + sensor = self.sensor(index) + cn = self.columnNames()[index.column()] + + if cn == self.mCNName and isinstance(value, str): + sensor.setName(value) + changed = True + + if changed: + self.dataChanged.emit(index, index, [role]) + return changed + + class SettingsDialog(QDialog, loadUI('settingsdialog.ui')): """ @@ -148,11 +343,16 @@ class SettingsDialog(QDialog, loadUI('settingsdialog.ui')): assert isinstance(e, enum.Enum) self.cbDateTimePrecission.addItem(e.name, e) + for e in SensorMatching: + assert isinstance(e, enum.Enum) + self.cbSensorMatching.addItem(e.value, e) + self.mFileWidgetScreenshots.setStorageMode(QgsFileWidget.GetDirectory) self.mFileWidgetRasterSources.setStorageMode(QgsFileWidget.GetDirectory) self.mFileWidgetVectorSources.setStorageMode(QgsFileWidget.GetDirectory) self.cbDateTimePrecission.currentIndexChanged.connect(self.validate) + self.cbSensorMatching.currentIndexChanged.connect(self.validate) self.sbMapSizeX.valueChanged.connect(self.validate) self.sbMapSizeY.valueChanged.connect(self.validate) self.sbMapRefreshIntervall.valueChanged.connect(self.validate) @@ -166,18 +366,49 @@ class SettingsDialog(QDialog, loadUI('settingsdialog.ui')): self.mLastValues = dict() + self.mSensorSpecsModel = SensorSettingsTableModel() + self.mSensorSpecsProxyModel = QSortFilterProxyModel() + self.mSensorSpecsProxyModel.setSourceModel(self.mSensorSpecsModel) + + self.tableViewSensorSettings.setModel(self.mSensorSpecsProxyModel) + sm = self.tableViewSensorSettings.selectionModel() + assert isinstance(sm, QItemSelectionModel) + sm.selectionChanged.connect(self.onSensorSettingsSelectionChanged) + + self.btnDeleteSelectedSensors.setDefaultAction(self.actionDeleteSelectedSensors) + self.btnReloadSensorSettings.setDefaultAction(self.actionRefreshSensorList) + self.actionRefreshSensorList.triggered.connect(self.mSensorSpecsModel.reload) + + self.actionDeleteSelectedSensors.triggered.connect(self.onRemoveSelectedSensors) + self.actionDeleteSelectedSensors.setEnabled(len(sm.selectedRows()) > 0) + self.mSensorSpecsModel.clear() self.setValues(settings()) + + def onRemoveSelectedSensors(self): + + sm = self.tableViewSensorSettings.selectionModel() + assert isinstance(sm, QItemSelectionModel) + + for r in sm.selectedRows(): + s = "" + + def onSensorSettingsSelectionChanged(self, selected:QItemSelection, deselected:QItemSelection): + self.actionDeleteSelectedSensors.setEnabled(len(selected) > 0) + def validate(self, *args): values = self.values() def onAccept(self): - pass + self.setResult(QDialog.Accepted) values = self.values() setValues(values) + + self.mSensorSpecsModel.saveSettings() + if values != self.mLastValues: pass @@ -194,6 +425,7 @@ class SettingsDialog(QDialog, loadUI('settingsdialog.ui')): d[Keys.VectorSourceDirectory] = self.mFileWidgetVectorSources.filePath() d[Keys.DateTimePrecision] = self.cbDateTimePrecission.currentData() + d[Keys.SensorMatching] = self.cbSensorMatching.currentData() d[Keys.MapSize] = QSize(self.sbMapSizeX.value(), self.sbMapSizeY.value()) d[Keys.MapUpdateInterval] = self.sbMapRefreshIntervall.value() d[Keys.MapBackgroundColor] = self.mCanvasColorButton.color() @@ -210,7 +442,10 @@ class SettingsDialog(QDialog, loadUI('settingsdialog.ui')): if isinstance(values, QSettings): d = dict() for k in values.allKeys(): - d[k] = values.value(k) + try: + d[k] = values.value(k) + except Exception as ex: + s = "" #TypeError: unable to convert a QVariant back to a Python object values = d assert isinstance(values, dict) @@ -232,6 +467,11 @@ class SettingsDialog(QDialog, loadUI('settingsdialog.ui')): if i > -1: self.cbDateTimePrecission.setCurrentIndex(i) + if checkKey(key, Keys.SensorMatching): + i = self.cbSensorMatching.findData(value) + if i > -1: + self.cbSensorMatching.setCurrentIndex(i) + if checkKey(key, Keys.MapSize) and isinstance(value, QSize): self.sbMapSizeX.setValue(value.width()) self.sbMapSizeY.setValue(value.height()) @@ -247,5 +487,3 @@ class SettingsDialog(QDialog, loadUI('settingsdialog.ui')): - - diff --git a/eotimeseriesviewer/stackedbandinput.py b/eotimeseriesviewer/stackedbandinput.py index c6ff847a920f196f2ea1b2e4b7915399f3fb3461..347a698c0bc6aa29ba5e897444834980b5e197be 100644 --- a/eotimeseriesviewer/stackedbandinput.py +++ b/eotimeseriesviewer/stackedbandinput.py @@ -38,10 +38,17 @@ def datesFromDataset(dataset:gdal.Dataset)->list: return False return True - searchedKeys = [] - searchedKeys.append(re.compile('acquisition[ ]*dates$', re.I)) - searchedKeys.append(re.compile('observation[ ]*dates$', re.I)) - searchedKeys.append(re.compile('wavelength$', re.I)) + searchedKeysDataSet = [] + searchedKeysDataSet.append(re.compile('acquisition[ ]*dates$', re.I)) + searchedKeysDataSet.append(re.compile('observation[ ]*dates$', re.I)) + searchedKeysDataSet.append(re.compile('dates$', re.I)) + searchedKeysDataSet.append(re.compile('wavelength$', re.I)) + + searchedKeysBand = [] + searchedKeysBand.append(re.compile('acquisition[ ]*date$', re.I)) + searchedKeysBand.append(re.compile('observation[ ]*date$', re.I)) + searchedKeysBand.append(re.compile('date$', re.I)) + searchedKeysBand.append(re.compile('wavelength$', re.I)) #1. Check Metadata for domain in dataset.GetMetadataDomainList(): @@ -49,7 +56,7 @@ def datesFromDataset(dataset:gdal.Dataset)->list: assert isinstance(domainData, dict) for key, values in domainData.items(): - for regex in searchedKeys: + for regex in searchedKeysDataSet: if regex.search(key.strip()): values = re.sub('[{}]', '', values) values = values.split(',') @@ -58,12 +65,46 @@ def datesFromDataset(dataset:gdal.Dataset)->list: return dateValues - #2. Check Band Names + # 2. Search in band metadata + # 2.1. via GetDescription bandDates = [extractDateTimeGroup(dataset.GetRasterBand(b+1).GetDescription()) for b in range(nb)] bandDates = [b for b in bandDates if isinstance(b, np.datetime64)] if checkDates(bandDates): return bandDates + # 2.2 via Band Metadata + bandDates = [] + for b in range(nb): + band = dataset.GetRasterBand(b+1) + assert isinstance(band, gdal.Band) + bandDate = None + for domain in band.GetMetadataDomainList(): + + md = band.GetMetadata_Dict(domain) + + candidates = [] + for k in md.keys(): + for rx in searchedKeysBand: + if rx.search(k): + candidates.append(k) + + for key in candidates: + assert isinstance(key, str) + DTG = extractDateTimeGroup(md[key]) + if isinstance(DTG, np.datetime64): + bandDate = DTG + break + + if isinstance(bandDate, np.datetime64): + break + + if isinstance(bandDate, np.datetime64): + bandDates.append(bandDate) + + if checkDates(bandDates): + return bandDates + + return [] class InputStackInfo(object): @@ -109,6 +150,7 @@ class InputStackInfo(object): self.bandnames = [] self.nodatavalues = [] + for b in range(self.nb): band = dataset.GetRasterBand(b+1) assert isinstance(band, gdal.Band) diff --git a/eotimeseriesviewer/temporalprofiles2d.py b/eotimeseriesviewer/temporalprofiles.py similarity index 84% rename from eotimeseriesviewer/temporalprofiles2d.py rename to eotimeseriesviewer/temporalprofiles.py index eef24c79c9fc54bd111e5831d0d9cba31d4c8a59..0bdbf343c9f305c1c5748e353e040cbea2a0cc95 100644 --- a/eotimeseriesviewer/temporalprofiles2d.py +++ b/eotimeseriesviewer/temporalprofiles.py @@ -31,14 +31,15 @@ from qgis.PyQt.QtWidgets import * import numpy as np from osgeo import ogr, osr, gdal from .externals import pyqtgraph as pg -from .externals.pyqtgraph import functions as fn, AxisItem +from .externals.pyqtgraph import functions as fn, AxisItem, ScatterPlotItem, SpotItem, GraphicsScene from .externals.qps.plotstyling.plotstyling import PlotStyle -from .timeseries import TimeSeries, TimeSeriesDate, SensorInstrument +from .timeseries import TimeSeries, TimeSeriesDate, SensorInstrument, TimeSeriesSource from .pixelloader import PixelLoader, PixelLoaderTask from .utils import * from .externals.qps.speclib.spectrallibraries import createQgsField + LABEL_EXPRESSION_2D = 'DN or Index' LABEL_TIME = 'Date' DEBUG = False @@ -50,15 +51,25 @@ FN_ID = 'fid' FN_X = 'x' FN_Y = 'y' FN_NAME = 'name' + +FN_DOY = 'DOY' +FN_DTG = 'DTG' +FN_IS_NODATA ='is_nodata' +FN_GEO_X = 'geo_x' +FN_GEO_Y = 'geo_y' +FN_PX_X = 'px_x' +FN_PX_Y = 'px_y' + #FN_N_TOTAL = 'n' #FN_N_NODATA = 'no_data' #FN_N_LOADED = 'loaded' #FN_N_LOADED_PERCENT = 'percent' - regBandKey = re.compile(r"(?<!\w)b\d+(?!\w)", re.IGNORECASE) regBandKeyExact = re.compile(r'^' + regBandKey.pattern + '$', re.IGNORECASE) + + try: import OpenGL @@ -69,36 +80,58 @@ except: -def sensorExampleQgsFeature(sensor, singleBandOnly=False): - # populate with exemplary band values (generally stored as floats) +def temporalProfileFeatureFields(sensor: SensorInstrument, singleBandOnly=False) -> QgsFields: + """ + Returns the fields of a single temporal profile + :return: + """ + assert isinstance(sensor, SensorInstrument) + fields = QgsFields() + fields.append(createQgsField(FN_DTG, '2011-09-12', comment='Date-time-group')) + fields.append(createQgsField(FN_DOY, 42, comment='Day-of-year')) + fields.append(createQgsField(FN_GEO_X, 12.1233, comment='geo-coordinate x/east value')) + fields.append(createQgsField(FN_GEO_Y, 12.1233, comment='geo-coordinate y/north value')) + fields.append(createQgsField(FN_PX_X, 42, comment='pixel-coordinate x index')) + fields.append(createQgsField(FN_PX_Y, 24, comment='pixel-coordinate y index')) - if sensor is None: - singleBandOnly = True - fieldValues = collections.OrderedDict() - if singleBandOnly: - fieldValues['b'] = 1.0 - else: - assert isinstance(sensor, SensorInstrument) - for b in range(sensor.nb): - fn = bandIndex2bandKey(b) - fieldValues[fn] = 1.0 + for b in range(sensor.nb): + bandKey = bandIndex2bandKey(b) + fields.append(createQgsField(bandKey, 1.0, comment='value band {}'.format(b+1))) - date = datetime.date.today() - doy = dateDOY(date) - fieldValues['doy'] = doy - fieldValues['date'] = str(date) + return fields +def sensorExampleQgsFeature(sensor:SensorInstrument, singleBandOnly=False)->QgsFeature: + """ + Returns an exemplary QgsFeature with value for a specific sensor + :param sensor: SensorInstrument + :param singleBandOnly: + :return: + """ + # populate with exemplary band values (generally stored as floats) - fields = QgsFields() - for k, v in fieldValues.items(): - fields.append(createQgsField(k,v)) + fields = temporalProfileFeatureFields(sensor) f = QgsFeature(fields) - for k, v in fieldValues.items(): - f.setAttribute(k, v) + pt = QgsPointXY(12.34567, 12.34567) + f.setGeometry(QgsGeometry.fromPointXY(pt)) + f.setAttribute(FN_GEO_X, pt.x()) + f.setAttribute(FN_GEO_Y, pt.y()) + f.setAttribute(FN_PX_X, 1) + f.setAttribute(FN_PX_Y, 1) + dtg = datetime.date.today() + doy = dateDOY(dtg) + f.setAttribute(FN_DTG, str(dtg)) + f.setAttribute(FN_DOY, doy) + + for b in range(sensor.nb): + bandKey = bandIndex2bandKey(b) + f.setAttribute(bandKey, 1.0) + return f + + def dateDOY(date): if isinstance(date, np.datetime64): date = date.astype(datetime.date) @@ -179,6 +212,7 @@ class DateTimePlotWidget(pg.PlotWidget): """ Subclass of PlotWidget """ + def __init__(self, parent=None): """ Constructor of the widget @@ -208,12 +242,14 @@ class DateTimePlotWidget(pg.PlotWidget): pi.addItem(self.mCrosshairLineV, ignoreBounds=True) pi.addItem(self.mCrosshairLineH, ignoreBounds=True) + assert isinstance(self.scene(), pg.GraphicsScene) self.proxy2D = pg.SignalProxy(self.scene().sigMouseMoved, rateLimit=60, slot=self.onMouseMoved2D) def resetViewBox(self): self.plotItem.getViewBox().autoRange() + def onMouseMoved2D(self, evt): pos = evt[0] ## using signal proxy turns original arguments into a tuple @@ -328,6 +364,7 @@ class DateTimeViewBox(pg.ViewBox): Subclass of ViewBox """ sigMoveToDate = pyqtSignal(np.datetime64) + sigMoveToLocation = pyqtSignal(SpatialPoint) def __init__(self, parent=None): """ Constructor of the CustomViewBox @@ -337,6 +374,7 @@ class DateTimeViewBox(pg.ViewBox): #self.menu = self.getMenu() # Create the menu #self.menu = None self.mCurrentDate = np.datetime64('today') + self.mXAxisUnit = 'date' xAction = [a for a in self.menu.actions() if a.text() == 'X Axis'][0] yAction = [a for a in self.menu.actions() if a.text() == 'Y Axis'][0] @@ -382,7 +420,11 @@ class DateTimeViewBox(pg.ViewBox): self.menu.removeAction(xAction) self.mActionMoveToDate = self.menu.addAction('Move to {}'.format(self.mCurrentDate)) - self.mActionMoveToDate.triggered.connect(lambda : self.sigMoveToDate.emit(self.mCurrentDate)) + self.mActionMoveToDate.triggered.connect(lambda *args : self.sigMoveToDate.emit(self.mCurrentDate)) + + #self.mActionMoveToProfile = self.menu.addAction('Move to profile location') + #self.mActionMoveToProfile.triggered.connect(lambda *args: self.sigM.emit(self.mCurrentDate)) + self.mActionShowCrosshair = self.menu.addAction('Show Crosshair') self.mActionShowCrosshair.setCheckable(True) self.mActionShowCrosshair.setChecked(True) @@ -429,6 +471,9 @@ class DateTimeViewBox(pg.ViewBox): pt = self.mapDeviceToView(ev.pos()) self.updateCurrentDate(num2date(pt.x(), dt64=True)) + plotDataItems = [item for item in self.scene().itemsNearEvent(ev) if isinstance(item, ScatterPlotItem) and isinstance(item.parentItem(), TemporalProfilePlotDataItem)] + + xRange, yRange = self.viewRange() t0 = num2date(xRange[0], qDate=True) t1 = num2date(xRange[1], qDate=True) @@ -436,158 +481,28 @@ class DateTimeViewBox(pg.ViewBox): self.dateEditX1.setDate(t1) menu = self.getMenu(ev) - self.scene().addParentContextMenus(self, menu, ev) - menu.exec_(ev.screenPos().toPoint()) - - -class TemporalProfilePlotStyleBase(PlotStyle): - - sigStyleUpdated = pyqtSignal() - sigDataUpdated = pyqtSignal() - sigExpressionUpdated = pyqtSignal() - sigSensorChanged = pyqtSignal(SensorInstrument) - - def __init__(self, parent=None, temporalProfile=None): - super(TemporalProfilePlotStyleBase, self).__init__() - self.mSensor = None - self.mTP = None - self.mExpression = 'b1' - self.mPlotItems = [] - self.mIsVisible = True - self.mShowLastLocation = True - - if isinstance(temporalProfile, TemporalProfile): - self.setTemporalProfile(temporalProfile) - - def showLastLocation(self)->bool: - """ - """ - return self.mShowLastLocation - - def isPlotable(self): - return self.isVisible() and isinstance(self.temporalProfile(), TemporalProfile) and isinstance(self.sensor(), SensorInstrument) - - def createPlotItem(self): - raise NotImplementedError() - - def temporalProfile(self): - return self.mTP - - def setTemporalProfile(self, temporalPofile): - - b = temporalPofile != self.mTP - self.mTP = temporalPofile - if temporalPofile in [None, QVariant()]: - s ="" - else: - assert isinstance(temporalPofile, TemporalProfile) - if b: - self.updateDataProperties() - - def setSensor(self, sensor): - assert sensor is None or isinstance(sensor, SensorInstrument) - b = sensor != self.mSensor - self.mSensor = sensor - if b: - self.update() - self.sigSensorChanged.emit(sensor) - - def sensor(self): - return self.mSensor - - def updateStyleProperties(self): - raise NotImplementedError() - - def updateDataProperties(self): - raise NotImplementedError() - - def update(self): - self.updateDataProperties() - - def setExpression(self, exp): - assert isinstance(exp, str) - b = self.mExpression != exp - self.mExpression = exp - self.updateDataProperties() - if b: - self - self.sigExpressionUpdated.emit() - - def expression(self): - return self.mExpression - - def __reduce_ex__(self, protocol): - return self.__class__, (), self.__getstate__() - - def __getstate__(self): - result = super(TemporalProfile2DPlotStyle, self).__getstate__() - #remove - del result['mTP'] - del result['mSensor'] - - return result - - def isVisible(self): - return self.mIsVisible - - def setVisibility(self, b): - assert isinstance(b, bool) - old = self.isVisible() - self.mIsVisible = b - - if b != old: - self.updateStyleProperties() - #self.update() - - def copyFrom(self, plotStyle): - if isinstance(plotStyle, PlotStyle): - super(TemporalProfilePlotStyleBase, self).copyFrom(plotStyle) - self.updateStyleProperties() - - if isinstance(plotStyle, TemporalProfilePlotStyleBase): - self.setExpression(plotStyle.expression()) - self.setSensor(plotStyle.sensor()) - self.setTemporalProfile(plotStyle.temporalProfile()) - self.updateDataProperties() - - - - -class TemporalProfile2DPlotStyle(TemporalProfilePlotStyleBase): + if len(plotDataItems) > 0: + s = "" - def __init__(self, temporalProfile=None): - super(TemporalProfile2DPlotStyle, self).__init__(temporalProfile=temporalProfile) - #PlotStyle.__init__(self) - #TemporalProfilePlotStyleBase.__init__(self, temporalProfile=temporalProfile) + self.scene().addParentContextMenus(self, menu, ev) + menu.exec_(ev.screenPos().toPoint()) - def createPlotItem(self, plotWidget): - pdi = TemporalProfilePlotDataItem(self) - self.mPlotItems.append(pdi) - return pdi - - def updateStyleProperties(self): - for pdi in self.mPlotItems: - assert isinstance(pdi, TemporalProfilePlotDataItem) - pdi.updateStyle() - - def updateDataProperties(self): - for pdi in self.mPlotItems: - assert isinstance(pdi, TemporalProfilePlotDataItem) - pdi.updateDataAndStyle() class TemporalProfile(QObject): sigNameChanged = pyqtSignal(str) - sigDataChanged = pyqtSignal() + #sigDataChanged = pyqtSignal() - def __init__(self, layer, fid): + def __init__(self, layer, fid:int, geometry:QgsGeometry): super(TemporalProfile, self).__init__() + assert isinstance(geometry, QgsGeometry) assert isinstance(layer, TemporalProfileLayer) assert fid >= 0 + self.mID = fid self.mLayer = layer self.mTimeSeries = layer.timeSeries() @@ -598,15 +513,26 @@ class TemporalProfile(QObject): for tsd in self.mTimeSeries: assert isinstance(tsd, TimeSeriesDate) - meta = {'doy': tsd.mDOY, - 'date': str(tsd.mDate), - 'nodata': False} + meta = {FN_DOY: tsd.mDOY, + FN_DTG: str(tsd.mDate), + FN_IS_NODATA: False} self.updateData(tsd, meta, skipStatusUpdate=True) - #self.updateLoadingStatus() - s = "" + self.mGEOM_CACHE = dict() + def printData(self, sensor:SensorInstrument=None): + """ + Prints the entire temporal profile. For debug purposes. + """ + for tsd in sorted(self.mData.keys()): + assert isinstance(tsd, TimeSeriesDate) + data = self.mData[tsd] + if isinstance(sensor, SensorInstrument) and tsd.sensor() != sensor: + continue + assert isinstance(data, dict) + info = '{}:{}={}'.format(tsd.date(), tsd.sensor().name(), str(data)) + print(info) def __hash__(self): return hash('{}{}'.format(self.mID, self.mLayer.layerId())) @@ -623,8 +549,24 @@ class TemporalProfile(QObject): return other.mID == self.mID and self.mLayer == other.mLayer - def geometry(self): - return self.mLayer.getFeature(self.mID).geometry() + def geometry(self, crs:QgsCoordinateReferenceSystem=None)->QgsGeometry: + """ + Returns the geometry + :param crs: + :return: QgsGeometry. usually a QgsPoint + """ + g = self.mLayer.getFeature(self.mID).geometry() + + if not isinstance(g, QgsGeometry): + return None + + if isinstance(crs, QgsCoordinateReferenceSystem) and crs != self.mLayer.crs(): + trans = QgsCoordinateTransform() + trans.setSourceCrs(self.mLayer.crs()) + trans.setDestinationCrs(crs) + g.transform(trans) + return g + def coordinate(self)->SpatialPoint: """ @@ -662,58 +604,32 @@ class TemporalProfile(QObject): def timeSeries(self): return self.mTimeSeries - def pullDataUpdate(self, d): - assert isinstance(d, PixelLoaderTask) - if d.success() and self.mID in d.temporalProfileIDs: - i = d.temporalProfileIDs.index(self.mID) - tsd = self.mTimeSeries.getTSD(d.sourcePath) - assert isinstance(tsd, TimeSeriesDate) - - values = {} - if d.validPixelValues(i): - profileData = d.resProfiles[i] - - vMean, vStd = profileData - - validValues = not isinstance(vMean, str) - # 1. add the pixel values per returned band - for iBand, bandIndex in enumerate(d.bandIndices): - key = 'b{}'.format(bandIndex + 1) - values[key] = vMean[iBand] if validValues else None - key = 'std{}'.format(bandIndex + 1) - values[key] = vStd[iBand] if validValues else None - else: - values['nodata'] = True - - self.updateData(tsd, values) - - - def loadMissingData(self, showGUI=False): + def loadMissingData(self): """ - Loads the missing data for this profile. - :return: + Loads the missing data for this profile (synchronous eexecution, may take some time). """ - from eotimeseriesviewer.pixelloader import PixelLoaderTask, doLoaderTask tasks = [] for tsd in self.mTimeSeries: assert isinstance(tsd, TimeSeriesDate) missingIndices = self.missingBandIndices(tsd) if len(missingIndices) > 0: - - for pathImg in tsd.sourceUris(): - - task = PixelLoaderTask(pathImg, [self.coordinate()], - bandIndices=missingIndices, - temporalProfileIDs=[self.mID]) - tasks.append(task) - - - for task in tasks: - result = PixelLoaderTask.fromDump(doLoaderTask(None, task)) - assert isinstance(result, PixelLoaderTask) - self.pullDataUpdate(result) + for tss in tsd: + assert isinstance(tss, TimeSeriesSource) + if tss.spatialExtent().contains(self.coordinate().toCrs(tss.crs())): + task = TemporalProfileLoaderTask(tss, [self], bandIndices=missingIndices) + tasks.append(task) + + results = doLoadTemporalProfileTasks(TaskMock(), pickle.dumps(tasks)) + ts = self.timeSeries() + for result in pickle.loads(results): + assert isinstance(result, TemporalProfileLoaderTask) + tsd = ts.getTSD(result.mSourcePath) + assert isinstance(tsd, TimeSeriesDate) + for tpId, data in result.mRESULTS.items(): + if tpId == self.id(): + self.updateData(tsd, data) def missingBandIndices(self, tsd, requiredIndices=None): """ @@ -726,7 +642,12 @@ class TemporalProfile(QObject): if requiredIndices is None: requiredIndices = list(range(tsd.mSensor.nb)) requiredIndices = [i for i in requiredIndices if i >= 0 and i < tsd.mSensor.nb] + existingBandIndices = [bandKey2bandIndex(k) for k in self.data(tsd).keys() if regBandKeyExact.search(k)] + + if FN_PX_X not in self.data(tsd).keys() and len(requiredIndices) == 0: + requiredIndices.append(0) + return [i for i in requiredIndices if i not in existingBandIndices] @@ -751,13 +672,22 @@ class TemporalProfile(QObject): assert isinstance(values, dict) if tsd not in self.mData.keys(): - self.mData[tsd] = {} + self.mData[tsd] = dict() + + values2 = self.mData.get(tsd) + assert isinstance(values2, dict) + for k, v in values.items(): + if v in [None, np.NaN] and k not in values2.keys(): + values2[k] = v + else: + values2[k] = v + + #self.mData[tsd].update(values) - self.mData[tsd].update(values) if not skipStatusUpdate: #self.updateLoadingStatus() self.mUpdated = True - self.sigDataChanged.emit() + #self.sigDataChanged.emit() def resetUpdatedFlag(self): self.mUpdated = False @@ -765,7 +695,7 @@ class TemporalProfile(QObject): def updated(self): return self.mUpdated - def dataFromExpression(self, sensor, expression:str, dateType='date'): + def dataFromExpression(self, sensor:SensorInstrument, expression:str, dateType='date'): assert dateType in ['date', 'doy'] x = [] y = [] @@ -776,39 +706,52 @@ class TemporalProfile(QObject): assert isinstance(expression, QgsExpression) expression = QgsExpression(expression) - # define required QgsFields - fields = QgsFields() + + sensorTSDs = sorted([tsd for tsd in self.mData.keys() if tsd.sensor() == sensor]) - for tsd in sensorTSDs: - data = self.mData[tsd] - for k, v in data.items(): - if v is not None and fields.indexFromName(k) == -1: - fields.append(createQgsField(k, v)) + + # define required QgsFields + fields = temporalProfileFeatureFields(sensor) + + geo_x = self.geometry().centroid().get().x() + geo_y = self.geometry().centroid().get().y() for i, tsd in enumerate(sensorTSDs): assert isinstance(tsd, TimeSeriesDate) data = self.mData[tsd] + + if dateType == 'date': + xValue = date2num(tsd.mDate) + elif dateType == 'doy': + xValue = tsd.mDOY + context = QgsExpressionContext() context.setFields(fields) - #scope = QgsExpressionContextScope() f = QgsFeature(fields) - for k, v in data.items(): - setQgsFieldValue(f, k, v) + + # set static properties (same for all TSDs) + f.setGeometry(QgsGeometry(self.geometry())) + f.setAttribute(FN_GEO_X, geo_x) + f.setAttribute(FN_GEO_Y, geo_y) + + # set TSD specific properties + f.setAttribute(FN_DOY, tsd.doy()) + f.setAttribute(FN_DTG, str(tsd.date())) + + for fn in fields.names(): + if fn in data.keys(): + setQgsFieldValue(f, fn, data[fn]) context.setFeature(f) - value = expression.evaluate(context) + yValue = expression.evaluate(context) + if yValue in [None, QVariant()]: + yValue = np.NaN - if value in [None, QVariant()]: - s = "" - else: - if dateType == 'date': - x.append(date2num(tsd.mDate)) - elif dateType == 'doy': - x.append(tsd.mDOY) - y.append(value) + y.append(yValue) + x.append(xValue) #return np.asarray(x), np.asarray(y) assert len(x) == len(y) @@ -868,7 +811,7 @@ class TemporalProfile(QObject): def isNoData(self, tsd): assert isinstance(tsd, TimeSeriesDate) - return self.mData[tsd]['nodata'] + return self.mData[tsd][FN_IS_NODATA] def hasData(self, tsd): assert isinstance(tsd, TimeSeriesDate) @@ -878,6 +821,245 @@ class TemporalProfile(QObject): return 'TemporalProfile {} "{}"'.format(self.id(), self.name()) +class TemporalProfilePlotStyleBase(PlotStyle): + + sigStyleUpdated = pyqtSignal() + sigDataUpdated = pyqtSignal() + sigExpressionUpdated = pyqtSignal() + sigSensorChanged = pyqtSignal(SensorInstrument) + + def __init__(self, parent=None, temporalProfile=None): + super(TemporalProfilePlotStyleBase, self).__init__() + self.mSensor = None + self.mTP = None + self.mExpression = 'b1' + self.mPlotItems = [] + self.mIsVisible = True + self.mShowLastLocation = True + + if isinstance(temporalProfile, TemporalProfile): + self.setTemporalProfile(temporalProfile) + + def showLastLocation(self)->bool: + """ + """ + return self.mShowLastLocation + + def isPlotable(self): + return self.isVisible() and isinstance(self.temporalProfile(), TemporalProfile) and isinstance(self.sensor(), SensorInstrument) + + def createPlotItem(self): + raise NotImplementedError() + + def temporalProfile(self): + return self.mTP + + def setTemporalProfile(self, temporalPofile): + + b = temporalPofile != self.mTP + self.mTP = temporalPofile + if temporalPofile in [None, QVariant()]: + s ="" + else: + assert isinstance(temporalPofile, TemporalProfile) + if b: + self.updateDataProperties() + + def setSensor(self, sensor): + assert sensor is None or isinstance(sensor, SensorInstrument) + b = sensor != self.mSensor + self.mSensor = sensor + if b: + self.update() + self.sigSensorChanged.emit(sensor) + + def sensor(self): + return self.mSensor + + def updateStyleProperties(self): + raise NotImplementedError() + + def updateDataProperties(self): + raise NotImplementedError() + + def update(self): + self.updateDataProperties() + + def setExpression(self, exp): + assert isinstance(exp, str) + b = self.mExpression != exp + self.mExpression = exp + self.updateDataProperties() + if b: + self + self.sigExpressionUpdated.emit() + + def expression(self): + return self.mExpression + + def __reduce_ex__(self, protocol): + return self.__class__, (), self.__getstate__() + + def __getstate__(self): + result = super(TemporalProfile2DPlotStyle, self).__getstate__() + #remove + del result['mTP'] + del result['mSensor'] + + return result + + def isVisible(self): + return self.mIsVisible + + def setVisibility(self, b): + assert isinstance(b, bool) + old = self.isVisible() + self.mIsVisible = b + + if b != old: + self.updateStyleProperties() + #self.update() + + def copyFrom(self, plotStyle): + if isinstance(plotStyle, PlotStyle): + super(TemporalProfilePlotStyleBase, self).copyFrom(plotStyle) + self.updateStyleProperties() + + if isinstance(plotStyle, TemporalProfilePlotStyleBase): + self.setExpression(plotStyle.expression()) + self.setSensor(plotStyle.sensor()) + self.setTemporalProfile(plotStyle.temporalProfile()) + self.updateDataProperties() + + + + +class TemporalProfile2DPlotStyle(TemporalProfilePlotStyleBase): + + + def __init__(self, temporalProfile=None): + super(TemporalProfile2DPlotStyle, self).__init__(temporalProfile=temporalProfile) + #PlotStyle.__init__(self) + #TemporalProfilePlotStyleBase.__init__(self, temporalProfile=temporalProfile) + + def createPlotItem(self, plotWidget): + pdi = TemporalProfilePlotDataItem(self) + self.mPlotItems.append(pdi) + return pdi + + def updateStyleProperties(self): + for pdi in self.mPlotItems: + assert isinstance(pdi, TemporalProfilePlotDataItem) + pdi.updateStyle() + + def updateDataProperties(self): + for pdi in self.mPlotItems: + assert isinstance(pdi, TemporalProfilePlotDataItem) + pdi.updateDataAndStyle() + + + + + + + + + +class TemporalProfileLoaderTask(object): + """ + An object to loading temporal profile values from a *single* raster source. + """ + + def __init__(self, tss:TimeSeriesSource, temporalProfiles: list, bandIndices=None): + + assert isinstance(temporalProfiles, list) + + self.mId = '' + self.mTSS = tss + # assert isinstance(source, str) or isinstance(source, unicode) + self.mSourcePath = tss.uri() + self.mGeometries = {} + self.mRESULTS = {} + self.mERRORS = [] + + for tp in temporalProfiles: + assert isinstance(tp, TemporalProfile) + # save geometry as WKT to be pickable + self.mGeometries[tp.id()] = tp.geometry(tss.crs()).asWkt() + + if bandIndices is None: + bandIndices = list(range(tss.nb)) + self.mBandIndices = bandIndices + + + + +def doLoadTemporalProfileTasks(qgsTask:QgsTask, dump): + + assert isinstance(qgsTask, QgsTask) + tasks = pickle.loads(dump) + 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): @@ -946,10 +1128,9 @@ class TemporalProfilePlotDataItem(pg.PlotDataItem): if isinstance(TP, TemporalProfile) and isinstance(sensor, SensorInstrument): x, y = TP.dataFromExpression(self.mPlotStyle.sensor(), self.mPlotStyle.expression(), dateType='date') - if len(y) > 0: - #x = np.asarray(x, dtype=np.float) - #y = np.asarray(y, dtype=np.float) - self.setData(x=x, y=y) + + if np.any(np.isfinite(y)): + self.setData(x=x, y=y, connect='finite') else: self.setData(x=[], y=[]) # dummy else: @@ -1100,69 +1281,7 @@ class TemporalProfileLayer(QgsVectorLayer): def __getitem__(self, slice): return list(self.mProfiles.values())[slice] - def loadMissingData(self, backgroundProcess=False): - assert isinstance(self.mTimeSeries, TimeSeries) - - # Get or create the TimeSeriesProfiles which will store the loaded values - tasks = [] - - theGeometries = [] - - # Define which (new) bands need to be loaded for each sensor - LUT_bandIndices = dict() - for sensor in self.mTimeSeries.sensors(): - LUT_bandIndices[sensor] = list(range(sensor.nb)) - - PL = PixelLoader() - PL.sigPixelLoaded.connect(self.addPixelLoaderResult) - # update new / existing points - - for tsd in self.mTimeSeries: - assert isinstance(tsd, TimeSeriesDate) - - - requiredIndices = LUT_bandIndices[tsd.mSensor] - requiredIndexKeys = [bandIndex2bandKey(b) for b in requiredIndices] - TPs = [] - missingIndices = set() - for TP in self.mProfiles.values(): - assert isinstance(TP, TemporalProfile) - dataValues = TP.mData[tsd] - existingKeys = list(dataValues.keys()) - missingIdx = [bandKey2bandIndex(k) for k in requiredIndexKeys if k not in existingKeys] - if len(missingIdx) > 0: - TPs.append(TP) - missingIndices.union(set(missingIdx)) - - if len(TPs) > 0: - theGeometries = [tp.coordinate() for tp in TPs] - theIDs = [tp.id() for tp in TPs] - for pathImg in tsd.sourceUris(): - task = PixelLoaderTask(pathImg, theGeometries, - bandIndices=requiredIndices, - temporalProfileIDs=theIDs) - tasks.append(task) - - - if len(tasks) > 0: - - if backgroundProcess: - PL.startLoading(tasks) - else: - import eotimeseriesviewer.pixelloader - tasks = [PixelLoaderTask.fromDump(eotimeseriesviewer.pixelloader.doLoaderTask(None, task.toDump())) for task in tasks] - l = len(tasks) - for i, task in enumerate(tasks): - PL.sigPixelLoaded.emit(task) - - - else: - if DEBUG: - print('Data for geometries already loaded') - - s = "" - - def saveTemporalProfiles(self, pathVector, loadMissingValues=False, sep='\t'): + def saveTemporalProfiles(self, pathVector, sep='\t'): if pathVector is None or len(pathVector) == 0: global DEFAULT_SAVE_PATH if DEFAULT_SAVE_PATH == None: @@ -1177,11 +1296,6 @@ class TemporalProfileLayer(QgsVectorLayer): else: DEFAULT_SAVE_PATH = pathVector - if loadMissingValues: - self.loadMissingData(backgroundProcess=False) - for p in self.mProfiles.values(): - assert isinstance(p, TemporalProfile) - p.loadMissingData() drvName = QgsVectorFileWriter.driverForExtension(os.path.splitext(pathVector)[-1]) QgsVectorFileWriter.writeAsVectorFormat(self, pathVector, 'utf-8', destCRS=self.crs(), driverName=drvName) @@ -1190,7 +1304,7 @@ class TemporalProfileLayer(QgsVectorLayer): # write a flat list of profiles csvLines = ['Temporal Profiles'] nBands = max([s.nb for s in self.mTimeSeries.sensors()]) - csvLines.append(sep.join(['id', 'name', 'sensor', 'date', 'doy', 'sensor'] + ['b{}'.format(b+1) for b in range(nBands)])) + csvLines.append(sep.join(['id', 'name', 'sensor', 'date', 'doy'] + ['b{}'.format(b+1) for b in range(nBands)])) for p in list(self.getFeatures()): @@ -1244,7 +1358,7 @@ class TemporalProfileLayer(QgsVectorLayer): fid = feature.id() if fid < 0: continue - tp = TemporalProfile(self, fid) + tp = TemporalProfile(self, fid, feature.geometry()) self.mProfiles[fid] = tp temporalProfiles.append(tp) @@ -1360,7 +1474,7 @@ class TemporalProfileLayer(QgsVectorLayer): self.committedFeaturesAdded.connect(onFeaturesAdded) self.beginEditCommand('Add {} profile locations'.format(len(features))) - success = self.addFeatures(features) + self.addFeatures(features) self.endEditCommand() self.saveEdits(leaveEditable=b) self.committedFeaturesAdded.disconnect(onFeaturesAdded) @@ -1369,7 +1483,6 @@ class TemporalProfileLayer(QgsVectorLayer): profiles = [self.mProfiles[f.id()] for f in newFeatures] return profiles - def saveEdits(self, leaveEditable=False, triggerRepaint=True): """ function to save layer changes- @@ -1402,7 +1515,6 @@ class TemporalProfileLayer(QgsVectorLayer): self.dataProvider().addAttributes(missingFields) self.saveEdits(leaveEditable=b) - def __len__(self): return self.dataProvider().featureCount() @@ -1414,7 +1526,6 @@ class TemporalProfileLayer(QgsVectorLayer): def __contains__(self, item): return item in self.mProfiles.values() - def temporalProfileToLocationFeature(self, tp:TemporalProfile): self.mLocations.selectByIds([tp.id()]) @@ -1424,29 +1535,12 @@ class TemporalProfileLayer(QgsVectorLayer): return None - def fromSpatialPoint(self, spatialPoint): """ Tests if a Temporal Profile already exists for the given spatialPoint""" - - for p in list(self.mProfiles.values()): assert isinstance(p, TemporalProfile) if p.coordinate() == spatialPoint: return p - """ - spatialPoint = spatialPoint.toCrs(self.crs()) - unit = QgsUnitTypes.toAbbreviatedString(self.crs().mapUnits()).lower() - x = spatialPoint.x() + 0.00001 - y = spatialPoint.y() + 0. - - if 'degree' in unit: - dx = dy = 0.000001 - else: - dx = dy = 0.1 - rect = QgsRectangle(x-dx,y-dy, x+dy,y+dy) - for f in self.getFeatures(rect): - return self.mProfiles[f.id()] - """ return None def removeTemporalProfiles(self, temporalProfiles): @@ -1527,8 +1621,21 @@ class TemporalProfileLayer(QgsVectorLayer): else: pass s = "" + else: + s = "" def clear(self): + """ + Removes all temporal profiles + """ + b = self.isEditable() + self.startEditing() + fids = self.allFeatureIds() + self.deleteFeatures(fids) + self.commitChanges() + + if b: + self.startEditing() #todo: remove TS Profiles #self.mTemporalProfiles.clear() #self.sensorPxLayers.clear() diff --git a/eotimeseriesviewer/temporalprofiles3d.py b/eotimeseriesviewer/temporalprofiles3d.py index 0fb19a7034f4a9312be2c34dfc8714a6fd68e579..fba43b5453b69c8ad4650f9bfe61e3c52adebf0d 100644 --- a/eotimeseriesviewer/temporalprofiles3d.py +++ b/eotimeseriesviewer/temporalprofiles3d.py @@ -27,7 +27,7 @@ from PyQt5.QtGui import * from eotimeseriesviewer.externals.qps.models import Option, OptionListModel -from eotimeseriesviewer.temporalprofiles2d import * +from eotimeseriesviewer.temporalprofiles import * LABEL_EXPRESSION_3D = 'Scaling' diff --git a/eotimeseriesviewer/temporalprofiles3dGL.py b/eotimeseriesviewer/temporalprofiles3dGL.py index 0bc439016068e2f0ef3012172346954773e1ca96..53537e7855962a86999445b0d58b745d1e52ba3e 100644 --- a/eotimeseriesviewer/temporalprofiles3dGL.py +++ b/eotimeseriesviewer/temporalprofiles3dGL.py @@ -33,7 +33,7 @@ from pyqtgraph.opengl import * from pyqtgraph.opengl.GLGraphicsItem import GLGraphicsItem from pyqtgraph.Vector import Vector -from eotimeseriesviewer.temporalprofiles2d import * +from eotimeseriesviewer.temporalprofiles import * diff --git a/eotimeseriesviewer/tests.py b/eotimeseriesviewer/tests.py index febf4ab9c1b8e9eafa4068e0a12b73dbe443030b..732c3becc7615ce23f5950771b67a5507aafc80b 100644 --- a/eotimeseriesviewer/tests.py +++ b/eotimeseriesviewer/tests.py @@ -73,7 +73,7 @@ class TestObjects(eotimeseriesviewer.externals.qps.testing.TestObjects): TS = TimeSeries() files = file_search(DIR_EXAMPLES, '*.tif', recursive=True) - TS.addSources(list(files)) + TS.addSources(list(files), runAsync=False) assert len(TS) > 0 return TS @@ -108,7 +108,7 @@ class TestObjects(eotimeseriesviewer.externals.qps.testing.TestObjects): @staticmethod def createTimeSeriesStacks(): vsiDir = '/vsimem/tmp' - from eotimeseriesviewer.temporalprofiles2d import date2num + from eotimeseriesviewer.temporalprofiles import date2num ns = 50 nl = 100 diff --git a/eotimeseriesviewer/timeseries.py b/eotimeseriesviewer/timeseries.py index f318a53b706c7562d7cf58e405ed0aaf597b8f5a..87ba3d75ae412b8d1a7233edabd341ec52847864 100644 --- a/eotimeseriesviewer/timeseries.py +++ b/eotimeseriesviewer/timeseries.py @@ -33,6 +33,7 @@ from qgis.PyQt.QtGui import * from qgis.PyQt.QtWidgets import * from qgis.PyQt.QtCore import * +DEFAULT_WKT = QgsCoordinateReferenceSystem('EPSG:4326').toWkt() LUT_WAVELENGTH_UNITS = {} for siUnit in [r'nm', r'μm', r'mm', r'cm', r'dm']: @@ -46,8 +47,9 @@ LUT_WAVELENGTH_UNITS[r'decimeters'] = r'dm' from osgeo import gdal +from eotimeseriesviewer import LOG_MESSAGE_TAG from eotimeseriesviewer.dateparser import DOYfromDatetime64 -from eotimeseriesviewer.utils import SpatialExtent, loadUI, px2geo +from eotimeseriesviewer.utils import SpatialExtent, loadUI, px2geo, geo2px, SpatialPoint gdal.SetConfigOption('VRT_SHARED_SOURCE', '0') #!important. really. do not change this. @@ -114,7 +116,7 @@ def getDS(pathOrDataset)->gdal.Dataset: -def sensorID(nb:int, px_size_x:float, px_size_y:float, dt:int, wl:list, wlu:str)->str: +def sensorID(nb:int, px_size_x:float, px_size_y:float, dt:int, wl:list, wlu:str, name:str)->str: """ Create a sensor ID :param nb: number of bands @@ -136,7 +138,10 @@ def sensorID(nb:int, px_size_x:float, px_size_y:float, dt:int, wl:list, wlu:str) if wlu != None: assert isinstance(wlu, str) - return json.dumps((nb, px_size_x, px_size_y, dt, wl, wlu)) + if name != None: + assert isinstance(name, str) + + return json.dumps((nb, px_size_x, px_size_y, dt, wl, wlu, name)) def sensorIDtoProperties(idString:str)->tuple: """ @@ -144,17 +149,24 @@ def sensorIDtoProperties(idString:str)->tuple: :param idString: str :return: (ns, px_size_x, px_size_y, [wl], wlu) """ - nb, px_size_x, px_size_y, dt, wl, wlu = json.loads(idString) + try: + nb, px_size_x, px_size_y, dt, wl, wlu, name = json.loads(idString) + except ValueError as ex: + if ex.args[0] == 'not enough values to unpack (expected 7, got 6)': + nb, px_size_x, px_size_y, dt, wl, wlu = json.loads(idString) + name = None + assert isinstance(dt, int) and dt >= 0 assert isinstance(nb, int) - assert isinstance(px_size_x, (int,float)) and px_size_x > 0 + assert isinstance(px_size_x, (int, float)) and px_size_x > 0 assert isinstance(px_size_y, (int, float)) and px_size_y > 0 if wl != None: assert isinstance(wl, list) if wlu != None: assert isinstance(wlu, str) - - return nb, px_size_x, px_size_y, dt, wl, wlu + if name != None: + assert isinstance(name, str) + return nb, px_size_x, px_size_y, dt, wl, wlu, name class SensorInstrument(QObject): @@ -173,12 +185,18 @@ class SensorInstrument(QObject): 'swIR2': 2150 }) - def __init__(self, sid:str, sensor_name:str=None, band_names:list = None): + def __init__(self, sid:str, band_names:list = None): super(SensorInstrument, self).__init__() self.mId = sid - - self.nb, self.px_size_x, self.px_size_y, self.dataType, self.wl, self.wlu = sensorIDtoProperties(self.mId) + self.nb:int + self.px_size_x:float + self.px_size_y:float + self.dataType:int + self.wl:list + self.wlu:str + self.nb, self.px_size_x, self.px_size_y, self.dataType, self.wl, self.wlu, self.mNameOriginal = sensorIDtoProperties(self.mId) + self.mName = '' if not isinstance(band_names, list): band_names = ['Band {}'.format(b+1) for b in range(self.nb)] @@ -191,13 +209,17 @@ class SensorInstrument(QObject): else: self.wl = np.asarray(self.wl) - if sensor_name is None: - from eotimeseriesviewer.settings import value, Keys - sensorNames = value(Keys.SensorNames) - sensor_name = sensorNames.get(sid, '{}bands@{}m'.format(self.nb, self.px_size_x)) - self.mName = '' - self.setName(sensor_name) + + if self.mNameOriginal in [None, '']: + from eotimeseriesviewer.settings import sensorName + sensor_name = sensorName(sid) + if sensor_name is None: + sensor_name = '{}bands@{}m'.format(self.nb, self.px_size_x) + self.setName(sensor_name) + else: + self.setName(self.mNameOriginal) + self.hashvalue = hash(self.mId) @@ -205,6 +227,14 @@ class SensorInstrument(QObject): import uuid path = '/vsimem/mockupImage.{}.bsq'.format(uuid.uuid4()) self.mMockupDS = TestObjects.inMemoryImage(path=path, nb=self.nb, eType=self.dataType, ns=2, nl=2) + if self.wl is not None: + self.mMockupDS.SetMetadataItem('wavelength', '{{{}}}'.format(','.join(str(wl) for wl in self.wl))) + if self.wlu is not None: + self.mMockupDS.SetMetadataItem('wavelength units', self.wlu) + self.mMockupDS.FlushCache() + s = "" + + def bandIndexClosestToWavelength(self, wl, wl_unit='nm')->int: """ @@ -251,14 +281,12 @@ class SensorInstrument(QObject): Sets the sensor/product name :param name: str """ + if name != self.mName: + assert isinstance(name, str) self.mName = name - from eotimeseriesviewer.settings import Keys, value, setValue - - sensorNames = value(Keys.SensorNames) - sensorNames[self.id()] = name - setValue(Keys.SensorNames, sensorNames) - + from eotimeseriesviewer.settings import saveSensorName + saveSensorName(self) self.sigNameChanged.emit(self.name()) def name(self)->str: @@ -377,6 +405,7 @@ class TimeSeriesSource(object): if provider == 'gdal': ds = gdal.Open(lyr.source()) + s = "" elif provider == 'wcs': parts = urllib.parse.parse_qs(lyr.source()) url = re.search(r'^[^?]+', parts['url'][0]).group() @@ -397,11 +426,12 @@ class TimeSeriesSource(object): elif isinstance(source, str): ds = gdal.Open(source) + s = "" elif isinstance(source, gdal.Dataset): ds = source - else: + if not isinstance(ds, gdal.Dataset): raise Exception('Unsupported source: {}'.format(source)) return TimeSeriesSource(ds) @@ -410,7 +440,6 @@ class TimeSeriesSource(object): self.mUri = None self.mDrv = None - self.mGT = None self.mWKT = None self.mCRS = None self.mWL = None @@ -449,17 +478,24 @@ class TimeSeriesSource(object): self.mWKT = dataset.GetProjection() if self.mWKT == '': # no CRS? try with QGIS API - lyr = QgsRasterLayer(self.mUri) + loptions = QgsRasterLayer.LayerOptions(loadDefaultStyle=False) + lyr = QgsRasterLayer(self.mUri, options=loptions) if lyr.crs().isValid(): self.mWKT = lyr.crs().toWkt() + if self.mWKT == '': + # default to WGS-84 lat lon + self.mWKT = str(DEFAULT_WKT) + self.mCRS = QgsCoordinateReferenceSystem(self.mWKT) px_x = float(abs(self.mGeoTransform[1])) px_y = float(abs(self.mGeoTransform[5])) self.mGSD = (px_x, px_y) self.mDataType = dataset.GetRasterBand(1).DataType - self.mSid = sensorID(self.nb, px_x, px_y, self.mDataType, self.mWL, self.mWLU) + + sName = sensorName(dataset) + self.mSidOriginal = self.mSid = sensorID(self.nb, px_x, px_y, self.mDataType, self.mWL, self.mWLU, sName) self.mUL = QgsPointXY(*px2geo(QPoint(0, 0), self.mGeoTransform, pxCenter=False)) @@ -504,6 +540,7 @@ class TimeSeriesSource(object): self.mUL = QgsPointXY(QgsGeometry.fromWkt(self.mUL).asPoint()) self.mLR = QgsPointXY(QgsGeometry.fromWkt(self.mLR).asPoint()) self.mDate = np.datetime64(self.mDate) + self.mSpatialExtent = None def __getstate__(self): @@ -546,6 +583,30 @@ class TimeSeriesSource(object): uri.layerType = 'raster' return uri + def asRasterLayer(self)->QgsRasterLayer: + return QgsRasterLayer(self.uri(), self.name(), 'gdal') + + def pixelCoordinate(self, geometry)->QPoint: + """ + + :param QgsGeometry | QgsPoint | SpatialPoint: + :return: QPoint, if coordinate interects with source raster, None else + """ + + if isinstance(geometry, QgsGeometry): + geometry = geometry.asPoint() + if isinstance(geometry, QgsPoint): + geometry = QgsPointXY(geometry.x(), geometry.y()) + if isinstance(geometry, SpatialPoint): + geometry = geometry.toCrs(self.crs()) + assert isinstance(geometry, QgsPointXY) + px = geo2px(geometry, self.mGeoTransform) + assert isinstance(px, QPoint) + + if px.x() < 0 or px.y() < 0 or px.x() >= self.ns or px.y() > self.nl: + return None + return px + def sid(self)->str: """ Returns the sensor id @@ -574,7 +635,9 @@ class TimeSeriesSource(object): return self.mCRS def spatialExtent(self)->SpatialExtent: - return SpatialExtent(self.mCRS, self.mUL, self.mLR) + if not isinstance(self.mSpatialExtent, SpatialExtent): + self.mSpatialExtent = SpatialExtent(self.mCRS, self.mUL, self.mLR) + return self.mSpatialExtent def __eq__(self, other): if not isinstance(other, TimeSeriesSource): @@ -584,7 +647,7 @@ class TimeSeriesSource(object): class TimeSeriesDate(QAbstractTableModel): """ - A containe to store all image source related to a single observation date and sensor. + A container to store all source images related to a single observation date and sensor. """ sigSourcesAdded = pyqtSignal(list) sigSourcesRemoved = pyqtSignal(list) @@ -676,7 +739,7 @@ class TimeSeriesDate(QAbstractTableModel): """ return self.mSensor - def sources(self)->list: + def sources(self)->typing.List[TimeSeriesSource]: """ Returns the source images :return: [list-of-TimeSeriesSource] @@ -684,7 +747,7 @@ class TimeSeriesDate(QAbstractTableModel): return self.mSources - def sourceUris(self)->list: + def sourceUris(self)->typing.List[str]: """ Returns all source URIs as list of strings- :return: [list-of-str] @@ -988,23 +1051,39 @@ class DateTimePrecision(enum.Enum): Milisecond = 'ms' Original = 0 +class SensorMatching(enum.Enum): + """ + Describes when two different sources should be considered to be from the same sensor + """ + DIMS = 'Image Dimensions only' + DIMS_WL = 'Image Dimensions + Wavelength' + DIMS_Name = 'Image Dimensions + Name' + DIMS_WL_Name = 'Image Dimensions + Wavelength + Name' -def doLoadTimeSeriesSourcesTask(taskWrapper:QgsTask, dump): + +def doLoadTimeSeriesSourcesTask(qgsTask:QgsTask, dump): sources = pickle.loads(dump) - assert isinstance(taskWrapper, QgsTask) + assert isinstance(qgsTask, QgsTask) results = [] + invalidSources = list() n = len(sources) for i, source in enumerate(sources): - if taskWrapper.isCanceled(): + if qgsTask.isCanceled(): return pickle.dumps(results) - s = TimeSeriesSource.create(source) - if isinstance(s, TimeSeriesSource): - results.append(s) - taskWrapper.setProgress(i+1) - return pickle.dumps(results) - s = "" + + try: + tss = TimeSeriesSource.create(source) + assert isinstance(tss, TimeSeriesSource) + results.append(tss) + except Exception as ex: + invalidSources.append((source, ex)) + + qgsTask.setProgress(i + 1) + + return pickle.dumps(results), pickle.dumps(invalidSources) + class TimeSeries(QAbstractItemModel): """ @@ -1029,10 +1108,12 @@ class TimeSeries(QAbstractItemModel): self.mTSDs = list() self.mSensors = [] self.mShape = None + self.mDateTimePrecision = DateTimePrecision.Original + self.mProductSimilarity = SensorMatching.DIMS self.mLoadingProgressDialog = None - + self.mLUT_Path2TSD = {} self.mVisibleDate = [] self.mCurrentSpatialExtent = None @@ -1060,8 +1141,13 @@ class TimeSeries(QAbstractItemModel): if isinstance(spatialExtent, SpatialExtent) and self.mCurrentSpatialExtent != spatialExtent: self.mCurrentSpatialExtent = spatialExtent - def focusVisibilityToExtent(self): - ext = self.currentSpatialExtent() + def focusVisibilityToExtent(self, ext:SpatialExtent=None): + """ + Changes TSDs visibility according to its intersection with a SpatialExtent + :param ext: SpatialExtent + """ + if ext is None: + ext = self.currentSpatialExtent() if isinstance(ext, SpatialExtent): changed = False for tsd in self: @@ -1099,6 +1185,39 @@ class TimeSeries(QAbstractItemModel): idx2 = self.index(idx.row(), self.columnCount()-1) self.dataChanged.emit(idx, idx2, [Qt.BackgroundColorRole]) + def findMatchingSensor(self, sensorID:str)->SensorInstrument: + if isinstance(sensorID, str): + nb, px_size_x, px_size_y, dt, wl, wlu, name = sensorIDtoProperties(sensorID) + + else: + assert isinstance(sensorID, tuple) and len(sensorID) == 7 + nb, px_size_x, px_size_y, dt, wl, wlu, name = sensorID + + DIMS = (nb, px_size_y, px_size_x, dt) + for sensor in self.sensors(): + DIMS2 = (sensor.nb, sensor.px_size_y, sensor.px_size_x, sensor.dataType) + + bName = sensor.mNameOriginal == name + bWL = wlu == sensor.wlu and np.array_equal(wl, sensor.wl) + + if DIMS != DIMS2: + # self.mProductSimilarity == SensorMatching.DIMS: + continue + + if self.mProductSimilarity == SensorMatching.DIMS: + return sensor + + elif self.mProductSimilarity == SensorMatching.DIMS_Name and bName: + return sensor + + elif self.mProductSimilarity == SensorMatching.DIMS_WL and bWL: + return sensor + + elif self.mProductSimilarity == SensorMatching.DIMS_WL_Name and bName and bWL: + return sensor + + return None + def sensor(self, sensorID:str)->SensorInstrument: """ Returns the sensor with sid = sid @@ -1106,21 +1225,26 @@ class TimeSeries(QAbstractItemModel): :return: SensorInstrument """ assert isinstance(sensorID, str) - for sensor in self.mSensors: - assert isinstance(sensor, SensorInstrument) - if sensor.id() == sensorID: + + nb, px_size_x, px_size_y, dt, wl, wlu, name = sensorIDtoProperties(sensorID) + + for sensor in self.sensors(): + if (sensor.nb, sensor.px_size_y, sensor.px_size_x, sensor.dataType, sensor.wl, sensor.wlu, + sensor.mNameOriginal) == ( + nb, px_size_y, px_size_x, dt, wl, wlu, name): return sensor + return None - def sensors(self)->list: + def sensors(self)->typing.List[SensorInstrument]: """ Returns the list of sensors derived from the TimeSeries data sources :return: [list-of-SensorInstruments] """ return self.mSensors[:] - def loadFromFile(self, path, n_max=None, progressDialog:QProgressDialog=None): + def loadFromFile(self, path, n_max=None, progressDialog:QProgressDialog=None, runAsync=True): """ Loads a CSV file with source images of a TimeSeries :param path: str, Path of CSV file @@ -1151,7 +1275,7 @@ class TimeSeries(QAbstractItemModel): progressDialog.setValue(0) progressDialog.setLabelText('Start loading {} images....'.format(len(images))) - self.addSourcesAsync(images, progressDialog=progressDialog) + self.addSources(images, progressDialog=progressDialog, runAsync=runAsync) def saveToFile(self, path): """ @@ -1214,10 +1338,14 @@ class TimeSeries(QAbstractItemModel): :param pathOfInterest: str, image source uri :return: TimeSeriesDate """ - for tsd in self.mTSDs: - assert isinstance(tsd, TimeSeriesDate) - if pathOfInterest in tsd.sourceUris(): - return tsd + tsd = self.mLUT_Path2TSD.get(pathOfInterest) + if isinstance(tsd, TimeSeriesDate): + return tsd + else: + for tsd in self.mTSDs: + assert isinstance(tsd, TimeSeriesDate) + if pathOfInterest in tsd.sourceUris(): + return tsd return None def tsd(self, date: np.datetime64, sensor)->TimeSeriesDate: @@ -1306,7 +1434,7 @@ class TimeSeries(QAbstractItemModel): if isinstance(t, TimeSeriesSource): toRemove.add(t.timeSeriesDate()) - for tsd in list(toRemove): + for tsd in list(sorted(list(toRemove), reverse=True)): assert isinstance(tsd, TimeSeriesDate) @@ -1322,6 +1450,11 @@ class TimeSeries(QAbstractItemModel): self.endRemoveRows() if len(removed) > 0: + pathsToRemove = [path for path, tsd in self.mLUT_Path2TSD.items() if tsd in removed] + for path in pathsToRemove: + self.mLUT_Path2TSD.pop(path) + + self.checkSensorList() self.sigTimeSeriesDatesRemoved.emit(removed) def tsds(self, date:np.datetime64=None, sensor:SensorInstrument=None)->list: @@ -1385,7 +1518,14 @@ class TimeSeries(QAbstractItemModel): return sensor return None - def addSourcesAsync(self, sources:list, nWorkers:int = 1, progressDialog:QProgressDialog=None): + def addSources(self, sources:list, nWorkers:int = 1, progressDialog:QProgressDialog=None, runAsync=True): + """ + Adds source images to the TimeSeries + :param sources: list of source images, e.g. a list of file paths + :param nWorkers: not used yet + :param progressDialog: QProgressDialog + :param runAsync: bool + """ tm = QgsApplication.taskManager() assert isinstance(tm, QgsTaskManager) @@ -1394,49 +1534,35 @@ class TimeSeries(QAbstractItemModel): self.mLoadingProgressDialog = progressDialog - if True: - n = len(sources) - taskDescription = 'Load {} images'.format(n) - dump = pickle.dumps(sources) + n = len(sources) + taskDescription = 'Load {} images'.format(n) + dump = pickle.dumps(sources) + + + if runAsync: qgsTask = QgsTask.fromFunction(taskDescription, doLoadTimeSeriesSourcesTask, dump, on_finished = self.onAddSourcesAsyncFinished) - tid = id(qgsTask) - qgsTask.taskCompleted.connect(lambda *args, tid=tid: self.onRemoveTask(tid)) - qgsTask.taskTerminated.connect(lambda *args, tid=tid: self.onRemoveTask(tid)) - self.mTasks[tid] = qgsTask - - if False: # for debugging only - resultDump = doLoadTimeSeriesSourcesTask(qgsTask, dump) - self.onAddSourcesAsyncFinished(None, resultDump) - else: - tm.addTask(qgsTask) - else: - # see https://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks + from .utils import TaskMock + qgsTask = TaskMock() - def chunks(l, n): - """Yield successive n-sized chunks from l.""" - for i in range(0, len(l), n): - yield l[i:i + n] - - n = int(len(sources) / nWorkers) - for subset in chunks(sources, 50): - - dump = pickle.dumps(subset) + tid = id(qgsTask) + qgsTask.taskCompleted.connect(lambda *args, tid=tid: self.onRemoveTask(tid)) + qgsTask.taskTerminated.connect(lambda *args, tid=tid: self.onRemoveTask(tid)) + if isinstance(progressDialog, QProgressDialog): + qgsTask.progressChanged.connect(progressDialog.setValue) + progressDialog.setLabelText('Read images') + progressDialog.setRange(0, n) + progressDialog.setValue(0) - taskDescription = 'Load {} images'.format(len(subset)) - qgsTask = QgsTask.fromFunction(taskDescription, doLoadTimeSeriesSourcesTask, dump, on_finished=self.onAddSourcesAsyncFinished) - tid = id(qgsTask) - self.mTasks[tid] = qgsTask - qgsTask.taskCompleted.connect(lambda *args, tid=tid: self.onRemoveTask(tid)) - qgsTask.taskTerminated.connect(lambda *args, tid=tid: self.onRemoveTask(tid)) + self.mTasks[tid] = qgsTask - if False: # for debugging only - resultDump = doLoadTimeSeriesSourcesTask(qgsTask, dump) - self.onAddSourcesAsyncFinished(None, resultDump) - else: - tm.addTask(qgsTask) - s = "" + if runAsync: + tm.addTask(qgsTask) + else: + # for debugging only + resultDump = doLoadTimeSeriesSourcesTask(qgsTask, dump) + self.onAddSourcesAsyncFinished(None, resultDump) def onRemoveTask(self, key): self.mTasks.pop(key) @@ -1444,21 +1570,40 @@ class TimeSeries(QAbstractItemModel): def onAddSourcesAsyncFinished(self, *args): # print(':: onAddSourcesAsyncFinished') error = args[0] + + hasProgressDialog = isinstance(self.mLoadingProgressDialog, QProgressDialog) + if error is None: try: - addedDates = [] - dump = args[1] - sources = pickle.loads(dump) - for source in sources: - if isinstance(self.mLoadingProgressDialog, QProgressDialog): - self.increaseProgressBar() - newTSD = self._addSource(source) - if isinstance(newTSD, TimeSeriesDate): - addedDates.append(newTSD) + sources, invalidSources = args[1] + sources = pickle.loads(sources) + invalidSources = pickle.loads(invalidSources) - if len(addedDates) > 0: - self.sigTimeSeriesDatesAdded.emit(addedDates) + if len(invalidSources) > 0: + info = ['Unable to load {} data source(s):'.format(len(invalidSources))] + for s, ex in invalidSources: + info.append('Path="{}" Error="{}"'.format(str(s), str(ex).replace('\n', ' '))) + info = '\n'.join(info) + messageLog(info, Qgis.Critical) + + if len(sources) > 0: + addedDates = [] + if hasProgressDialog: + self.mLoadingProgressDialog.setLabelText('Add Images') + self.mLoadingProgressDialog.setRange(0, len(sources)) + self.mLoadingProgressDialog.setValue(0) + + for i, source in enumerate(sources): + newTSD = self._addSource(source) + if isinstance(newTSD, TimeSeriesDate): + addedDates.append(newTSD) + + if hasProgressDialog: + self.mLoadingProgressDialog.setValue(i+1) + + if len(addedDates) > 0: + self.sigTimeSeriesDatesAdded.emit(addedDates) except Exception as ex: import traceback @@ -1468,57 +1613,8 @@ class TimeSeries(QAbstractItemModel): s = "" if isinstance(self.mLoadingProgressDialog, QProgressDialog): - if self.mLoadingProgressDialog.wasCanceled() or self.mLoadingProgressDialog.value() == -1: - self.mLoadingProgressDialog = None - - def increaseProgressBar(self): - if isinstance(self.mLoadingProgressDialog, QProgressDialog): - v = self.mLoadingProgressDialog.value() + 1 - self.mLoadingProgressDialog.setValue(v) - self.mLoadingProgressDialog.setLabelText('{}/{}'.format(v, self.mLoadingProgressDialog.maximum())) - - if v == 1 or v % 25 == 0: - QApplication.processEvents() - - def addSources(self, sources:list, progressDialog:QProgressDialog=None): - """ - Adds new data sources to the TimeSeries - :param sources: [list-of-TimeSeriesSources] - """ - assert isinstance(sources, list) - self.mLoadingProgressDialog = progressDialog - nMax = len(sources) - # 1. read sources - # this could be excluded into a parallel process - addedDates = [] - for i, source in enumerate(sources): - newTSD = None - msg = None - if False: #debug - newTSD = self._addSource(source) - else: - try: - newTSD = self._addSource(source) - except Exception as ex: - msg = 'Unable to add: {}\n{}'.format(str(source), str(ex)) - print(msg, file=sys.stderr) - - if isinstance(self.mLoadingProgressDialog, QProgressDialog): - if self.mLoadingProgressDialog.wasCanceled(): - break - self.increaseProgressBar() - - if isinstance(newTSD, TimeSeriesDate): - addedDates.append(newTSD) - - #if len(addedDates) > 0: - - if isinstance(progressDialog, QProgressDialog): - progressDialog.setLabelText('Create map widgets...') - - if len(addedDates) > 0: - self.sigTimeSeriesDatesAdded.emit(addedDates) - self.mLoadingProgressDialog = None + self.mLoadingProgressDialog.hide() + self.mLoadingProgressDialog = None def _addSource(self, source:TimeSeriesSource)->TimeSeriesDate: @@ -1538,20 +1634,31 @@ class TimeSeries(QAbstractItemModel): tsdDate = self.date2date(tss.date()) tssDate = tss.date() sid = tss.sid() - sensor = self.sensor(sid) + + + sensor = self.findMatchingSensor(sid) + # if necessary, add a new sensor instance if not isinstance(sensor, SensorInstrument): sensor = self.addSensor(SensorInstrument(sid)) + assert isinstance(sensor, SensorInstrument) tsd = self.tsd(tsdDate, sensor) + # if necessary, add a new TimeSeriesDate instance if not isinstance(tsd, TimeSeriesDate): tsd = self.insertTSD(TimeSeriesDate(self, tsdDate, sensor)) newTSD = tsd # addedDates.append(tsd) assert isinstance(tsd, TimeSeriesDate) + + # ensure that the source refers to the sensor ID of the linked sensor (which might be different from its orginal sensor id) + tss.mSid = sensor.id() + # add the source + tsd.addSource(tss) + self.mLUT_Path2TSD[tss.uri()] = tsd return newTSD def setDateTimePrecision(self, mode:DateTimePrecision): @@ -1564,6 +1671,15 @@ class TimeSeries(QAbstractItemModel): #do we like to update existing sources? + def setSensorMatching(self, mode:SensorMatching): + """ + Sets the mode under which two source images can be considered as to be from the same sensor/product + :param mode: + :return: + """ + assert isinstance(mode, SensorMatching) + self.mProductSimilarity = mode + def date2date(self, date:np.datetime64)->np.datetime64: """ @@ -1605,7 +1721,7 @@ class TimeSeries(QAbstractItemModel): def __len__(self): return len(self.mTSDs) - def __iter__(self): + def __iter__(self)->typing.Iterator[TimeSeriesDate]: return iter(self.mTSDs) def __getitem__(self, slice): @@ -1867,7 +1983,7 @@ class TimeSeries(QAbstractItemModel): def findDate(self, date)->TimeSeriesDate: """ - Returns a TimeSeriesDate closes to that in date + Returns a TimeSeriesDate closest to that in date :param date: numpy.datetime64 | str | TimeSeriesDate :return: TimeSeriesDate """ @@ -1895,6 +2011,43 @@ class TimeSeries(QAbstractItemModel): flags = flags | Qt.ItemIsUserCheckable return flags +regSensorName = re.compile(r'(SATELLITEID|sensor[ _]?type|product[ _]?type)', re.IGNORECASE) +#regSensorName = re.compile(r'(SATELLITEID|sensor[ _]?type)', re.IGNORECASE) + +def sensorName(dataset:gdal.Dataset)->str: + """ + Reads the sensor/product name. Returns None if a proper name can not be extracted. + :param dataset: gdal.Dataset + :return: str + """ + assert isinstance(dataset, gdal.Dataset) + domains = dataset.GetMetadataDomainList() + if isinstance(domains, list): + for domain in domains: + md = dataset.GetMetadata_Dict(domain) + if isinstance(md, dict): + for key, value in md.items(): + if regSensorName.search(key): + return str(value) + + for b in range(dataset.RasterCount): + band = dataset.GetRasterBand(b+1) + if isinstance(band, gdal.Band): + domains = band.GetMetadataDomainList() + if isinstance(domains, list): + for domain in domains: + md = band.GetMetadata_Dict(domain) + if isinstance(md, dict): + for key, value in md.items(): + if regSensorName.search(key): + return str(value) + + return None + + + + + def getSpatialPropertiesFromDataset(ds): assert isinstance(ds, gdal.Dataset) @@ -1931,14 +2084,27 @@ def extractWavelengthsFromGDALMetaData(ds:gdal.Dataset)->(list, str): for b in range(ds.RasterCount): band = ds.GetRasterBand(b + 1) assert isinstance(band, gdal.Band) - md = band.GetMetadata_Dict() + domains = band.GetMetadataDomainList() + if not isinstance(domains, list): + continue + for domain in domains: + md = band.GetMetadata_Dict(domain) - keyWLU = findKey(md, regWLUkey) - keyWL = findKey(md, regWLkey) + keyWLU = findKey(md, regWLUkey) + keyWL = findKey(md, regWLkey) - if isinstance(keyWL, str) and isinstance(keyWLU, str): - wl.append(float(md[keyWL])) - wlu.append(LUT_WAVELENGTH_UNITS[md[keyWLU].lower()]) + if isinstance(keyWL, str) and isinstance(keyWLU, str): + + valueWL = float(md[keyWL]) + valueWLU = str(md[keyWLU]).lower() + + if valueWL > 0: + wl.append(valueWL) + + if valueWLU in LUT_WAVELENGTH_UNITS.keys(): + wlu.append(LUT_WAVELENGTH_UNITS[valueWLU]) + + break if len(wlu) == len(wl) and len(wl) == ds.RasterCount: return wl, wlu[0] @@ -1977,7 +2143,7 @@ def extractWavelengthsFromRapidEyeXML(ds:gdal.Dataset, dom:QDomDocument)->(list, 0.5 * (520 + 590), 0.5 * (630 + 685), 0.5 * (760 + 850), - 0.5 * (760 - 850) + 0.5 * (760 + 850) ] return wl, wlu return None, None diff --git a/eotimeseriesviewer/ui/profileviewdock.ui b/eotimeseriesviewer/ui/profileviewdock.ui index 38697fce7a0ebee279d20054ec538248117f61a1..2c298d7dd3f1ab794a06ec6144d505c909a5eb5c 100644 --- a/eotimeseriesviewer/ui/profileviewdock.ui +++ b/eotimeseriesviewer/ui/profileviewdock.ui @@ -964,7 +964,7 @@ background-color: rgb(0, 0, 0);</string> <customwidget> <class>DateTimePlotWidget</class> <extends>QGraphicsView</extends> - <header>eotimeseriesviewer.temporalprofiles2d</header> + <header>eotimeseriesviewer.temporalprofiles</header> </customwidget> <customwidget> <class>PlotSettingsTableView</class> diff --git a/eotimeseriesviewer/ui/settingsdialog.ui b/eotimeseriesviewer/ui/settingsdialog.ui index 0ea0ca00981a6d74297133b602dbf31cd54dda4f..c2b8ee40e4378bf3fa6319de3907f1cb2880c4b2 100644 --- a/eotimeseriesviewer/ui/settingsdialog.ui +++ b/eotimeseriesviewer/ui/settingsdialog.ui @@ -6,7 +6,7 @@ <rect> <x>0</x> <y>0</y> - <width>411</width> + <width>606</width> <height>326</height> </rect> </property> @@ -46,12 +46,17 @@ <string>Maps</string> </property> </item> + <item> + <property name="text"> + <string>Sensors</string> + </property> + </item> </widget> </item> <item> <widget class="QStackedWidget" name="stackedWidget"> <property name="currentIndex"> - <number>1</number> + <number>2</number> </property> <widget class="QWidget" name="pageGeneral"> <layout class="QVBoxLayout" name="verticalLayout_3"> @@ -69,8 +74,8 @@ </widget> </item> <item row="0" column="1"> - <widget class="QgsFileWidget" name="mFileWidgetScreenshots"> - <property name="fileWidgetButtonVisible"> + <widget class="QgsFileWidget" name="mFileWidgetScreenshots" native="true"> + <property name="fileWidgetButtonVisible" stdset="0"> <bool>true</bool> </property> </widget> @@ -90,10 +95,10 @@ </widget> </item> <item row="1" column="1"> - <widget class="QgsFileWidget" name="mFileWidgetRasterSources"/> + <widget class="QgsFileWidget" name="mFileWidgetRasterSources" native="true"/> </item> <item row="2" column="1"> - <widget class="QgsFileWidget" name="mFileWidgetVectorSources"/> + <widget class="QgsFileWidget" name="mFileWidgetVectorSources" native="true"/> </item> </layout> </widget> @@ -113,6 +118,26 @@ </item> <item row="0" column="1"> <widget class="QComboBox" name="cbDateTimePrecission"> + <property name="toolTip"> + <string>Defines the accuracy with which source images of same sensor but different time stamps are combined into the same image group.</string> + </property> + <property name="whatsThis"> + <string>Selects the precission used to extract time stamps from raster meta data.</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string>Sensor/Product Matching</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="cbSensorMatching"> + <property name="toolTip"> + <string>Specifies when source images should be considered to be from the same sensor/image product.</string> + </property> <property name="whatsThis"> <string>Selects the precission used to extract time stamps from raster meta data.</string> </property> @@ -205,6 +230,19 @@ </property> </widget> </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> </layout> </item> <item row="1" column="0"> @@ -236,9 +274,6 @@ <property name="styleSheet"> <string notr="true">);</string> </property> - <property name="mode"> - <enum>QgsFontButton::ModeTextRenderer</enum> - </property> </widget> </item> <item row="3" column="0"> @@ -256,6 +291,9 @@ <height>16777215</height> </size> </property> + <property name="toolTip"> + <string>Intervall to check if updates are available for map canvases, e.g. to render new layers or apply renderer changes.</string> + </property> <property name="suffix"> <string>msec</string> </property> @@ -275,6 +313,75 @@ </item> </layout> </widget> + <widget class="QWidget" name="pageSensors"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <property name="topMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="label_12"> + <property name="text"> + <string>Sensor Names </string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QToolButton" name="btnReloadSensorSettings"> + <property name="text"> + <string>Reload</string> + </property> + <property name="icon"> + <iconset resource="../../../../QGIS/images/images.qrc"> + <normaloff>:/images/themes/default/mActionReload.svg</normaloff>:/images/themes/default/mActionReload.svg</iconset> + </property> + <property name="autoRaise"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="btnDeleteSelectedSensors"> + <property name="text"> + <string>Clear</string> + </property> + <property name="icon"> + <iconset resource="../../../../QGIS/images/images.qrc"> + <normaloff>:/images/themes/default/mActionDeleteSelected.svg</normaloff>:/images/themes/default/mActionDeleteSelected.svg</iconset> + </property> + <property name="autoRaise"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QTableView" name="tableViewSensorSettings"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> </widget> </item> </layout> @@ -290,26 +397,46 @@ </widget> </item> </layout> + <action name="actionRefreshSensorList"> + <property name="icon"> + <iconset resource="../../../../QGIS/images/images.qrc"> + <normaloff>:/images/themes/default/mActionReload.svg</normaloff>:/images/themes/default/mActionReload.svg</iconset> + </property> + <property name="text"> + <string>RefreshSensorList</string> + </property> + </action> + <action name="actionDeleteSelectedSensors"> + <property name="icon"> + <iconset resource="../../../../QGIS/images/images.qrc"> + <normaloff>:/images/themes/default/mActionDeleteSelected.svg</normaloff>:/images/themes/default/mActionDeleteSelected.svg</iconset> + </property> + <property name="text"> + <string>DeleteSelectedSensors</string> + </property> + </action> </widget> <customwidgets> + <customwidget> + <class>QgsFileWidget</class> + <extends>QWidget</extends> + <header>qgsfilewidget.h</header> + </customwidget> <customwidget> <class>QgsColorButton</class> <extends>QToolButton</extends> <header>qgscolorbutton.h</header> <container>1</container> </customwidget> - <customwidget> - <class>QgsFileWidget</class> - <extends>QWidget</extends> - <header>qgsfilewidget.h</header> - </customwidget> <customwidget> <class>QgsFontButton</class> <extends>QToolButton</extends> <header>qgsfontbutton.h</header> </customwidget> </customwidgets> - <resources/> + <resources> + <include location="../../../../QGIS/images/images.qrc"/> + </resources> <connections> <connection> <sender>buttonBox</sender> diff --git a/eotimeseriesviewer/ui/timeseriesviewer.ui b/eotimeseriesviewer/ui/timeseriesviewer.ui index 928be2e1c6f1564a799a1a3818bb444994aca838..008cac9bf2ddf2dafcdb5ecf0ff95951773f57f1 100644 --- a/eotimeseriesviewer/ui/timeseriesviewer.ui +++ b/eotimeseriesviewer/ui/timeseriesviewer.ui @@ -768,9 +768,6 @@ <property name="iconVisibleInMenu"> <bool>false</bool> </property> - <property name="shortcutVisibleInContextMenu"> - <bool>true</bool> - </property> </action> <action name="actionExportMapsToImages"> <property name="icon"> diff --git a/eotimeseriesviewer/utils.py b/eotimeseriesviewer/utils.py index 609e4a9a369df18778dc54d15683126f5204c009..a1ea2f8a3a9a7b25d4cc4b9280fe64c2dc32449b 100644 --- a/eotimeseriesviewer/utils.py +++ b/eotimeseriesviewer/utils.py @@ -73,3 +73,7 @@ def fixMenuButtons(w:QWidget): if isinstance(toolButton.defaultAction(), QAction) and isinstance(toolButton.defaultAction().menu(), QMenu)\ or isinstance(toolButton.menu(), QMenu): toolButton.setPopupMode(QToolButton.MenuButtonPopup) + +class TaskMock(QgsTask): + def __init__(self): + super(TaskMock, self).__init__() \ No newline at end of file diff --git a/example/exampleEvents.gpkg b/example/exampleEvents.gpkg index 826a2a726d71dac012e4067fe942bc683cc85960..b0b913914ecf33c7247367cd549be345febc6943 100644 --- a/example/exampleEvents.gpkg +++ b/example/exampleEvents.gpkg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f5cde8206cc45a57b417237aff68909cc06c98717cf510609cf83f3ee1933e21 +oid sha256:6bfd5c8fcc8d30ee3f1c9798d88b197f0fec4d64f3ab9f148fa3423feb05d6ce size 385024 diff --git a/make/deploy.py b/make/deploy.py index 77aff59561337f5d6923e99e64f2bf08de0c6506..b2351241b5d795c86f6edfb651b88662e2f2bd76 100644 --- a/make/deploy.py +++ b/make/deploy.py @@ -41,7 +41,7 @@ import eotimeseriesviewer DIR_BUILD = jp(DIR_REPO, 'build') DIR_DEPLOY = jp(DIR_REPO, 'deploy') -DIR_DOC_SOURCE = jp(DIR_REPO, *['doc','source']) +DIR_DOC_SOURCE = jp(DIR_REPO, *['doc', 'source']) QGIS_MIN = '3.4' QGIS_MAX = '3.99' @@ -127,7 +127,7 @@ class QGISMetadataFileWriter(object): self.mDescription = None self.mVersion = None - self.mQgisMinimumVersion = '3.4' + self.mQgisMinimumVersion = '3.8' self.mQgisMaximumVersion = '3.99' self.mAuthor = None self.mAbout = None @@ -270,13 +270,16 @@ def build(): if True: # 1. clean an existing directory = plugin folder - pb_tool.clean_deployment(ask_first=False) + try: + pb_tool.clean_deployment(ask_first=False) + except: + pass import make make.compileResourceFiles() # 3. Deploy = write the data to the new plugin folder - pb_tool.deploy_files(pathCfg, DIR_DEPLOY, quick=True, confirm=False) + pb_tool.deploy_files(pathCfg, DIR_DEPLOY, 'default', quick=True, confirm=False) # 4. As long as we can not specify in the pb_tool.cfg which file types are not to deploy, # we need to remove them afterwards. @@ -378,7 +381,7 @@ def updateInfoHTMLs(): from eotimeseriesviewer import PATH_CHANGELOG from docutils.core import publish_string - urlIssueTracke = r'https://bitbucket.org/jakimowb/eo-time-series-viewer/issues/' + urlIssueTracker = r'https://bitbucket.org/jakimowb/eo-time-series-viewer/issues/' def readTextFile(path): with open(path, 'r', encoding='utf-8') as f: @@ -402,7 +405,7 @@ def updateInfoHTMLs(): # CHANGELOG -> CHANGELOG.html txt = readTextFile(PATH_CHANGELOG) - txt = re.sub(r'(#(\d+))', r'`#\2 <{}\2>`_'.format(urlIssueTracke), txt) + txt = re.sub(r'(#(\d+))', r'`#\2 <{}\2>`_'.format(urlIssueTracker), txt) txt = publish_string(txt, writer_name='html').decode('utf-8') diff --git a/tests/test_fileFormatLoading.py b/tests/test_fileFormatLoading.py index be99a1ff1e2a7112d4bb05d54304619b9caea0da..dc8ab352fa6db9251dc307ec0d7a5cbd617e4b20 100644 --- a/tests/test_fileFormatLoading.py +++ b/tests/test_fileFormatLoading.py @@ -6,12 +6,12 @@ import qgis.testing from unittest import TestCase from eotimeseriesviewer import * from eotimeseriesviewer.utils import * -from eotimeseriesviewer.timeseries import TimeSeries +from eotimeseriesviewer.timeseries import * DIR_SENTINEL = r'' DIR_PLEIADES = r'H:\Pleiades' -DIR_RAPIDEYE = jp(DIR_EXAMPLES, 'Images') +DIR_RAPIDEYE = r'Y:\RapidEye\3A' DIR_LANDSAT = jp(DIR_EXAMPLES, 'Images') DIR_VRT = r'O:\SenseCarbonProcessing\BJ_NOC\01_RasterData\02_CuttedVRT' @@ -49,8 +49,11 @@ class TestFileFormatLoading(TestCase): if not os.path.isdir(searchDir): print('data directory undefined. skip test.') return - files = list(file_search(searchDir, 're_*.bsq', recursive=True)) - self.TS.addSources(files) + files = list(file_search(searchDir, 're_*.tif', recursive=True)) + for file in files: + tss = TimeSeriesSource.create(file) + self.assertIsInstance(tss, TimeSeriesSource) + self.TS.addSources(files, runAsync=False) self.assertEqual(len(files), len(self.TS)) def test_loadLandsat(self): @@ -59,11 +62,62 @@ class TestFileFormatLoading(TestCase): print('DIR_LANDSAT undefined. skip test.') return files = list(file_search(searchDir, '*_L*_BOA.bsq'))[0:3] - self.TS.addSources(files) + self.TS.addSources(files, runAsync=False) self.assertEqual(len(files), len(self.TS)) s = "" + + def test_loadOSARIS_GRD(self): + + testDir = r'Q:\Processing_BJ\99_OSARIS_Testdata\Loibl-2019-OSARIS-Ala-Archa\Coherences' + if os.path.isdir(testDir): + files = file_search(testDir, re.compile(r'.*\.grd$')) + for i, path in enumerate(files): + + tss = TimeSeriesSource.create(path) + self.assertIsInstance(tss, TimeSeriesSource) + self.assertTrue(tss.crs().isValid()) + self.TS.addSources([path], runAsync=False) + self.assertEqual(len(self.TS), i+1) + + tss = self.TS[0][0] + self.assertIsInstance(tss, TimeSeriesSource) + sensor = self.TS[0].sensor() + self.assertIsInstance(sensor, SensorInstrument) + + + def test_ForceLevel2(self): + + path = r'J:\diss_bj\level2\s-america\X0050_Y0025\20140601_LEVEL2_LND08_BOA.tif' + path = r'J:\diss_bj\level2\s-america\X0049_Y0025\20140531_LEVEL2_LND07_BOA.tif' + + testData = r'J:\diss_bj\level2\s-america\X0049_Y0025' + if os.path.isdir(testData): + files = file_search(testData, '*IMP.tif') + for path in files: + + self.TS.addSources([path], runAsync=False) + self.assertEqual(len(self.TS), 1) + + tss = self.TS[0][0] + self.assertIsInstance(tss, TimeSeriesSource) + sensor = self.TS[0].sensor() + self.assertIsInstance(sensor, SensorInstrument) + + s = "" + + def test_badtimeformat(self): + + p = r'C:\Users\geo_beja\Desktop\23042014_LEVEL2_LND08_VZN.tif' + + if os.path.isfile(p): + tss = TimeSeriesSource.create(p) + s = "" + + + + def test_nestedVRTs(self): # load VRTs pointing to another VRT pointing to Landsat imagery searchDir = DIR_VRT @@ -71,7 +125,7 @@ class TestFileFormatLoading(TestCase): print('DIR_VRT undefined. skip test.') return files = list(file_search(searchDir, '*BOA.vrt', recursive=True))[0:3] - self.TS.addSources(files) + self.TS.addSources(files, runAsync=False) self.assertEqual(len(files), len(self.TS)) def test_loadRapidEye(self): @@ -81,9 +135,17 @@ class TestFileFormatLoading(TestCase): print('DIR_RAPIDEYE undefined. skip test.') return files = file_search(searchDir, '*.tif', recursive=True) - files = [f for f in files if not re.search('_(udm|browse)\.tif$', f)] - self.TS.addSources(files) - self.assertEqual(len(files), len(self.TS)) + files = [f for f in files if not re.search(r'_(udm|browse)\.tif$', f)] + self.TS.addSources(files, runAsync=False) + self.assertEqual(len(files), len(self.TS.sourceUris())) + + tsd = self.TS[0] + self.assertIsInstance(tsd, TimeSeriesDate) + for wl in tsd.sensor().wl: + self.assertTrue(wl > 0) + tss = tsd[0] + self.assertIsInstance(tss, TimeSeriesSource) + @@ -95,7 +157,7 @@ class TestFileFormatLoading(TestCase): return #files = file_search(searchDir, 'DIM*.xml', recursive=True) files = list(file_search(searchDir, '*.jp2', recursive=True))[0:3] - self.TS.addSources(files) + self.TS.addSources(files, runAsync=False) self.assertEqual(len(files), len(self.TS)) def test_loadSentinel2(self): @@ -105,7 +167,7 @@ class TestFileFormatLoading(TestCase): print('DIR_SENTINEL undefined. skip test.') return files = list(file_search(searchDir, '*MSIL1C.xml', recursive=True)) - self.TS.addSources(files) + self.TS.addSources(files, runAsync=False) #self.assertRegexpMatches(self.stderr.getvalue().strip(), 'Unable to add:') self.assertEqual(0, len(self.TS)) # do not add a containers @@ -113,7 +175,7 @@ class TestFileFormatLoading(TestCase): for file in files: subs = gdal.Open(file).GetSubDatasets() subdatasets.extend(s[0] for s in subs) - self.TS.addSources(subdatasets) + self.TS.addSources(subdatasets, runAsync=False) self.assertEqual(len(subdatasets), len(self.TS)) # add subdatasets diff --git a/tests/test_main.py b/tests/test_main.py index fbd9e4d8516bfda7aae9bf446fef7cb6e99258cf..bb00299a37f600144ed0240743061f96af53a931 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -105,11 +105,27 @@ class TestInit(TestCase): while QgsApplication.taskManager().countActiveTasks() > 0 or len(TSV.timeSeries().mTasks) > 0: QCoreApplication.processEvents() - tsd = TSV.timeSeries()[-1] - TSV.setCurrentDate(tsd) + if len(TSV.timeSeries()) > 0: + tsd = TSV.timeSeries()[-1] + TSV.setCurrentDate(tsd) if SHOW_GUI: QGIS_APP.exec_() + + def test_TimeSeriesViewerInvalidSource(self): + + from eotimeseriesviewer.main import TimeSeriesViewer + + TSV = TimeSeriesViewer() + imgages = ['not-existing-source'] + TSV.addTimeSeriesImages(imgages, loadAsync=False) + while QgsApplication.taskManager().countActiveTasks() > 0 or len(TSV.timeSeries().mTasks) > 0: + QCoreApplication.processEvents() + + if SHOW_GUI: + QGIS_APP.exec_() + + def test_TimeSeriesViewerMultiSource(self): from eotimeseriesviewer.main import TimeSeriesViewer diff --git a/tests/test_mapvisualization.py b/tests/test_mapvisualization.py index acecb075bb6e238dd68eb1c2c080c469141149d3..9af4c0befb9f34a67a60639d4fbdb46581b63245 100644 --- a/tests/test_mapvisualization.py +++ b/tests/test_mapvisualization.py @@ -34,7 +34,7 @@ QGIS_APP = initQgisApplication(loadProcessingFramework=False) from eotimeseriesviewer import initResources initResources() -SHOW_GUI = True and os.environ.get('CI') is None +SHOW_GUI = False and os.environ.get('CI') is None def getChildElements(node): @@ -390,109 +390,22 @@ class testclassMapVisualization(unittest.TestCase): rClone = r1.clone() self.assertTrue(type(r1), type(rClone)) - xmlClone = rendererToXml(rClone) + xmlClone = rendererToXml(rClone) self.assertIsInstance(xmlClone, QDomDocument) similar = compareXML(xml1.firstChild(), xml2.firstChild()) self.assertTrue(similar) - - - - - + del rClone, xmlClone for path in styleFiles: with open(path, encoding='utf8') as f: xml = ''.join(f.readlines()) - - renderer = rendererFromXml(xml) self.assertTrue(renderer != None) + self.assertIsInstance(renderer, (QgsRasterRenderer, QgsFeatureRenderer)) - s ="" - - - - - def test_spatialTemporalVisualization(self): - from eotimeseriesviewer.main import TimeSeriesViewer - - TSV = TimeSeriesViewer() - TSV.loadExampleTimeSeries() - TSV.show() - - SV = TSV.spatialTemporalVis - self.assertIsInstance(SV, SpatialTemporalVisualization) - QApplication.processEvents() - # TSV.createMapView() - - import time - time.sleep(5) - - SV.timedCanvasRefresh() - - - visibleCanvases = [] - withLayers = [] - empty = [] - extent = None - for mapCanvas in SV.mapCanvases(): - self.assertIsInstance(mapCanvas, MapCanvas) - self.assertIsInstance(mapCanvas.spatialExtent(), SpatialExtent) - - if extent is None: - extent = mapCanvas.spatialExtent() - else: - self.assertTrue(mapCanvas.spatialExtent() == extent) - - if len(mapCanvas.layers()) == 0: - empty.append(mapCanvas) - else: - withLayers.append(mapCanvas) - - if True: - QGIS_APP.exec_() - - self.assertTrue(len(withLayers) > 0) - self.assertTrue(len(empty) > 0) - - # shift spatial extent - extent2 = extent.setCenter(SpatialPoint(extent.crs(), extent.center().x()-100, extent.center().y())) - SV.setSpatialExtent(extent2) - SV.timedCanvasRefresh() - for mapCanvas in SV.mapCanvases(): - self.assertIsInstance(mapCanvas, MapCanvas) - if mapCanvas.isVisibleToViewport(): - self.assertTrue(mapCanvas.spatialExtent() == extent2) - - - # shift spatial extent of single map canvas - extent3 = extent.setCenter(SpatialPoint(extent.crs(), extent.center().x() + 100, extent.center().y())) - canvas = SV.mapCanvases()[0] - self.assertIsInstance(canvas, MapCanvas) - canvas.setSpatialExtent(extent3) - SV.timedCanvasRefresh() - for mapCanvas in SV.mapCanvases(): - if mapCanvas.isVisibleToViewport(): - self.assertTrue(mapCanvas.spatialExtent() == extent3) - - # test map render changes - for canvas in SV.mapCanvases(): - self.assertIsInstance(canvas, MapCanvas) - menu = canvas.contextMenu() - self.assertIsInstance(menu, QMenu) - if canvas.isVisibleToViewport(): - - for action in menu.findChildren(QAction): - self.assertIsInstance(action, QAction) - text = action.text() - if text in ['', 'Style', 'PNG', 'JPEG']: - # skip menu / blocking dialog options - continue - else: - print('Test QAction "{}"'.format(action.text())) - action.trigger() - break - s = "" +if __name__ == '__main__': + os.environ['CI'] = True + unittest.main() diff --git a/tests/test_pixelloader.py b/tests/test_pixelloader.py index a3fa6583ad3939c3507ae83e7ed0ccaca8e86fe1..6cd7e97675c65af258b17dc92b7586dfe4726a32 100644 --- a/tests/test_pixelloader.py +++ b/tests/test_pixelloader.py @@ -148,11 +148,36 @@ class PixelLoaderTest(unittest.TestCase): if os.path.isfile(p): lyr = QgsRasterLayer(p) task = PixelLoaderTask(p, [SpatialPoint.fromMapLayerCenter(lyr)]) + from eotimeseriesviewer.utils import TaskMock + result = doLoaderTask(TaskMock(), task.toDump()) - result = doLoaderTask(None, task.toDump()) s = "" + def test_pixelLoader_OOImg(self): + + from eotimeseriesviewer.utils import TaskMock + from eotimeseriesviewer.pixelloader import PixelLoaderTask, doLoaderTask + + if os.path.isfile(path): + crs = QgsCoordinateReferenceSystem('EPSG:4326') + pt = SpatialPoint(crs, -55.41314771195199995, -6.92449242268311593) + + lyr = QgsRasterLayer(path) + ext = lyr.extent() + pt2 = pt.toCrs(lyr.crs()) + + + + task = PixelLoaderTask(path, [p]) + result = doLoaderTask(TaskMock(), task.toDump()) + + s = "" + + + PixelLoaderTask() + + def test_pixelLoader(self): from eotimeseriesviewer.pixelloader import doLoaderTask, PixelLoaderTask, INFO_OUT_OF_IMAGE, INFO_NO_DATA from eotimeseriesviewer import px2geo @@ -186,8 +211,8 @@ class PixelLoaderTest(unittest.TestCase): self.assertIsInstance(result, PixelLoaderTask) self.assertTrue(result.success()) - self.assertEqual(result.sourcePath, source) - self.assertSequenceEqual(result.bandIndices, [0,1,2,3,4,5]) + self.assertEqual(result.mSourcePath, source) + self.assertSequenceEqual(result.mBandIndices, [0, 1, 2, 3, 4, 5]) self.assertIs(result.exception, None) diff --git a/tests/test_sensorvisualization.py b/tests/test_sensorvisualization.py index 7b98f3d92a2bc381b2c6c8c69a88e0149e6f83f7..4c75e2f2738af57cb79e8b5839f078f8578e0732 100644 --- a/tests/test_sensorvisualization.py +++ b/tests/test_sensorvisualization.py @@ -19,7 +19,7 @@ class TestInit(unittest.TestCase): def createTestDatasets(self): vsiDir = '/vsimem/tmp' - from eotimeseriesviewer.temporalprofiles2d import date2num + from eotimeseriesviewer.temporalprofiles import date2num ns = 50 nl = 100 @@ -67,7 +67,7 @@ class TestInit(unittest.TestCase): model = SensorListModel(TS) self.assertTrue(model.rowCount() == 0) - TS.addSources(pathes) + TS.addSources(pathes, runAsync=False) self.assertTrue(len(TS) == len(pathes)) self.assertTrue(model.rowCount() == 2) diff --git a/tests/test_settings.py b/tests/test_settings.py index fb501838cf85d15033558b8ed3fdfeec63b88123..ceb99185c52f12bae7cd6d284e45b55a40fcd968 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -24,7 +24,7 @@ from PyQt5.QtCore import * import unittest, tempfile from eotimeseriesviewer.mapcanvas import * -from eotimeseriesviewer.crosshair import * +from eotimeseriesviewer import * from eotimeseriesviewer.utils import * resourceDir = os.path.join(DIR_REPO, 'qgisresources') QGIS_APP = initQgisApplication() @@ -78,6 +78,15 @@ class testclassSettingsTest(unittest.TestCase): self.assertIsInstance(defaults, dict) + def test_SensorModel(self): + + tb = QTableView() + m = SensorSettingsTableModel() + tb.setModel(m) + tb.show() + + if SHOW_GUI: + QGIS_APP.exec_() if __name__ == "__main__": diff --git a/tests/test_stackedbandinput.py b/tests/test_stackedbandinput.py index f7c0324106f2ce8bcbb2b1f62f8aaf4eba9a3c2b..1e0da56b362717b4801bb0ed816ae66eccc37bc5 100644 --- a/tests/test_stackedbandinput.py +++ b/tests/test_stackedbandinput.py @@ -33,14 +33,12 @@ from example.Images import Img_2014_06_16_LE72270652014167CUB00_BOA, Img_2014_05 QGIS_APP = initQgisApplication() -class testclassDialogTest(unittest.TestCase): - """Test rerources work.""" - +class testclassStackedInputTests(unittest.TestCase): def createTestDatasets(self): vsiDir = '/vsimem/tmp' - from eotimeseriesviewer.temporalprofiles2d import date2num + from eotimeseriesviewer.temporalprofiles import date2num ns = 50 nl = 100 @@ -105,6 +103,19 @@ class testclassDialogTest(unittest.TestCase): return datasets + def test_FORCEStacks(self): + + pathStack = r'T:\4BJ\2018-2018_000-000_LEVEL4_TSA_SEN2L_EVI_C0_S0_TSS.tif' + + if os.path.isfile(pathStack): + d = StackedBandInputDialog() + d.show() + d.addSources([pathStack]) + + if SHOW_GUI: + QGIS_APP.exec_() + s ="" + def test_inputmodel(self): testData = self.createTestDatasets() diff --git a/tests/test_temporalprofiles.py b/tests/test_temporalprofiles.py index dfde14e72401c858f4d61311850e35918fb5c38c..6071fd0a1ac9133757a681ea5bd6f4c5235630bb 100644 --- a/tests/test_temporalprofiles.py +++ b/tests/test_temporalprofiles.py @@ -18,13 +18,13 @@ from qgis.gui import * from PyQt5.QtGui import QIcon import example.Images from eotimeseriesviewer.timeseries import TimeSeries, TimeSeriesDate -from eotimeseriesviewer.temporalprofiles2d import * +from eotimeseriesviewer.temporalprofiles import * from eotimeseriesviewer.profilevisualization import * from eotimeseriesviewer.utils import * from eotimeseriesviewer.tests import initQgisApplication from osgeo import ogr, osr QGIS_APP = initQgisApplication() -SHOW_GUI = True and os.environ.get('CI') is None and not os.environ.get('CI') +SHOW_GUI = False and os.environ.get('CI') is None class testclassUtilityTests(unittest.TestCase): """Test temporal profiles""" @@ -34,7 +34,8 @@ class testclassUtilityTests(unittest.TestCase): self.TS = TimeSeries() files = list(file_search(os.path.dirname(example.Images.__file__), '*.tif')) - self.TS.addSources(files) + self.TS.addSources(files, runAsync=False) + self.assertTrue(len(self.TS) > 0) self.dirTmp = tempfile.mkdtemp(prefix='EOTSV_Test') def tearDown(self): @@ -56,6 +57,68 @@ class testclassUtilityTests(unittest.TestCase): self.assertIsInstance(p, TemporalProfile) return results + def test_loadTemporalProfiles(self): + + center = self.TS.maxSpatialExtent().spatialCenter() + + lyr = TemporalProfileLayer(self.TS) + tp1 = lyr.createTemporalProfiles(center)[0] + tp2 = lyr.createTemporalProfiles(center)[0] + tProfiles = [tp1, tp2] + + tss = self.TS[0][0] + self.assertIsInstance(tss, TimeSeriesSource) + + tasks = [] + for tss in self.TS[0]: + tasks.append(TemporalProfileLoaderTask(tss, tProfiles)) + + self.lastProgress = -1 + def onProgress(p): + self.lastProgress = p + + qgsTask = TaskMock() + qgsTask.progressChanged.connect(onProgress) + dump = doLoadTemporalProfileTasks(qgsTask, pickle.dumps(tasks)) + tasks = pickle.loads(dump) + self.assertIsInstance(tasks, list) + + + for task in tasks: + self.assertIsInstance(task, TemporalProfileLoaderTask) + self.assertTrue(len(task.mERRORS) > 0 or len(task.mRESULTS) > 0) + self.assertTrue(len(task.mRESULTS) == len(tProfiles)) + self.assertAlmostEqual(self.lastProgress, 100) + + + tasks = [] + bandIndices = [-12, 0, 4, 999] + tasks.append(TemporalProfileLoaderTask(tss, tProfiles, bandIndices=bandIndices)) + dump = doLoadTemporalProfileTasks(qgsTask, pickle.dumps(tasks)) + tasks = pickle.loads(dump) + self.assertIsInstance(tasks, list) + for task in tasks: + self.assertIsInstance(task, TemporalProfileLoaderTask) + self.assertTrue(task.mTSS, TimeSeriesSource) + for tpId, data in task.mRESULTS.items(): + # check returned data + self.assertIsInstance(data, dict) + self.assertTrue('px_x' in data.keys()) + self.assertTrue('px_y' in data.keys()) + + for idx in bandIndices: + bandName = 'b{}'.format(idx+1) + if idx < 0 or idx >= task.mTSS.nb - 1: + self.assertTrue(bandName not in data.keys()) + else: + self.assertTrue(bandName in data.keys()) + + self.assertTrue(len(task.mRESULTS) == len(tProfiles)) + + + # todo: test-nodata values + + def test_createTemporalProfile(self): center = self.TS.maxSpatialExtent().spatialCenter() @@ -63,9 +126,8 @@ class testclassUtilityTests(unittest.TestCase): lyr = TemporalProfileLayer(self.TS) tp = lyr.createTemporalProfiles(center)[0] - self.assertIsInstance(tp, TemporalProfile) - tp.loadMissingData(False) + tp.loadMissingData() temporalProfiles = [tp] temporalProfiles.extend(lyr.createTemporalProfiles((SpatialPoint(center.crs(), center.x() - 50, center.y() + 50)))) @@ -118,7 +180,7 @@ class testclassUtilityTests(unittest.TestCase): self.assertEqual(tp, tp2) p = tempfile.mktemp('.shp', 'testtemporalprofiles') - writtenFiles = lyr1.saveTemporalProfiles(p, loadMissingValues=True) + writtenFiles = lyr1.saveTemporalProfiles(p) self.assertTrue(len(writtenFiles) == 2) for f in writtenFiles: self.assertTrue(os.path.isfile(f)) @@ -172,7 +234,7 @@ class testclassUtilityTests(unittest.TestCase): for sensor in self.TS.sensors(): self.assertIsInstance(sensor, SensorInstrument) for expression in expressions: - x , y = tp.dataFromExpression(sensor, expression) + x, y = tp.dataFromExpression(sensor, expression) self.assertIsInstance(x, list) self.assertIsInstance(y, list) self.assertEqual(len(x), len(y)) diff --git a/tests/test_timeseries.py b/tests/test_timeseries.py index f40590f37cd2bf6ba63ba6877b6709a9d758e436..6c773058a62f1d259c0e5c181095b53f78ceed77 100644 --- a/tests/test_timeseries.py +++ b/tests/test_timeseries.py @@ -15,12 +15,19 @@ QAPP = initQgisApplication() SHOW_GUI = False and os.environ.get('CI') is None +import eotimeseriesviewer.settings + +s = eotimeseriesviewer.settings.settings() +s.clear() +s.sync() +s = "" + class TestInit(unittest.TestCase): def createTestDatasets(self): vsiDir = '/vsimem/tmp' - from eotimeseriesviewer.temporalprofiles2d import date2num + from eotimeseriesviewer.temporalprofiles import date2num ns = 50 nl = 100 @@ -158,7 +165,6 @@ class TestInit(unittest.TestCase): self.assertIsInstance(tss.spatialExtent(), SpatialExtent) self.assertIsInstance(tss, TimeSeriesSource) - if not isinstance(ref, TimeSeriesSource): ref = tss else: @@ -259,7 +265,7 @@ class TestInit(unittest.TestCase): w.show() TS = TimeSeries() - TS.addSourcesAsync(files, nWorkers=1) + TS.addSources(files, nWorkers=1) while QgsApplication.taskManager().countActiveTasks() > 0 or len(TS.mTasks) > 0: QCoreApplication.processEvents() @@ -352,6 +358,9 @@ class TestInit(unittest.TestCase): ds = gdal.Open(p) self.assertIsInstance(ds, gdal.Dataset) band = ds.GetRasterBand(1) + + + self.assertIsInstance(band, gdal.Band)