├── .docker ├── Dockerfile ├── docker-compose.gh.yml └── run-docker-tests.sh ├── .github └── workflows │ ├── build.yml │ ├── lint.yml │ ├── release.yml │ └── testing.yml ├── .gitignore ├── .pre-commit-config.yaml ├── AUTHORS.md ├── CONTRIBUTE.md ├── LICENSE ├── README.rst ├── pavement.py ├── planet_explorer ├── __init__.py ├── gui │ ├── __init__.py │ ├── pe_aoi_maptools.py │ ├── pe_basemap_layer_widget.py │ ├── pe_basemaps_list_widget.py │ ├── pe_basemaps_widget.py │ ├── pe_daily_images_preview_config_dialog.py │ ├── pe_dailyimages_search_results_widget.py │ ├── pe_dailyimages_widget.py │ ├── pe_explorer_dockwidget.py │ ├── pe_filters.py │ ├── pe_gui_utils.py │ ├── pe_legacy_warning_dialog.py │ ├── pe_legacy_warning_widget.py │ ├── pe_open_saved_search_dialog.py │ ├── pe_orders.py │ ├── pe_orders_monitor_dockwidget.py │ ├── pe_planet_inspector_dockwidget.py │ ├── pe_quads_treewidget.py │ ├── pe_range_slider.py │ ├── pe_results_configuration_dialog.py │ ├── pe_save_search_dialog.py │ ├── pe_settings_dialog.py │ ├── pe_show_curl_dialog.py │ ├── pe_tasking_dockwidget.py │ ├── pe_thumbnails.py │ ├── range_slider.py │ └── resources │ │ ├── __init__.py │ │ ├── intermediate-ca.pem │ │ └── item-types.json ├── metadata.txt ├── pe_analytics.py ├── pe_functions.py ├── pe_plugin.py ├── pe_utils.py ├── planet_api │ ├── __init__.py │ ├── p_client.py │ ├── p_order_tasks.py │ ├── p_quad_orders.py │ ├── p_utils.py │ ├── request-result-samples │ │ ├── annotated_basemap_search.json │ │ ├── aoi.json │ │ ├── aoi_mountain.json │ │ ├── basemap_search.json │ │ ├── bundles_2019-08-30.json │ │ ├── id_regex.py │ │ ├── item-perms-specs.json │ │ ├── item-perms-specs_sorted.json │ │ ├── item-specs.json │ │ ├── orders-spec.json │ │ ├── quota-spec.json │ │ ├── search_query.json │ │ └── test-aoi.json │ └── resources │ │ ├── empty_thumb.png │ │ ├── empty_thumb.png.aux.xml │ │ ├── empy_thumb.pgw │ │ └── productBundleDefaults.json ├── resources │ ├── PSScene4Band.qml │ ├── __init__.py │ ├── account.svg │ ├── basemap.svg │ ├── close-down.svg │ ├── close.svg │ ├── closedock-16.png │ ├── closedock-down-16.png │ ├── cog.svg │ ├── cog2.svg │ ├── collapse-triangle.svg │ ├── copy.svg │ ├── crop.svg │ ├── download.svg │ ├── envelope-gray.svg │ ├── expand-triangle.svg │ ├── expand_less.svg │ ├── expand_more.svg │ ├── extent-draw-polygon.svg │ ├── extent-select.svg │ ├── extents.svg │ ├── external-link.svg │ ├── file-open.svg │ ├── filetype.svg │ ├── footprints-nix.qml │ ├── footprints-win.qml │ ├── harmonize.svg │ ├── info-light.svg │ ├── info.svg │ ├── inspector.png │ ├── inspector.svg │ ├── lock-gray.svg │ ├── lock-light.svg │ ├── mActionAddXyzLayer.svg │ ├── mosaics_caps.json │ ├── nitems.svg │ ├── orders.svg │ ├── pin.svg │ ├── planet-explorer-inkscape-light.svg │ ├── planet-explorer-inkscape.svg │ ├── planet-explorer.svg │ ├── planet-logo-dark.png │ ├── planet-logo-dark.svg │ ├── planet-logo-p.png │ ├── planet-logo-p.svg │ ├── planet-user.svg │ ├── reload.svg │ ├── resources.py │ ├── resources.qrc │ ├── satellite.svg │ ├── search.svg │ ├── search_p.svg │ ├── sort.svg │ ├── tasking.png │ ├── tasking.svg │ ├── terms.html │ ├── thumb-placeholder-128.svg │ ├── thumb-placeholder.svg │ ├── udm.svg │ ├── zoom-target-search.svg │ ├── zoom-target.svg │ ├── zoom-to-aoi-dashed.svg │ └── zoom-to-aoi-solid.svg ├── settings.json ├── tests │ ├── __init__.py │ ├── conftest.py │ ├── data │ │ ├── aoi_tests │ │ │ ├── test_aoi.gpkg │ │ │ ├── test_empty.gpkg │ │ │ └── test_multipoly.gpkg │ │ └── test_add_to_map │ │ │ └── planet_orders │ │ │ └── 5c9e6c59-eb35-485d-ab7d-04a75e9e0f14 │ │ │ └── Add_to_map_test_QGIS │ │ │ ├── files │ │ │ └── PSScene │ │ │ │ └── 20221221_084022_18_2414 │ │ │ │ ├── 20221221_084022_18_2414_metadata.json │ │ │ │ └── analytic_sr_udm2 │ │ │ │ ├── 20221221_084022_18_2414_3B_AnalyticMS_SR.tif │ │ │ │ ├── 20221221_084022_18_2414_3B_AnalyticMS_metadata.xml │ │ │ │ └── 20221221_084022_18_2414_3B_udm2.tif │ │ │ └── manifest.json │ ├── install_plugin.py │ ├── pytest.ini │ ├── test_basemaps.py │ ├── test_daily_imagery.py │ ├── test_filters.py │ ├── test_login.py │ ├── test_orders.py │ ├── test_plugin.py │ ├── test_saved_search.py │ ├── test_tasking.py │ └── utils.py └── ui │ ├── OGLdpf.log │ ├── basemaps_widget.ui │ ├── dailyimages_widget.ui │ ├── pe_aoi_filter_base.ui │ ├── pe_daily_filter_base.ui │ ├── pe_daily_images_preview_config_dialog.ui │ ├── pe_explorer_dockwidget.ui │ ├── pe_legacy_warning_dialog.ui │ ├── pe_legacy_warning_widget.ui │ ├── pe_open_saved_search_dialog.ui │ ├── pe_orders.ui │ ├── pe_orders_monitor_dockwidget.ui │ ├── pe_orders_v2_base.ui │ ├── pe_orders_v2_source_base.ui │ ├── pe_planet_inspector_dockwidget.ui │ ├── pe_range_slider_base.ui │ ├── pe_search_results_base.ui │ ├── pe_tasking_dockwidget.ui │ ├── results_configuration_dialog.ui │ ├── save_search_dialog.ui │ ├── show_curl_dialog.ui │ ├── test_dispatch_callback_watcher.ui │ └── test_thumbnail_cache.ui ├── qgis3-conda-forge-env.yml ├── requirements.txt ├── requirements_test.txt ├── scripts └── run-tests.sh └── setup.cfg /.docker/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG QGIS_TEST_VERSION=latest 2 | FROM qgis/qgis:${QGIS_TEST_VERSION} 3 | 4 | RUN apt-get update && \ 5 | apt-get install -y python3-pip 6 | COPY ./requirements.txt /tmp/ 7 | RUN pip3 install -r /tmp/requirements.txt 8 | 9 | COPY ./requirements_test.txt /tmp/ 10 | RUN pip3 install -r /tmp/requirements_test.txt 11 | 12 | ENV LANG=C.UTF-8 13 | ENV IS_DOCKER_CONTAINER=true 14 | 15 | WORKDIR / 16 | -------------------------------------------------------------------------------- /.docker/docker-compose.gh.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | qgis: 4 | build: 5 | context: .. 6 | dockerfile: ./.docker/Dockerfile 7 | args: 8 | QGIS_TEST_VERSION: ${QGIS_TEST_VERSION} 9 | tty: true 10 | volumes: 11 | - ${GITHUB_WORKSPACE}:/usr/src 12 | -------------------------------------------------------------------------------- /.docker/run-docker-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | pushd /usr/src 6 | DEFAULT_PARAMS='./planet_explorer/tests/test_orders.py -k test_order_scene -v --qgis_disable_gui --qgis_disable_init --reruns 2' 7 | xvfb-run pytest ${@:-`echo $DEFAULT_PARAMS`} 8 | popd 9 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - '**' 10 | jobs: 11 | build: 12 | name: "Build" 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Get source code 17 | uses: actions/checkout@v2 18 | 19 | - name: Set up Python 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: ${{ env.PYTHON_VERSION }} 23 | 24 | - name: Install plugin dependencies 25 | run: pip install -r requirements.txt 26 | 27 | - name: Setup 28 | run: | 29 | pip install paver 30 | paver setup 31 | 32 | - name: Build package 33 | env: 34 | SENTRY_KEY: ${{ secrets.SENTRY_KEY }} 35 | SEGMENTS_KEY: ${{ secrets.SEGMENTS_KEY }} 36 | GITHUB_REF: ${{ github.ref }} 37 | GITHUB_SHA: ${{ github.sha}} 38 | run: | 39 | paver package.sentry=${SENTRY_KEY} package.segments=$SEGMENTS_KEY package.version=${GITHUB_SHA::7} package 40 | mkdir tmp 41 | unzip planet_explorer.zip -d tmp 42 | 43 | - uses: actions/upload-artifact@v2 44 | with: 45 | name: planet_explorer_${{github.sha}} 46 | path: tmp 47 | 48 | install: 49 | name: "Install" 50 | runs-on: ubuntu-latest 51 | needs: build 52 | strategy: 53 | matrix: 54 | qgis_version: [ release-3_10, release-3_16, release-3_24, release-3_26, release-3_28 ] 55 | env: 56 | QGIS_TEST_VERSION: ${{ matrix.qgis_version }} 57 | # cf https://docs.qgis.org/3.16/en/docs/user_manual/introduction/qgis_configuration.html#running-qgis-with-advanced-settings 58 | QGIS_COMMAND: qgis --noplugins --noversioncheck --nologo --version-migration --code ./planet_explorer/tests/install_plugin.py 59 | 60 | steps: 61 | - name: Checkout 62 | uses: actions/checkout@v2 63 | with: 64 | submodules: recursive 65 | 66 | - name: Download artifact 67 | uses: actions/download-artifact@v2 68 | with: 69 | name: planet_explorer_${{github.sha}} 70 | path: tmp 71 | 72 | - name: Zip artifact 73 | run: (cd tmp && zip -r ../planet_explorer_${{github.sha}}.zip .) 74 | 75 | - name: Pull qgis image 76 | run: docker pull qgis/qgis:${QGIS_TEST_VERSION} 77 | 78 | - name: Run install test 79 | run: docker run --rm -v `pwd`:/tests_directory -t -w /tests_directory qgis/qgis:${QGIS_TEST_VERSION} sh -c "xvfb-run ${QGIS_COMMAND}" 80 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Linting 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - '**' 10 | 11 | jobs: 12 | pre-commit-lint: 13 | runs-on: ubuntu-latest 14 | name: Lint 15 | steps: 16 | - name: Check out source repository 17 | uses: actions/checkout@v2 18 | - name: Set up Python environment 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: "3.8" 22 | - name: Install pre-commit 23 | run: pip install pre-commit 24 | - name: Run pre-commit 25 | run: pre-commit run --all --verbose 26 | - name: Analysis (git diff) 27 | if: failure() 28 | run: git diff 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: "*" 6 | 7 | jobs: 8 | release: 9 | name: "Release on tag" 10 | runs-on: ubuntu-latest 11 | 12 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') 13 | 14 | steps: 15 | - name: Get source code 16 | uses: actions/checkout@v2 17 | 18 | - name: Set up Python 19 | uses: actions/setup-python@v2 20 | with: 21 | python-version: ${{ env.PYTHON_VERSION }} 22 | 23 | - name: Install plugin dependencies 24 | run: pip install -r requirements.txt 25 | 26 | - name: Setup 27 | run: | 28 | pip install paver 29 | paver setup 30 | 31 | - name: Build package 32 | env: 33 | SENTRY_KEY: ${{ secrets.SENTRY_KEY }} 34 | SEGMENTS_KEY: ${{ secrets.SEGMENTS_KEY }} 35 | GITHUB_REF: ${{ github.ref }} 36 | run: | 37 | paver package.sentry=${SENTRY_KEY} package.segments=$SEGMENTS_KEY package.version=${GITHUB_REF##*/} package 38 | 39 | - name: Create/update release on GitHub 40 | uses: ncipollo/release-action@v1.8.8 41 | with: 42 | token: ${{ secrets.GITHUB_TOKEN }} 43 | allowUpdates: true 44 | omitNameDuringUpdate: true 45 | artifacts: "planet_explorer.zip" 46 | 47 | - name: Checkout code 48 | uses: actions/checkout@v2 49 | with: 50 | ref: release 51 | - name: Update custom plugin repository to include latest release 52 | run: | 53 | paver -v generate_plugin_repo_xml 54 | echo -e "\n" >> docs/repository/plugins.xml 55 | git config --global user.name "github-actions[bot]" 56 | git config --global user.email "github-actions[bot]@users.noreply.github.com" 57 | git config --global --add safe.directory /__w/qgis-planet-plugin/qgis-planet-plugin 58 | 59 | git add -A 60 | git commit -m "Update plugins.xml" 61 | git push --force origin release 62 | -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | name: Testing 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - '**' 10 | 11 | jobs: 12 | # Run unit tests 13 | test: 14 | runs-on: ubuntu-20.04 15 | strategy: 16 | matrix: 17 | qgis_version: [release-3_10, release-3_16, release-3_24, release-3_26, release-3_28] 18 | env: 19 | QGIS_TEST_VERSION: ${{ matrix.qgis_version }} 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v2 23 | with: 24 | submodules: recursive 25 | - name: Test on QGIS 26 | env: 27 | PLANET_USER: ${{ secrets.PLANET_USER }} 28 | PLANET_PASSWORD: ${{ secrets.PLANET_PASSWORD }} 29 | run: docker-compose -f .docker/docker-compose.gh.yml run -e PLANET_USER=${PLANET_USER} -e PLANET_PASSWORD=${PLANET_PASSWORD} qgis /usr/src/.docker/run-docker-tests.sh 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__ 2 | **/.vscode 3 | planet_explorer/extlibs 4 | pyq5-examples 5 | planet_explorer*.zip 6 | bundles-util.js 7 | qgis_resources.py 8 | venv 9 | .idea 10 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | --- 4 | repos: 5 | - repo: https://github.com/pre-commit/pre-commit-hooks 6 | rev: v4.3.0 7 | hooks: 8 | - id: end-of-file-fixer 9 | exclude: ^(planet_explorer/tests/data/) 10 | - id: trailing-whitespace 11 | exclude: ^(planet_explorer/tests/data/) 12 | - id: check-yaml 13 | - id: check-json 14 | exclude: ^(planet_explorer/planet_api/request-result-samples/) 15 | - repo: https://github.com/psf/black 16 | rev: 22.6.0 17 | hooks: 18 | - id: black 19 | - repo: https://github.com/PyCQA/flake8 20 | rev: 3.9.2 21 | hooks: 22 | - id: flake8 23 | language_version: python3 24 | args: ['--extend-ignore=E203'] 25 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | Larry Shaffer 2 | Erik Friesen 3 | Víctor Olaya 4 | -------------------------------------------------------------------------------- /CONTRIBUTE.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | Contributing to Planet Explorer QGIS plugin 5 | =========================================== 6 | 7 | Planet Explorer QGIS plugin is an open source project and we appreciate contributions very much. 8 | 9 | Proper formatting 10 | ----------------- 11 | 12 | Before making a pull request, please make sure your code is properly formatted. 13 | To check for formatting errors use 14 | 15 | paver pep8 16 | 17 | to automatically format you code run following command **before** issuing 18 | `git commit` 19 | 20 | paver autopep8 21 | -------------------------------------------------------------------------------- /planet_explorer/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | *************************************************************************** 4 | __init__.py 5 | --------------------- 6 | Date : August 2019 7 | Copyright : (C) 2019 Planet Inc, https://planet.com 8 | *************************************************************************** 9 | * * 10 | * This program is free software; you can redistribute it and/or modify * 11 | * it under the terms of the GNU General Public License as published by * 12 | * the Free Software Foundation; either version 2 of the License, or * 13 | * (at your option) any later version. * 14 | * * 15 | *************************************************************************** 16 | """ 17 | from __future__ import absolute_import 18 | 19 | __author__ = "Planet Federal" 20 | __date__ = "August 2019" 21 | __copyright__ = "(C) 2019 Planet Inc, https://planet.com" 22 | 23 | # This will get replaced with a git SHA1 when you do a git archive 24 | __revision__ = "$Format:%H$" 25 | 26 | import os 27 | import sys 28 | 29 | extlibs = os.path.abspath(os.path.dirname(__file__) + "/extlibs") 30 | if os.path.exists(extlibs) and extlibs not in sys.path: 31 | sys.path.insert(0, extlibs) 32 | 33 | 34 | # noinspection PyPep8Naming 35 | def classFactory(iface): 36 | from planet_explorer.pe_plugin import PlanetExplorer 37 | 38 | return PlanetExplorer(iface) 39 | -------------------------------------------------------------------------------- /planet_explorer/gui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetlabs/qgis-planet-plugin/7c12eb6484758be4224d301bd70df6a0b54f94ca/planet_explorer/gui/__init__.py -------------------------------------------------------------------------------- /planet_explorer/gui/pe_daily_images_preview_config_dialog.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from qgis.PyQt import uic 4 | from qgis.PyQt.QtWidgets import QFileDialog 5 | 6 | WIDGET, BASE = uic.loadUiType( 7 | os.path.join( 8 | os.path.dirname(os.path.dirname(__file__)), 9 | "ui", 10 | "pe_daily_images_preview_config_dialog.ui", 11 | ) 12 | ) 13 | 14 | 15 | class DailyImagesPreviewConfigDialog(BASE, WIDGET): 16 | def __init__(self, parent=None): 17 | super(DailyImagesPreviewConfigDialog, self).__init__(parent) 18 | self.footprintsFilename = None 19 | self.layerName = None 20 | self.setupUi(self) 21 | 22 | self.btnBrowse.clicked.connect(self.browse) 23 | self.chkAddToCatalog.stateChanged.connect(self.add_to_catalog_changed) 24 | self.radioGpkgLayer.toggled.connect(self.radio_changed) 25 | 26 | self.radio_changed() 27 | self.add_to_catalog_changed() 28 | 29 | def radio_changed(self): 30 | self.txtFilename.setEnabled(self.radioGpkgLayer.isChecked()) 31 | 32 | def add_to_catalog_changed(self): 33 | self.txtLayerName.setEnabled(self.chkAddToCatalog.isChecked()) 34 | 35 | def browse(self): 36 | filename, _ = QFileDialog.getSaveFileName( 37 | self, "Footprints filename", "", "GPKG Files (*.gpkg)" 38 | ) 39 | if filename: 40 | self.txtFilename.setText(filename) 41 | 42 | def accept(self): 43 | if self.radioGpkgLayer.isChecked(): 44 | filename = self.txtFilename.text() 45 | if filename: 46 | self.footprintsFilename = filename 47 | else: 48 | self.txtFilename.setStyleSheet( 49 | "QLineEdit { background: rgba(255, 0, 0, 150); }" 50 | ) 51 | return 52 | if self.chkAddToCatalog.isChecked(): 53 | name = self.txtLayerName.text() 54 | if name: 55 | self.layerName = name 56 | else: 57 | self.txtLayerName.setStyleSheet( 58 | "QLineEdit { background: rgba(255, 0, 0, 150); }" 59 | ) 60 | return 61 | super().accept() 62 | -------------------------------------------------------------------------------- /planet_explorer/gui/pe_gui_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | *************************************************************************** 4 | pe_gui_utils.py 5 | --------------------- 6 | Date : September 2019 7 | Copyright : (C) 2019 Planet Inc, https://planet.com 8 | *************************************************************************** 9 | * * 10 | * This program is free software; you can redistribute it and/or modify * 11 | * it under the terms of the GNU General Public License as published by * 12 | * the Free Software Foundation; either version 2 of the License, or * 13 | * (at your option) any later version. * 14 | * * 15 | *************************************************************************** 16 | """ 17 | __author__ = "Planet Federal" 18 | __date__ = "September 2019" 19 | __copyright__ = "(C) 2019 Planet Inc, https://planet.com" 20 | 21 | # This will get replaced with a git SHA1 when you do a git archive 22 | __revision__ = "$Format:%H$" 23 | 24 | import logging 25 | import os 26 | 27 | from qgis.PyQt.QtCore import QEvent, Qt, pyqtSignal 28 | from qgis.PyQt.QtGui import QMouseEvent 29 | from qgis.PyQt.QtWidgets import QApplication, QLabel, QToolTip 30 | 31 | LOG_LEVEL = os.environ.get("PYTHON_LOG_LEVEL", "WARNING").upper() 32 | logging.basicConfig(level=LOG_LEVEL) 33 | log = logging.getLogger(__name__) 34 | 35 | plugin_path = os.path.split(os.path.dirname(__file__))[0] 36 | 37 | 38 | class PlanetClickableLabel(QLabel): 39 | 40 | clicked = pyqtSignal() 41 | 42 | def __init__(self, parent=None): 43 | """ 44 | Clickable QLabel 45 | """ 46 | super().__init__(parent=parent) 47 | 48 | self._show_tooltip_on_hover = False 49 | self.setAttribute(Qt.WA_Hover) 50 | 51 | def set_show_tooltip_on_hover(self, show) -> None: 52 | self._show_tooltip_on_hover = show 53 | 54 | def shows_tooltip_on_hover(self) -> bool: 55 | return self._show_tooltip_on_hover 56 | 57 | def mousePressEvent(self, ev: QMouseEvent) -> None: 58 | self.clicked.emit() 59 | 60 | def event(self, event: QEvent) -> bool: 61 | if self._show_tooltip_on_hover and self.toolTip(): 62 | if event.type() == QEvent.HoverEnter: 63 | QToolTip.showText(self.mapToGlobal(event.pos()), self.toolTip(), self) 64 | event.accept() 65 | 66 | return QLabel.event(self, event) 67 | 68 | 69 | def waitcursor(method): 70 | def func(*args, **kw): 71 | try: 72 | QApplication.setOverrideCursor(Qt.WaitCursor) 73 | return method(*args, **kw) 74 | except Exception as ex: 75 | raise ex 76 | finally: 77 | QApplication.restoreOverrideCursor() 78 | 79 | return func 80 | -------------------------------------------------------------------------------- /planet_explorer/gui/pe_legacy_warning_dialog.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from qgis.PyQt import uic 4 | from qgis.PyQt.QtCore import pyqtSignal, Qt 5 | 6 | WIDGET, BASE = uic.loadUiType( 7 | os.path.join( 8 | os.path.dirname(os.path.dirname(__file__)), "ui", "pe_legacy_warning_dialog.ui" 9 | ) 10 | ) 11 | 12 | 13 | class LegacyWarningDialog(BASE, WIDGET): 14 | 15 | updateLegacySearch = pyqtSignal() 16 | 17 | def __init__(self, request, parent=None): 18 | super(LegacyWarningDialog, self).__init__(parent) 19 | self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint) 20 | self.setupUi(self) 21 | self.btnUpdate.clicked.connect(self.accept) 22 | self.btnContinue.clicked.connect(self.reject) 23 | 24 | sources = request["item_types"] 25 | self.label4Bands.setVisible("PSScene4Band" in sources) 26 | self.label3Bands.setVisible("PSScene3Band" in sources) 27 | -------------------------------------------------------------------------------- /planet_explorer/gui/pe_legacy_warning_widget.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from qgis.PyQt import uic 4 | from qgis.PyQt.QtCore import pyqtSignal 5 | from qgis.PyQt.QtGui import QPalette 6 | 7 | from planet_explorer.pe_utils import PLANET_COLOR, open_link_with_browser 8 | 9 | WIDGET, BASE = uic.loadUiType( 10 | os.path.join( 11 | os.path.dirname(os.path.dirname(__file__)), "ui", "pe_legacy_warning_widget.ui" 12 | ) 13 | ) 14 | 15 | text_without_id = """ 16 |

17 | Your saved search includes legacy imagery types

18 |

PSScene3Bands, PSScene4Bands

19 | """ 20 | text_with_id = """ 21 |

22 | Legacy search filters are disabled because we've updated PlanetScope items to PSScene

23 | """ 24 | 25 | 26 | class LegacyWarningWidget(BASE, WIDGET): 27 | 28 | updateLegacySearch = pyqtSignal() 29 | 30 | def __init__(self, parent=None): 31 | super(LegacyWarningWidget, self).__init__(parent) 32 | self.setupUi(self) 33 | palette = self.btnUpdate.palette() 34 | palette.setColor(QPalette.Button, PLANET_COLOR) 35 | self.btnUpdate.setPalette(palette) 36 | self.btnUpdate.clicked.connect(self.update_search) 37 | self.labelLink.linkActivated.connect(self.link_clicked) 38 | 39 | def link_clicked(self): 40 | url = "https://developers.planet.com/docs/data/psscene" 41 | open_link_with_browser(url) 42 | 43 | def update_search(self): 44 | self.updateLegacySearch.emit() 45 | 46 | def set_has_image_id(self, has_id): 47 | if has_id: 48 | self.textBrowser.setHtml(text_with_id) 49 | self.btnUpdate.setText("Clear item Ids to enable filters") 50 | else: 51 | self.textBrowser.setHtml(text_without_id) 52 | self.btnUpdate.setText("Click here to update search") 53 | -------------------------------------------------------------------------------- /planet_explorer/gui/pe_results_configuration_dialog.py: -------------------------------------------------------------------------------- 1 | import enum 2 | import os 3 | 4 | from qgis.PyQt import uic 5 | from qgis.PyQt.QtCore import Qt 6 | from qgis.PyQt.QtWidgets import QDialogButtonBox 7 | 8 | 9 | class PlanetNodeMetadata(enum.Enum): 10 | CLOUD_PERCENTAGE = "cloud_percent" 11 | GROUND_SAMPLE_DISTANCE = "gsd" 12 | GROUND_CONTROL = "ground_control" 13 | OFF_NADIR_ANGLE = "view_angle" 14 | INSTRUMENT = "instrument" 15 | AREA_COVER = "area_cover" 16 | SATELLITE_ID = "satellite_id" 17 | SUN_AZIMUTH = "sun_azimuth" 18 | SUN_ELEVATION = "sun_elevation" 19 | QUALITY_CATEGORY = "quality_category" 20 | PUBLISHING_STAGE = "publishing_stage" 21 | 22 | 23 | WIDGET, BASE = uic.loadUiType( 24 | os.path.join( 25 | os.path.dirname(os.path.dirname(__file__)), 26 | "ui", 27 | "results_configuration_dialog.ui", 28 | ) 29 | ) 30 | 31 | 32 | class ResultsConfigurationDialog(BASE, WIDGET): 33 | def __init__(self, selection, parent=None): 34 | super(ResultsConfigurationDialog, self).__init__(parent) 35 | self.selection = selection 36 | 37 | self.setupUi(self) 38 | 39 | self.checkboxes = { 40 | PlanetNodeMetadata.CLOUD_PERCENTAGE: self.chkCloudPercentage, 41 | PlanetNodeMetadata.GROUND_SAMPLE_DISTANCE: self.chkGroundSampleDistance, 42 | PlanetNodeMetadata.GROUND_CONTROL: self.chkGroundControl, 43 | PlanetNodeMetadata.OFF_NADIR_ANGLE: self.chkOffNadirAngle, 44 | PlanetNodeMetadata.INSTRUMENT: self.chkInstrument, 45 | PlanetNodeMetadata.AREA_COVER: self.chkAreaCover, 46 | PlanetNodeMetadata.SATELLITE_ID: self.chkSatelliteId, 47 | PlanetNodeMetadata.SUN_AZIMUTH: self.chkSunAzimuth, 48 | PlanetNodeMetadata.SUN_ELEVATION: self.chkSunElevation, 49 | PlanetNodeMetadata.QUALITY_CATEGORY: self.chkQualityCategory, 50 | PlanetNodeMetadata.PUBLISHING_STAGE: self.chkPublishStage, 51 | } 52 | 53 | for chk in self.checkboxes.values(): 54 | chk.clicked.connect(self.selection_changed) 55 | self._set_selected() 56 | 57 | self.btnRestoreDefaults.clicked.connect(self.restore_default) 58 | self.buttonBox.button(QDialogButtonBox.Ok).clicked.connect(self.accepted) 59 | self.buttonBox.button(QDialogButtonBox.Cancel).clicked.connect(self.rejected) 60 | 61 | def selection_changed(self, state): 62 | if len(self.selection) > 3 and state != Qt.Unchecked: 63 | self._set_selected() 64 | else: 65 | self.selection = [] 66 | for key, chk in self.checkboxes.items(): 67 | if chk.isChecked(): 68 | self.selection.append(key) 69 | 70 | def restore_default(self): 71 | self.selection = [ 72 | PlanetNodeMetadata.CLOUD_PERCENTAGE, 73 | PlanetNodeMetadata.GROUND_SAMPLE_DISTANCE, 74 | ] 75 | self._set_selected() 76 | 77 | def _set_selected(self): 78 | for key, chk in self.checkboxes.items(): 79 | chk.setChecked(key in self.selection) 80 | -------------------------------------------------------------------------------- /planet_explorer/gui/pe_show_curl_dialog.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | from qgis.PyQt import uic 5 | from qgis.PyQt.QtGui import QGuiApplication 6 | 7 | from ..pe_analytics import analytics_track, CURL_REQUEST_COPIED 8 | from ..planet_api import PlanetClient 9 | 10 | python_template = """ 11 | import json 12 | import requests 13 | from requests.auth import HTTPBasicAuth 14 | 15 | PLANET_API_KEY = "%s" 16 | 17 | request = %s 18 | 19 | # fire off the POST request 20 | search_result = \ 21 | requests.post( 22 | 'https://api.planet.com/data/v1/quick-search', 23 | auth=HTTPBasicAuth(PLANET_API_KEY, ''), 24 | json=request) 25 | 26 | print(json.dumps(search_result.json(), indent=2)) 27 | """ 28 | 29 | curl_template = ( 30 | """$ curl -u '%s: ' -d '%s' -H "Content-Type: application/json" """ 31 | """-X POST https://api.planet.com/data/v1/quick-search""" 32 | ) 33 | 34 | WIDGET, BASE = uic.loadUiType( 35 | os.path.join( 36 | os.path.dirname(os.path.dirname(__file__)), "ui", "show_curl_dialog.ui" 37 | ) 38 | ) 39 | 40 | 41 | class ShowCurlDialog(BASE, WIDGET): 42 | def __init__(self, request, parent=None): 43 | super(ShowCurlDialog, self).__init__(parent) 44 | self.request = request 45 | self.setupUi(self) 46 | 47 | self.btnCopy.clicked.connect(self.copyClicked) 48 | self.btnClose.clicked.connect(self.close) 49 | self.comboType.currentIndexChanged.connect(self.setText) 50 | 51 | self.setText() 52 | 53 | def setText(self): 54 | if self.comboType.currentText() == "cURL": 55 | txt = curl_template % ( 56 | PlanetClient.getInstance().api_key(), 57 | json.dumps(self.request), 58 | ) 59 | else: 60 | txt = python_template % ( 61 | PlanetClient.getInstance().api_key(), 62 | json.dumps(self.request, indent=4), 63 | ) 64 | self.textBrowser.setPlainText(txt) 65 | 66 | def copyClicked(self): 67 | clipboard = QGuiApplication.clipboard() 68 | clipboard.setText(self.textBrowser.toPlainText()) 69 | analytics_track(CURL_REQUEST_COPIED) 70 | -------------------------------------------------------------------------------- /planet_explorer/gui/pe_thumbnails.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | *************************************************************************** 4 | pe_thumbnails.py 5 | --------------------- 6 | Date : September 2019 7 | Author : Planet Federal 8 | Copyright : (C) 2019 Planet Inc, https://planet.com 9 | *************************************************************************** 10 | * * 11 | * This program is free software; you can redistribute it and/or modify * 12 | * it under the terms of the GNU General Public License as published by * 13 | * the Free Software Foundation; either version 2 of the License, or * 14 | * (at your option) any later version. * 15 | * * 16 | *************************************************************************** 17 | """ 18 | __author__ = "Planet Federal" 19 | __date__ = "September 2019" 20 | __copyright__ = "(C) 2019 Planet Inc, https://planet.com" 21 | 22 | # This will get replaced with a git SHA1 when you do a git archive 23 | __revision__ = "$Format:%H$" 24 | 25 | from collections import defaultdict 26 | 27 | from qgis.PyQt.QtNetwork import QNetworkAccessManager, QNetworkReply, QNetworkRequest 28 | from qgis.core import QgsCoordinateReferenceSystem, QgsCoordinateTransform, QgsProject 29 | from qgis.PyQt.QtCore import Qt, QUrl 30 | from qgis.PyQt.QtGui import QImage, QPainter, QPixmap 31 | 32 | from ..pe_utils import qgsgeometry_from_geojson 33 | 34 | 35 | class ThumbnailManager: 36 | def __init__(self): 37 | self.nam = QNetworkAccessManager() 38 | self.nam.finished.connect(self.thumbnail_downloaded) 39 | self.thumbnails = {} 40 | self.widgets = defaultdict(list) 41 | 42 | def download_thumbnail(self, url, widget): 43 | if url in self.thumbnails: 44 | widget.set_thumbnail(self.thumbnails[url]) 45 | else: 46 | self.widgets[url].append(widget) 47 | self.nam.get(QNetworkRequest(QUrl(url))) 48 | 49 | def thumbnail_downloaded(self, reply): 50 | if reply.error() == QNetworkReply.NoError: 51 | url = reply.url().toString() 52 | img = QImage() 53 | img.loadFromData(reply.readAll()) 54 | self.thumbnails[url] = img 55 | for w in self.widgets[url]: 56 | try: 57 | w.set_thumbnail(img) 58 | except Exception: 59 | # the widget might have been deleted 60 | pass 61 | 62 | 63 | _thumbnailManager = ThumbnailManager() 64 | 65 | 66 | def download_thumbnail(url, widget): 67 | _thumbnailManager.download_thumbnail(url, widget) 68 | 69 | 70 | def createCompoundThumbnail(_bboxes, thumbnails): 71 | bboxes = [] 72 | transform = QgsCoordinateTransform( 73 | QgsCoordinateReferenceSystem("EPSG:4326"), 74 | QgsCoordinateReferenceSystem("EPSG:3857"), 75 | QgsProject.instance(), 76 | ) 77 | for box in _bboxes: 78 | rect4326 = qgsgeometry_from_geojson(box).boundingBox() 79 | rect = transform.transformBoundingBox(rect4326) 80 | bboxes.append( 81 | [rect.xMinimum(), rect.yMinimum(), rect.xMaximum(), rect.yMaximum()] 82 | ) 83 | globalbox = ( 84 | min([v[0] for v in bboxes]), 85 | min([v[1] for v in bboxes]), 86 | max([v[2] for v in bboxes]), 87 | max([v[3] for v in bboxes]), 88 | ) 89 | SIZE = 256 90 | globalwidth = globalbox[2] - globalbox[0] 91 | globalheight = globalbox[3] - globalbox[1] 92 | pixmap = QPixmap(SIZE, SIZE) 93 | pixmap.fill(Qt.transparent) 94 | painter = QPainter(pixmap) 95 | try: 96 | for i, thumbnail in enumerate(thumbnails): 97 | box = bboxes[i] 98 | width = box[2] - box[0] 99 | height = box[3] - box[1] 100 | if width > height: 101 | offsety = (width - height) / 2 102 | offsetx = 0 103 | else: 104 | offsetx = (height - width) / 2 105 | offsety = 0 106 | x = int((box[0] - offsetx - globalbox[0]) / globalwidth * SIZE) 107 | y = int((globalbox[3] - box[3] - offsety) / globalheight * SIZE) 108 | outputwidth = int((width + 2 * offsetx) / globalwidth * SIZE) 109 | outputheight = int((height + 2 * offsety) / globalheight * SIZE) 110 | painter.drawPixmap(x, y, outputwidth, outputheight, thumbnail) 111 | except Exception: 112 | """ 113 | Unexpected values for bboxes might cause uneexpected errors. We just ignore 114 | them and return an empty image in that case 115 | """ 116 | finally: 117 | painter.end() 118 | return pixmap 119 | -------------------------------------------------------------------------------- /planet_explorer/gui/resources/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetlabs/qgis-planet-plugin/7c12eb6484758be4224d301bd70df6a0b54f94ca/planet_explorer/gui/resources/__init__.py -------------------------------------------------------------------------------- /planet_explorer/gui/resources/intermediate-ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIGEzCCA/ugAwIBAgIQfVtRJrR2uhHbdBYLvFMNpzANBgkqhkiG9w0BAQwFADCB 3 | iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl 4 | cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV 5 | BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTgx 6 | MTAyMDAwMDAwWhcNMzAxMjMxMjM1OTU5WjCBjzELMAkGA1UEBhMCR0IxGzAZBgNV 7 | BAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEYMBYGA1UE 8 | ChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQDEy5TZWN0aWdvIFJTQSBEb21haW4g 9 | VmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC 10 | AQ8AMIIBCgKCAQEA1nMz1tc8INAA0hdFuNY+B6I/x0HuMjDJsGz99J/LEpgPLT+N 11 | TQEMgg8Xf2Iu6bhIefsWg06t1zIlk7cHv7lQP6lMw0Aq6Tn/2YHKHxYyQdqAJrkj 12 | eocgHuP/IJo8lURvh3UGkEC0MpMWCRAIIz7S3YcPb11RFGoKacVPAXJpz9OTTG0E 13 | oKMbgn6xmrntxZ7FN3ifmgg0+1YuWMQJDgZkW7w33PGfKGioVrCSo1yfu4iYCBsk 14 | Haswha6vsC6eep3BwEIc4gLw6uBK0u+QDrTBQBbwb4VCSmT3pDCg/r8uoydajotY 15 | uK3DGReEY+1vVv2Dy2A0xHS+5p3b4eTlygxfFQIDAQABo4IBbjCCAWowHwYDVR0j 16 | BBgwFoAUU3m/WqorSs9UgOHYm8Cd8rIDZsswHQYDVR0OBBYEFI2MXsRUrYrhd+mb 17 | +ZsF4bgBjWHhMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMB0G 18 | A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAbBgNVHSAEFDASMAYGBFUdIAAw 19 | CAYGZ4EMAQIBMFAGA1UdHwRJMEcwRaBDoEGGP2h0dHA6Ly9jcmwudXNlcnRydXN0 20 | LmNvbS9VU0VSVHJ1c3RSU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDB2Bggr 21 | BgEFBQcBAQRqMGgwPwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRydXN0LmNv 22 | bS9VU0VSVHJ1c3RSU0FBZGRUcnVzdENBLmNydDAlBggrBgEFBQcwAYYZaHR0cDov 23 | L29jc3AudXNlcnRydXN0LmNvbTANBgkqhkiG9w0BAQwFAAOCAgEAMr9hvQ5Iw0/H 24 | ukdN+Jx4GQHcEx2Ab/zDcLRSmjEzmldS+zGea6TvVKqJjUAXaPgREHzSyrHxVYbH 25 | 7rM2kYb2OVG/Rr8PoLq0935JxCo2F57kaDl6r5ROVm+yezu/Coa9zcV3HAO4OLGi 26 | H19+24rcRki2aArPsrW04jTkZ6k4Zgle0rj8nSg6F0AnwnJOKf0hPHzPE/uWLMUx 27 | RP0T7dWbqWlod3zu4f+k+TY4CFM5ooQ0nBnzvg6s1SQ36yOoeNDT5++SR2RiOSLv 28 | xvcRviKFxmZEJCaOEDKNyJOuB56DPi/Z+fVGjmO+wea03KbNIaiGCpXZLoUmGv38 29 | sbZXQm2V0TP2ORQGgkE49Y9Y3IBbpNV9lXj9p5v//cWoaasm56ekBYdbqbe4oyAL 30 | l6lFhd2zi+WJN44pDfwGF/Y4QA5C5BIG+3vzxhFoYt/jmPQT2BVPi7Fp2RBgvGQq 31 | 6jG35LWjOhSbJuMLe/0CjraZwTiXWTb2qHSihrZe68Zk6s+go/lunrotEbaGmAhY 32 | LcmsJWTyXnW0OMGuf1pGg+pRyrbxmRE1a6Vqe8YAsOf4vmSyrcjC8azjUeqkk+B5 33 | yOGBQMkKW+ESPMFgKuOXwIlCypTPRpgSabuY0MLTDXJLR27lk8QyKGOHQ+SwMj4K 34 | 00u/I5sUKUErmgQfky3xxzlIPK1aEn8= 35 | -----END CERTIFICATE----- 36 | -------------------------------------------------------------------------------- /planet_explorer/pe_functions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | *************************************************************************** 4 | extent_maptool.py 5 | --------------------- 6 | Date : March 2017, August 2019 7 | Author : Alex Bruy, Planet Federal 8 | Copyright : (C) 2017 Boundless, http://boundlessgeo.com 9 | : (C) 2019 Planet Inc, https://planet.com 10 | *************************************************************************** 11 | * * 12 | * This program is free software; you can redistribute it and/or modify * 13 | * it under the terms of the GNU General Public License as published by * 14 | * the Free Software Foundation; either version 2 of the License, or * 15 | * (at your option) any later version. * 16 | * * 17 | *************************************************************************** 18 | """ 19 | __author__ = "Planet Federal" 20 | __date__ = "August 2019" 21 | __copyright__ = "(C) 2019 Planet Inc, https://planet.com" 22 | 23 | # This will get replaced with a git SHA1 when you do a git archive 24 | __revision__ = "$Format:%H$" 25 | 26 | from qgis.core import QgsExpression 27 | from qgis.utils import qgsfunction 28 | 29 | 30 | # noinspection PyPep8Naming,PyBroadException 31 | @qgsfunction(1, "PlanetExplorer") 32 | def metadataValue(values, feature, parent): 33 | """Returns metadata value from the "metadata" field of the 34 | Planet Inc catalog layer. 35 | 36 |

Syntax

37 |

metadataValue(metadata)

38 |

Arguments

39 |

metadata → a string. Must be a valid name of the 40 | metadata entry.

41 |

Example

42 |

43 | metadataValue('provider')

44 | """ 45 | fieldName = "metadata" 46 | idx = feature.fieldNameIndex(fieldName) 47 | if idx == -1: 48 | parent.setEvalErrorString("Required '{0}' field not found".format(fieldName)) 49 | return None 50 | 51 | text = feature[fieldName] 52 | if text is None or text == "": 53 | return None 54 | 55 | metadata = {k: v for k, v in (i.split("=") for i in text.split("\n"))} 56 | if values[0] not in metadata: 57 | return None 58 | 59 | value = metadata[values[0]] 60 | try: 61 | v = int(value) 62 | return v 63 | except ValueError: 64 | return value 65 | 66 | 67 | functions = [metadataValue] 68 | 69 | 70 | # noinspection PyPep8Naming,PyArgumentList 71 | def registerFunctions(): 72 | for func in functions: 73 | if QgsExpression.registerFunction(func): 74 | yield func.name() 75 | 76 | 77 | # noinspection PyPep8Naming,PyArgumentList 78 | def unregisterFunctions(): 79 | for func in functions: 80 | QgsExpression.unregisterFunction(func.name()) 81 | -------------------------------------------------------------------------------- /planet_explorer/planet_api/__init__.py: -------------------------------------------------------------------------------- 1 | from .p_client import API_KEY_DEFAULT, LoginException, PlanetClient 2 | 3 | __all_ = [ 4 | PlanetClient, 5 | API_KEY_DEFAULT, 6 | LoginException, 7 | ] 8 | -------------------------------------------------------------------------------- /planet_explorer/planet_api/p_quad_orders.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import json 3 | import os 4 | import uuid 5 | 6 | from planet.api.models import MosaicQuads 7 | from qgis.core import QgsApplication 8 | 9 | from ..pe_utils import orders_download_folder, user_agent 10 | from .p_client import PlanetClient 11 | 12 | 13 | class OrderAlreadyExistsException(Exception): 14 | pass 15 | 16 | 17 | def _quad_orders_file(): 18 | folder = os.path.join( 19 | os.path.dirname(QgsApplication.qgisUserDatabaseFilePath()), "planetexplorer" 20 | ) 21 | os.makedirs(folder, exist_ok=True) 22 | file = os.path.join(folder, "quadorders.json") 23 | return file 24 | 25 | 26 | NAME = "name" 27 | DATE = "date" 28 | QUADS = "quads" 29 | LOAD_AS_VIRTUAL = "load_as_virtual" 30 | DESCRIPTION = "description" 31 | MOSAICS = "mosaics" 32 | 33 | ID = "id" 34 | LINKS = "_links" 35 | DOWNLOAD = "download" 36 | 37 | 38 | def quad_orders(): 39 | if os.path.exists(_quad_orders_file()): 40 | try: 41 | orders = [] 42 | with open(_quad_orders_file()) as f: 43 | definitions = json.load(f) 44 | for orderdef in definitions: 45 | if QUADS in orderdef: 46 | order = QuadOrder( 47 | orderdef[NAME], 48 | orderdef[DESCRIPTION], 49 | orderdef[QUADS], 50 | orderdef[LOAD_AS_VIRTUAL], 51 | orderdef[DATE], 52 | ) 53 | else: 54 | order = QuadCompleteOrder( 55 | orderdef[NAME], 56 | orderdef[DESCRIPTION], 57 | orderdef[MOSAICS], 58 | orderdef[LOAD_AS_VIRTUAL], 59 | orderdef[DATE], 60 | ) 61 | orders.append(order) 62 | except Exception: 63 | pass # will return an empty array if the file is corrupted 64 | return orders 65 | else: 66 | return [] 67 | 68 | 69 | def _add_order(order): 70 | all_orders = [order] 71 | all_orders.extend(quad_orders()) 72 | with open(_quad_orders_file(), "w") as f: 73 | json.dump( 74 | all_orders, 75 | f, 76 | default=lambda x: { 77 | k: v for k, v in x.__dict__.items() if not k.startswith("_") 78 | }, 79 | ) 80 | 81 | 82 | def create_quad_order_from_quads(name, description, quads, load_as_virtual): 83 | order = QuadOrder(name, description, quads, load_as_virtual) 84 | _add_order(order) 85 | 86 | 87 | def create_quad_order_from_mosaics(name, description, mosaics, load_as_virtual): 88 | order = QuadCompleteOrder(name, description, mosaics, load_as_virtual) 89 | _add_order(order) 90 | 91 | 92 | class QuadOrder: 93 | def __init__(self, name, description, quads, load_as_virtual, date=None): 94 | self.quads = quads 95 | self.load_as_virtual = load_as_virtual 96 | self.name = name 97 | self.description = description 98 | self.date = date or (datetime.date.today().isoformat()) 99 | self._id = uuid.uuid3(uuid.NAMESPACE_DNS, name) 100 | 101 | def locations(self): 102 | locations = {} 103 | for mosaic, mosaicquads in self.quads.items(): 104 | mosaiclocations = [] 105 | for quad in mosaicquads: 106 | mosaiclocations.append( 107 | (f"{quad[LINKS][DOWNLOAD]}&ua={user_agent()}", quad[ID]) 108 | ) 109 | locations[mosaic] = mosaiclocations 110 | return locations 111 | 112 | def download_folder(self): 113 | return os.path.join(orders_download_folder(), "basemaps", self.name) 114 | 115 | def downloaded(self): 116 | return os.path.exists(self.download_folder()) 117 | 118 | def id(self): 119 | return self._id 120 | 121 | def numquads(self): 122 | return sum([len(m) for m in self.quads.values()]) 123 | 124 | 125 | class QuadCompleteOrder(QuadOrder): 126 | def __init__(self, name, description, mosaics, load_as_virtual, date=None): 127 | self.mosaics = mosaics 128 | self.load_as_virtual = load_as_virtual 129 | self.name = name 130 | self.description = description 131 | self.date = date or (datetime.datetime.now().replace(microsecond=0).isoformat()) 132 | self._id = uuid.uuid4() 133 | 134 | def locations(self): 135 | p_client = PlanetClient.getInstance() 136 | locations = {} 137 | for mosaic in self.mosaics: 138 | json_quads = [] 139 | quads = p_client.get_quads_for_mosaic(mosaic, minimal=True) 140 | for page in quads.iter(): 141 | json_quads.extend(page.get().get(MosaicQuads.ITEM_KEY)) 142 | locations[mosaic[NAME]] = [ 143 | (quad[LINKS][DOWNLOAD], quad[ID]) for quad in json_quads 144 | ] 145 | return locations 146 | 147 | def id(self): 148 | return self._id 149 | 150 | def numquads(self): 151 | return f"{len(self.mosaics)} complete mosaics" 152 | -------------------------------------------------------------------------------- /planet_explorer/planet_api/p_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | *************************************************************************** 4 | p_utils.py 5 | --------------------- 6 | Date : September 2019 7 | Copyright : (C) 2019 Planet Inc, https://planet.com 8 | *************************************************************************** 9 | * * 10 | * This program is free software; you can redistribute it and/or modify * 11 | * it under the terms of the GNU General Public License as published by * 12 | * the Free Software Foundation; either version 2 of the License, or * 13 | * (at your option) any later version. * 14 | * * 15 | *************************************************************************** 16 | """ 17 | __author__ = "Planet Federal" 18 | __date__ = "September 2019" 19 | __copyright__ = "(C) 2019 Planet Inc, https://planet.com" 20 | 21 | # This will get replaced with a git SHA1 when you do a git archive 22 | __revision__ = "$Format:%H$" 23 | 24 | import json 25 | import logging 26 | import os 27 | from typing import Optional, Union 28 | 29 | from planet.api.utils import geometry_from_json 30 | 31 | LOG_LEVEL = os.environ.get("PYTHON_LOG_LEVEL", "WARNING").upper() 32 | logging.basicConfig(level=LOG_LEVEL) 33 | log = logging.getLogger(__name__) 34 | 35 | 36 | def json_str_or_obj_to_obj(json_type: Union[str, dict]) -> Optional[dict]: 37 | """ 38 | :param json_type: JSON as string or `json` object 39 | :type json_type: str | dict 40 | :rtype: dict | None 41 | """ 42 | json_obj = None 43 | if isinstance(json_type, (str, bytes, bytearray)): 44 | try: 45 | json_obj = json.loads(json_type) 46 | except TypeError: 47 | json_obj = None 48 | log.debug("JSON input type invalid") 49 | except ValueError: 50 | json_obj = None 51 | log.debug("JSON string invalid") 52 | elif isinstance(json_type, dict): 53 | json_obj = json_type 54 | 55 | if not json_obj: 56 | log.debug("JSON Python object invalid") 57 | return None 58 | 59 | return json_obj 60 | 61 | 62 | def geometry_from_json_str_or_obj(json_type: Union[str, dict]) -> Optional[dict]: 63 | """ 64 | :param json_type: GeoJSON feature, feature collection or geometry as 65 | string or `json` object 66 | :type json_type: str | dict 67 | :rtype: dict | None 68 | """ 69 | json_obj = json_str_or_obj_to_obj(json_type) 70 | 71 | if json_obj is None: 72 | return None 73 | 74 | # Strip outer Feature or FeatureCollection 75 | json_geom = geometry_from_json(json_obj) 76 | 77 | if not json_geom: 78 | log.debug("GeoJSON geometry invalid") 79 | 80 | return json_geom 81 | 82 | 83 | def geometry_from_request(request: Union[str, dict]) -> Optional[dict]: 84 | """ 85 | :param request: JSON request as string or `json` object 86 | :type request: str | dict 87 | :rtype: dict | None 88 | """ 89 | req_obj = json_str_or_obj_to_obj(request) 90 | if req_obj is None: 91 | return None 92 | 93 | config = [] 94 | geom = None 95 | fltr = req_obj.get("filter", None) 96 | if fltr: 97 | config = fltr.get("config", None) 98 | 99 | for conf in config: 100 | if isinstance(conf, dict) and conf.get("field_name", None) == "geometry": 101 | geom = conf.get("config", None) 102 | break 103 | 104 | return geom 105 | -------------------------------------------------------------------------------- /planet_explorer/planet_api/request-result-samples/annotated_basemap_search.json: -------------------------------------------------------------------------------- 1 | // commented, so invalid json. all values are examples. not to be treated as actual data 2 | { 3 | "mosaics": [ 4 | { 5 | "_links": { // dictionary of urls to this object, component geotiff quads, and tileserver 6 | "_self": "https://api.planet.com/basemaps/v1/mosaics/49329058-74fa-43d9-aa1b-d6c8d8cdc9b8?api_key=YOUR-API-KEY", 7 | "quads": "https://api.planet.com/basemaps/v1/mosaics/49329058-74fa-43d9-aa1b-d6c8d8cdc9b8/quads?api_key=YOUR-API-KEY&bbox={lx},{ly},{ux},{uy}", 8 | "tiles": "https://tiles.planet.com/basemaps/v1/planet-tiles/EEA39_demo_narrow_toi_coverage_mosaic/gmap/{z}/{x}/{y}.png?api_key=YOUR-API-KEY" 9 | }, 10 | "bbox": [ // list of floats for pair of bounding box corners 11 | -10.722656, 12 | 47.279229, 13 | 23.378906, 14 | 62.186014 15 | ], 16 | "coordinate_system": "EPSG:3857", //string 17 | "datatype": "byte", // string - can be byte, uint16, int16, uint32, int32, float32, float64, cint16, cint32, cfloat32, cfloat64 18 | "first_acquired": "2018-06-15T00:00:00.000Z", // string - ISO 8601 datetime 19 | "grid": { 20 | "quad_pattern": "glevel", // string - NOT ALWAYS PRESENT. can be glevel, tilex, tiley 21 | "quad_size": 4096, // int - pixel count 22 | "resolution": 4.777314267823516 // float - pixel size in meters 23 | }, 24 | "id": "49329058-74fa-43d9-aa1b-d6c8d8cdc9b8", // string - unique id 25 | "interval": "1 year", // string - NOT ALWAYS PRESENT. corresponds to timelapse item type. 26 | "item_types": [ // list of strings of source scene type 27 | "PSScene3Band" 28 | ], 29 | "last_acquired": "2018-08-22T00:00:00.000Z", // string - ISO 8601 datetime 30 | "level": 15, // int - max zoom level 31 | "name": "EEA39_demo_narrow_toi_coverage_mosaic", // string 32 | "product_type": "basemap", // string 33 | "quad_download": true // bool - user has permission to download quads from this mosaic 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /planet_explorer/planet_api/request-result-samples/aoi.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Polygon", 3 | "coordinates": [ 4 | [ 5 | [ 6 | -123.34584755425278502, 7 | 37.66740864391950083 8 | ], 9 | [ 10 | -122.22836633585940547, 11 | 37.66740864391950083 12 | ], 13 | [ 14 | -122.22836633585940547, 15 | 38.49868810882473724 16 | ], 17 | [ 18 | -123.34584755425278502, 19 | 38.49868810882473724 20 | ], 21 | [ 22 | -123.34584755425278502, 23 | 37.66740864391950083 24 | ] 25 | ] 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /planet_explorer/planet_api/request-result-samples/aoi_mountain.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Polygon", 3 | "coordinates": [ 4 | [ 5 | [ 6 | -122.3956726239391, 7 | 41.21394953252933 8 | ], 9 | [ 10 | -121.8938167065475, 11 | 41.21394953252933 12 | ], 13 | [ 14 | -121.8938167065475, 15 | 41.55217106103009 16 | ], 17 | [ 18 | -122.3956726239391, 19 | 41.55217106103009 20 | ], 21 | [ 22 | -122.3956726239391, 23 | 41.21394953252933 24 | ] 25 | ] 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /planet_explorer/planet_api/request-result-samples/id_regex.py: -------------------------------------------------------------------------------- 1 | # Scene images: 2 | # PS 4-band: 20190822_185258_25_106d or 20190823_182545_0f17 3 | # PS 3-band: 20190822_185258_25_106d or 20190823_182545_0f17 4 | # SkySap Scene: 20190823_183412_ssc2d3_0015 5 | # SkySat Collect: 20190823_183412_ssc2_u0001 6 | # PS Ortho Tile: 2621706_1055523_2019-08-23_0f17 7 | # RE Basic Scene: 2019-08-21T181707_RE3 8 | # RE Ortho Tile: 20190821_181706_1055225_RapidEye-3 9 | # Sent-2 Tile: S2B_MSIL1C_20190820T183929_N0208_R070_T10SGD_20190820T222602 10 | # Landsat-8: LC80430362019196LGN01, LC80420352019173LGN00 11 | 12 | # Mosaics 13 | -------------------------------------------------------------------------------- /planet_explorer/planet_api/request-result-samples/search_query.json: -------------------------------------------------------------------------------- 1 | { 2 | "item_types": [ 3 | "PSScene4Band" 4 | ], 5 | "filter": { 6 | "type": "AndFilter", 7 | "config": [ 8 | { 9 | "field_name": "geometry", 10 | "type": "GeometryFilter", 11 | "config": { 12 | "type": "Polygon", 13 | "coordinates": [ 14 | [ 15 | [ 16 | -122.85311925238402, 17 | 39.38423372796037 18 | ], 19 | [ 20 | -119.71945874761533, 21 | 39.38423372796037 22 | ], 23 | [ 24 | -119.71945874761533, 25 | 41.19895817770122 26 | ], 27 | [ 28 | -122.85311925238402, 29 | 41.19895817770122 30 | ], 31 | [ 32 | -122.85311925238402, 33 | 39.38423372796037 34 | ] 35 | ] 36 | ] 37 | } 38 | }, 39 | { 40 | "field_name": "cloud_cover", 41 | "type": "RangeFilter", 42 | "config": { 43 | "gte": 0, 44 | "lte": 100 45 | } 46 | } 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /planet_explorer/planet_api/request-result-samples/test-aoi.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Polygon", 3 | "coordinates": [ 4 | [ 5 | [ 6 | -122.48433994473542, 7 | 39.566829312478696 8 | ], 9 | [ 10 | -120.96020906094635, 11 | 39.566829312478696 12 | ], 13 | [ 14 | -120.96020906094635, 15 | 41.0188729247372 16 | ], 17 | [ 18 | -122.48433994473542, 19 | 41.0188729247372 20 | ], 21 | [ 22 | -122.48433994473542, 23 | 39.566829312478696 24 | ] 25 | ] 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /planet_explorer/planet_api/resources/empty_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetlabs/qgis-planet-plugin/7c12eb6484758be4224d301bd70df6a0b54f94ca/planet_explorer/planet_api/resources/empty_thumb.png -------------------------------------------------------------------------------- /planet_explorer/planet_api/resources/empty_thumb.png.aux.xml: -------------------------------------------------------------------------------- 1 | 2 | PROJCS["WGS 84 / UTM zone 10N",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",-123],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH],AUTHORITY["EPSG","32610"]] 3 | 4 | <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0"> 5 | <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> 6 | <rdf:Description rdf:about="" 7 | xmlns:tiff="http://ns.adobe.com/tiff/1.0/" 8 | xmlns:exif="http://ns.adobe.com/exif/1.0/" 9 | xmlns:dc="http://purl.org/dc/elements/1.1/" 10 | xmlns:xmp="http://ns.adobe.com/xap/1.0/"> 11 | <tiff:ResolutionUnit>2</tiff:ResolutionUnit> 12 | <tiff:Compression>5</tiff:Compression> 13 | <tiff:XResolution>72</tiff:XResolution> 14 | <tiff:Orientation>1</tiff:Orientation> 15 | <tiff:YResolution>72</tiff:YResolution> 16 | <exif:PixelXDimension>256</exif:PixelXDimension> 17 | <exif:ColorSpace>1</exif:ColorSpace> 18 | <exif:PixelYDimension>130</exif:PixelYDimension> 19 | <dc:subject> 20 | <rdf:Bag/> 21 | </dc:subject> 22 | <xmp:ModifyDate>2019-09-08T13:09:94</xmp:ModifyDate> 23 | <xmp:CreatorTool>Pixelmator 3.8.5</xmp:CreatorTool> 24 | </rdf:Description> 25 | </rdf:RDF> 26 | </x:xmpmeta> 27 | 28 | 29 | 30 | PIXEL 31 | 32 | 33 | 34 | 0 35 | 0 36 | 0 37 | 0 38 | 100 39 | 40 | 41 | 42 | 43 | 0 44 | 0 45 | 0 46 | 0 47 | 100 48 | 49 | 50 | 51 | 52 | 0 53 | 0 54 | 0 55 | 0 56 | 100 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /planet_explorer/planet_api/resources/empy_thumb.pgw: -------------------------------------------------------------------------------- 1 | 104.8125 2 | 0 3 | 0 4 | -101.6307692308 5 | 603975 6 | 4486407 7 | -------------------------------------------------------------------------------- /planet_explorer/planet_api/resources/productBundleDefaults.json: -------------------------------------------------------------------------------- 1 | { 2 | "Landsat8L1G": ["analytic"], 3 | "REOrthoTile": ["analytic_sr"], 4 | "Sentinel2L1C": ["analytic"], 5 | "SkySatCollect": ["pansharpened_udm2"], 6 | "SkySatScene": ["pansharpened_udm2"], 7 | "PSOrthoTile": ["analytic_sr_udm2"], 8 | "PSScene3Band": ["visual"], 9 | "PSScene4Band": ["analytic_sr_udm2"], 10 | "PSScene": ["analytic_sr_udm2", "visual"] 11 | } 12 | -------------------------------------------------------------------------------- /planet_explorer/resources/PSScene4Band.qml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1 5 | 1 6 | 1 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | None 19 | WholeRaster 20 | Estimated 21 | 0.02 22 | 0.98 23 | 2 24 | 25 | 26 | 27 | 28 | 29 | 30 | 0 31 | 32 | -------------------------------------------------------------------------------- /planet_explorer/resources/__init__.py: -------------------------------------------------------------------------------- 1 | from .resources import * 2 | -------------------------------------------------------------------------------- /planet_explorer/resources/account.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /planet_explorer/resources/basemap.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /planet_explorer/resources/close-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | 27 | 28 | 29 | 30 | 32 | 52 | 58 | 63 | 64 | -------------------------------------------------------------------------------- /planet_explorer/resources/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | 27 | 28 | 29 | 31 | 51 | 57 | 62 | 63 | -------------------------------------------------------------------------------- /planet_explorer/resources/closedock-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetlabs/qgis-planet-plugin/7c12eb6484758be4224d301bd70df6a0b54f94ca/planet_explorer/resources/closedock-16.png -------------------------------------------------------------------------------- /planet_explorer/resources/closedock-down-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetlabs/qgis-planet-plugin/7c12eb6484758be4224d301bd70df6a0b54f94ca/planet_explorer/resources/closedock-down-16.png -------------------------------------------------------------------------------- /planet_explorer/resources/cog.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 30 | 50 | 54 | 60 | 65 | 66 | -------------------------------------------------------------------------------- /planet_explorer/resources/cog2.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /planet_explorer/resources/collapse-triangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 50 | 53 | 58 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /planet_explorer/resources/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 30 | 50 | 53 | 57 | 62 | 67 | 72 | 77 | 82 | 87 | 92 | 93 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /planet_explorer/resources/crop.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /planet_explorer/resources/download.svg: -------------------------------------------------------------------------------- 1 | 2 | 58 | -------------------------------------------------------------------------------- /planet_explorer/resources/envelope-gray.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /planet_explorer/resources/expand-triangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 50 | 53 | 58 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /planet_explorer/resources/expand_less.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /planet_explorer/resources/expand_more.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /planet_explorer/resources/extent-draw-polygon.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | 31 | 53 | 60 | 64 | 69 | 70 | 72 | 90 | 95 | 100 | 105 | 110 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /planet_explorer/resources/extent-select.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 50 | 53 | 58 | 64 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /planet_explorer/resources/extents.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 38 | 41 | 47 | 53 | 59 | 60 | 61 | 62 | 82 | 85 | 90 | 95 | 100 | 105 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /planet_explorer/resources/external-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 58 | 66 | 69 | 74 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /planet_explorer/resources/file-open.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 53 | 58 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /planet_explorer/resources/filetype.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /planet_explorer/resources/harmonize.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Harmonize 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /planet_explorer/resources/info-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 50 | 53 | 56 | 59 | 62 | 68 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /planet_explorer/resources/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /planet_explorer/resources/inspector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetlabs/qgis-planet-plugin/7c12eb6484758be4224d301bd70df6a0b54f94ca/planet_explorer/resources/inspector.png -------------------------------------------------------------------------------- /planet_explorer/resources/inspector.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /planet_explorer/resources/lock-gray.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /planet_explorer/resources/lock-light.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /planet_explorer/resources/mActionAddXyzLayer.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /planet_explorer/resources/nitems.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /planet_explorer/resources/orders.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /planet_explorer/resources/pin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /planet_explorer/resources/planet-logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetlabs/qgis-planet-plugin/7c12eb6484758be4224d301bd70df6a0b54f94ca/planet_explorer/resources/planet-logo-dark.png -------------------------------------------------------------------------------- /planet_explorer/resources/planet-logo-dark.svg: -------------------------------------------------------------------------------- 1 | Untitled-1 2 | -------------------------------------------------------------------------------- /planet_explorer/resources/planet-logo-p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetlabs/qgis-planet-plugin/7c12eb6484758be4224d301bd70df6a0b54f94ca/planet_explorer/resources/planet-logo-p.png -------------------------------------------------------------------------------- /planet_explorer/resources/planet-logo-p.svg: -------------------------------------------------------------------------------- 1 | Untitled-5 2 | -------------------------------------------------------------------------------- /planet_explorer/resources/reload.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 50 | 55 | 60 | 65 | 70 | 71 | -------------------------------------------------------------------------------- /planet_explorer/resources/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | planet-logo-p.svg 4 | planet-logo-p.png 5 | planet-logo-dark.svg 6 | planet-logo-dark.png 7 | planet-explorer-inkscape.svg 8 | planet-explorer-inkscape-light.svg 9 | planet-user.svg 10 | extents.svg 11 | extent-draw-polygon.svg 12 | extent-select.svg 13 | zoom-to-aoi-dashed.svg 14 | zoom-to-aoi-solid.svg 15 | zoom-target.svg 16 | zoom-target-search.svg 17 | thumb-placeholder.svg 18 | thumb-placeholder-128.svg 19 | lock-light.svg 20 | lock-gray.svg 21 | reload.svg 22 | search.svg 23 | search_p.svg 24 | file-open.svg 25 | copy.svg 26 | info.svg 27 | info-light.svg 28 | external-link.svg 29 | cog.svg 30 | cog2.svg 31 | download.svg 32 | expand-triangle.svg 33 | collapse-triangle.svg 34 | close.svg 35 | close-down.svg 36 | closedock-16.png 37 | closedock-down-16.png 38 | envelope-gray.svg 39 | terms.html 40 | 41 | 42 | -------------------------------------------------------------------------------- /planet_explorer/resources/satellite.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 27 | 28 | 48 | 53 | 60 | 61 | -------------------------------------------------------------------------------- /planet_explorer/resources/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /planet_explorer/resources/search_p.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 50 | 53 | 60 | 65 | 70 | 75 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /planet_explorer/resources/sort.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | 31 | 51 | 55 | 1 68 | 9 81 | 82 | -------------------------------------------------------------------------------- /planet_explorer/resources/tasking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetlabs/qgis-planet-plugin/7c12eb6484758be4224d301bd70df6a0b54f94ca/planet_explorer/resources/tasking.png -------------------------------------------------------------------------------- /planet_explorer/resources/tasking.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /planet_explorer/resources/terms.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Planet Explorer Terms 6 | 34 | 35 | 36 |
37 | planet logo 38 | 39 |

Evaluation Use Only License

40 |

You are using the free version of Planet Explorer, which is for your personal evaluation of Planet's imagery only. This free access does not allow for professional, project, or other use of Planet imagery, whether paid or unpaid, including by individuals, non-profits, universities, governments, or companies.

41 | 42 |

Purchase Data

43 | 44 |

Discounted licenses are available for Education and Research, and Disaster Data programs. For other uses, please contact us for licensing options.

45 | 46 |

Full Evaluation Terms

47 | 48 |

Planet Explorer Terms of Service

49 |

Privacy Policy

50 | 51 |

© 2017 Planet for the Planet and RapidEye satellite imagery. Planet imagery available in this browser is solely for personal, non-commercial, and informational purposes only.

52 | 53 |

Landsat imagery courtesy of NASA Goddard Space Flight Center and U.S. Geological Survey

54 | 55 |

Sentinel Dataset Source: Commission (Copernicus), ESA.
56 | Sentinel data is free, full and open for public use under EU law. For full details of use, refer to the Sentinel Data Terms and Conditions.

57 | 58 |

California: Planet Labs content of California is made available to you under a Creative Commons Attribution-ShareAlike 4.0 International license as available at http://creativecommons.org/licenses/by-sa/4.0/.

59 | 60 |

Plugin documentation

61 | 62 |

To know more, check the plugin documentation.

63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /planet_explorer/resources/thumb-placeholder-128.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | 31 | 51 | 58 | 59 | -------------------------------------------------------------------------------- /planet_explorer/resources/thumb-placeholder.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | 31 | 51 | 58 | 59 | -------------------------------------------------------------------------------- /planet_explorer/resources/udm.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /planet_explorer/resources/zoom-target-search.svg: -------------------------------------------------------------------------------- 1 | 2 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 28 | 48 | 53 | 59 | 64 | 65 | -------------------------------------------------------------------------------- /planet_explorer/resources/zoom-target.svg: -------------------------------------------------------------------------------- 1 | 2 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 28 | 48 | 53 | 59 | 64 | 65 | -------------------------------------------------------------------------------- /planet_explorer/resources/zoom-to-aoi-dashed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /planet_explorer/resources/zoom-to-aoi-solid.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 29 | 49 | 52 | 58 | 64 | 69 | 74 | 79 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /planet_explorer/settings.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "ordersPath", 4 | "label": "Order download location", 5 | "description": "Default location of the downloaded orders", 6 | "type": "folder", 7 | "default": "", 8 | "group": "Orders" 9 | }, 10 | { 11 | "name": "enableClip", 12 | "label": "Enable Clip tool by default", 13 | "description": "Enable Clip tool by default in orders panel", 14 | "type": "bool", 15 | "default": false, 16 | "group": "Orders" 17 | }, 18 | { 19 | "name": "enableHarmonization", 20 | "label": "Enable Harmonization tool by default", 21 | "description": "Enable Harmonization tool by default in orders panel", 22 | "type": "bool", 23 | "default": false, 24 | "group": "Orders" 25 | }, 26 | { 27 | "name": "enableStacMetadata", 28 | "label": "Enable STAC Metadata tool by default", 29 | "description": "Enable STAC Metadata tool by default in orders panel", 30 | "type": "bool", 31 | "default": false, 32 | "group": "Orders" 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /planet_explorer/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetlabs/qgis-planet-plugin/7c12eb6484758be4224d301bd70df6a0b54f94ca/planet_explorer/tests/__init__.py -------------------------------------------------------------------------------- /planet_explorer/tests/data/aoi_tests/test_aoi.gpkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetlabs/qgis-planet-plugin/7c12eb6484758be4224d301bd70df6a0b54f94ca/planet_explorer/tests/data/aoi_tests/test_aoi.gpkg -------------------------------------------------------------------------------- /planet_explorer/tests/data/aoi_tests/test_empty.gpkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetlabs/qgis-planet-plugin/7c12eb6484758be4224d301bd70df6a0b54f94ca/planet_explorer/tests/data/aoi_tests/test_empty.gpkg -------------------------------------------------------------------------------- /planet_explorer/tests/data/aoi_tests/test_multipoly.gpkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetlabs/qgis-planet-plugin/7c12eb6484758be4224d301bd70df6a0b54f94ca/planet_explorer/tests/data/aoi_tests/test_multipoly.gpkg -------------------------------------------------------------------------------- /planet_explorer/tests/data/test_add_to_map/planet_orders/5c9e6c59-eb35-485d-ab7d-04a75e9e0f14/Add_to_map_test_QGIS/files/PSScene/20221221_084022_18_2414/20221221_084022_18_2414_metadata.json: -------------------------------------------------------------------------------- 1 | {"id":"20221221_084022_18_2414","type":"Feature","geometry":{"coordinates":[[[18.28138735653141,-33.75207489835009],[18.235718215986875,-33.930909459887886],[18.595039806750812,-33.994401090235954],[18.6401484699692,-33.81544428639961],[18.28138735653141,-33.75207489835009]]],"type":"Polygon"},"properties":{"acquired":"2022-12-21T08:40:22.180138Z","anomalous_pixels":0,"clear_confidence_percent":93,"clear_percent":100,"cloud_cover":0,"cloud_percent":0,"ground_control":true,"gsd":3.8,"heavy_haze_percent":0,"instrument":"PSB.SD","item_type":"PSScene","light_haze_percent":0,"pixel_resolution":3,"provider":"planetscope","ps4b_geometry":{"coordinates":[[[18.28138735653141,-33.75207489835009],[18.235718215986875,-33.930909459887886],[18.595039806750812,-33.994401090235954],[18.6401484699692,-33.81544428639961],[18.28138735653141,-33.75207489835009]]],"type":"Polygon"},"published":"2022-12-21T21:04:58Z","publishing_stage":"finalized","quality_category":"standard","satellite_azimuth":285.1,"satellite_id":"2414","shadow_percent":0,"snow_ice_percent":0,"strip_id":"6158805","sun_azimuth":77.2,"sun_elevation":61,"updated":"2022-12-22T04:42:19Z","view_angle":1.2,"visible_confidence_percent":73,"visible_percent":100}} 2 | -------------------------------------------------------------------------------- /planet_explorer/tests/data/test_add_to_map/planet_orders/5c9e6c59-eb35-485d-ab7d-04a75e9e0f14/Add_to_map_test_QGIS/files/PSScene/20221221_084022_18_2414/analytic_sr_udm2/20221221_084022_18_2414_3B_AnalyticMS_SR.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetlabs/qgis-planet-plugin/7c12eb6484758be4224d301bd70df6a0b54f94ca/planet_explorer/tests/data/test_add_to_map/planet_orders/5c9e6c59-eb35-485d-ab7d-04a75e9e0f14/Add_to_map_test_QGIS/files/PSScene/20221221_084022_18_2414/analytic_sr_udm2/20221221_084022_18_2414_3B_AnalyticMS_SR.tif -------------------------------------------------------------------------------- /planet_explorer/tests/data/test_add_to_map/planet_orders/5c9e6c59-eb35-485d-ab7d-04a75e9e0f14/Add_to_map_test_QGIS/files/PSScene/20221221_084022_18_2414/analytic_sr_udm2/20221221_084022_18_2414_3B_udm2.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetlabs/qgis-planet-plugin/7c12eb6484758be4224d301bd70df6a0b54f94ca/planet_explorer/tests/data/test_add_to_map/planet_orders/5c9e6c59-eb35-485d-ab7d-04a75e9e0f14/Add_to_map_test_QGIS/files/PSScene/20221221_084022_18_2414/analytic_sr_udm2/20221221_084022_18_2414_3B_udm2.tif -------------------------------------------------------------------------------- /planet_explorer/tests/data/test_add_to_map/planet_orders/5c9e6c59-eb35-485d-ab7d-04a75e9e0f14/Add_to_map_test_QGIS/manifest.json: -------------------------------------------------------------------------------- 1 | {"name":"","files":[{"path":"files/PSScene/20221221_084022_18_2414/20221221_084022_18_2414_metadata.json","media_type":"application/json","size":1223,"digests":{"md5":"84730ec95fae885fc125d8c15b73191c","sha256":"5001393686734262311df056d30a09630a4534bcb81944ea1361d5372961196b"},"annotations":{"planet/item_id":"20221221_084022_18_2414","planet/item_type":"PSScene"}},{"path":"files/PSScene/20221221_084022_18_2414/analytic_sr_udm2/20221221_084022_18_2414_3B_AnalyticMS_SR.tif","media_type":"image/tiff","size":485490660,"digests":{"md5":"4ce92c468e38e525e4b0f3e95195ea7c","sha256":"7f25477411462a6a857fb537e9a0c6563a9812a789fb632f4971c2c4896c9293"},"annotations":{"planet/asset_type":"ortho_analytic_4b_sr","planet/bundle_type":"analytic_sr_udm2","planet/item_id":"20221221_084022_18_2414","planet/item_type":"PSScene"}},{"path":"files/PSScene/20221221_084022_18_2414/analytic_sr_udm2/20221221_084022_18_2414_3B_AnalyticMS_metadata.xml","media_type":"text/xml","size":10287,"digests":{"md5":"62584741d63c065ce312002dca96e66f","sha256":"96b85db9089d25fe161c895e8881316c2d1a2b65f8923bbe684d06d405ef25d7"},"annotations":{"planet/asset_type":"ortho_analytic_4b_xml","planet/bundle_type":"analytic_sr_udm2","planet/item_id":"20221221_084022_18_2414","planet/item_type":"PSScene"}},{"path":"files/PSScene/20221221_084022_18_2414/analytic_sr_udm2/20221221_084022_18_2414_3B_udm2.tif","media_type":"image/tiff","size":4468456,"digests":{"md5":"091d68ad022a6cb6daa764888f3a55a9","sha256":"ec3a7bd46a7010e2637d5dd5fd3989bc43dbc3929e3119bd8b711ea9c7fbcae8"},"annotations":{"planet/asset_type":"ortho_udm2","planet/bundle_type":"analytic_sr_udm2","planet/item_id":"20221221_084022_18_2414","planet/item_type":"PSScene"}}]} 2 | -------------------------------------------------------------------------------- /planet_explorer/tests/install_plugin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ 3 | Install a plugin from a zip file into QGIS. This script is meant to be run inside of QGIS: 4 | 5 | .. code-block:: bash 6 | 7 | qgis --noplugins --code install_plugin.py 8 | 9 | The package zip file for the plugin is expected to be present in current working directory. 10 | 11 | Verifies: 12 | - PLQGIS-TC01 13 | - PLQGIS-TC02 14 | """ 15 | import os 16 | import pathlib 17 | import traceback 18 | 19 | 20 | class PluginInstallException(Exception): 21 | pass 22 | 23 | 24 | ERROR_OCCURRED = False 25 | ERROR_MSG = "" 26 | PLUGIN_KEY = "planet_explorer" 27 | 28 | 29 | def error_catcher(msg, tag, level): 30 | """ 31 | Catch a python error and raise a PluginInstallException 32 | """ 33 | global ERROR_OCCURRED, ERROR_MSG 34 | if tag == "Python error" and level != 0: 35 | ERROR_OCCURRED = True 36 | ERROR_MSG = msg 37 | 38 | 39 | try: 40 | try: 41 | import pyplugin_installer 42 | from qgis.core import QgsApplication 43 | from qgis import utils 44 | except ImportError: 45 | raise PluginInstallException( 46 | "Cannot install plugin as 'pyplugin_installer' could not be imported." 47 | " Is the script running in the QGIS env?" 48 | ) 49 | 50 | zip_files = [file for file in pathlib.Path("./").glob("*.zip")] 51 | 52 | if not zip_files: 53 | raise PluginInstallException( 54 | f"ERROR: No plugin zip file found at {pathlib.Path('./').absolute()}." 55 | ) 56 | 57 | if len(zip_files) > 1: 58 | raise PluginInstallException( 59 | f"ERROR: More than one plugin zip file found at {pathlib.Path('./').absolute()}." 60 | f" Found {[str(f) for f in zip_files]}." 61 | ) 62 | 63 | plugin_install_zip = zip_files[0] 64 | plugin_installer = pyplugin_installer.instance() 65 | # Make sure plugin is not installed 66 | if PLUGIN_KEY in pyplugin_installer.installer_data.plugins.all(): 67 | plugin_installer.uninstallPlugin(PLUGIN_KEY) 68 | 69 | # Attach the error catcher 70 | QgsApplication.messageLog().messageReceived.connect(error_catcher) 71 | 72 | # Install from the zip file 73 | plugin_installer.installFromZipFile(str(plugin_install_zip.absolute())) 74 | # unload plugin so we can test load/unload 75 | if PLUGIN_KEY in utils.active_plugins: 76 | utils.unloadPlugin(PLUGIN_KEY) 77 | 78 | assert ( 79 | PLUGIN_KEY in pyplugin_installer.installer_data.plugins.all().keys() 80 | ), "Planet plugin failed to install!" 81 | 82 | if ERROR_OCCURRED: 83 | raise PluginInstallException( 84 | f"Python exception hit during plugin install: \n {ERROR_MSG}" 85 | ) 86 | 87 | # Start/Load the plugin 88 | assert utils.loadPlugin(PLUGIN_KEY) 89 | assert utils.startPlugin(PLUGIN_KEY), f"'{PLUGIN_KEY}' failed to start!" 90 | assert ( 91 | PLUGIN_KEY in utils.active_plugins 92 | ), f"'{PLUGIN_KEY}' not found in active_plugins, found: {utils.active_plugins}" 93 | 94 | # Unload the plugin 95 | assert utils.unloadPlugin(PLUGIN_KEY), "'planet_explorer' failed to unload" 96 | assert PLUGIN_KEY not in utils.active_plugins 97 | 98 | # Uninstall the plugin 99 | plugin_installer.uninstallPlugin(PLUGIN_KEY, quiet=True) 100 | assert ( 101 | PLUGIN_KEY not in pyplugin_installer.installer_data.plugins.all().keys() 102 | ), "Planet plugin failed to uninstall!" 103 | 104 | if ERROR_OCCURRED: 105 | raise PluginInstallException( 106 | f"Python exception hit during plugin uninstall: \n {ERROR_MSG}" 107 | ) 108 | except Exception: # noqa 109 | # Print the error so we know where it failed, 110 | # and exit with a non-zero status code so CI will fail. 111 | print( 112 | "FAIL: Plugin install, load, unload, and uninstall failed with the following:" 113 | ) 114 | print(traceback.format_exc()) 115 | os._exit(1) 116 | finally: 117 | # The install and uninstall worked! Exit QGIS with a 0 status code. 118 | print( 119 | f"PASS: Plugin install, load, unload, and " 120 | f"uninstall successful for {str(plugin_install_zip)}" 121 | ) 122 | os._exit(0) 123 | -------------------------------------------------------------------------------- /planet_explorer/tests/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | qgis_window_height=1000 3 | qgis_window_width=1000 4 | qgis_canvas_height=800 5 | qgis_canvas_width=800 6 | filterwarnings = 7 | ignore::UserWarning:pytest_qgis.*: 8 | -------------------------------------------------------------------------------- /planet_explorer/tests/test_filters.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from qgis.core import QgsGeometry, QgsPoint, QgsPointXY, QgsWkbTypes 4 | from qgis.gui import QgsRubberBand, QgsMapCanvas 5 | from qgis.utils import iface 6 | 7 | from planet_explorer.gui.pe_filters import PlanetAOIFilter 8 | 9 | 10 | @pytest.mark.parametrize( 11 | "name, polygon, expected_size", 12 | [ 13 | pytest.param( 14 | "small_polygon", 15 | [ 16 | QgsPointXY(QgsPoint(10, 10)), 17 | QgsPointXY(QgsPoint(10, 20)), 18 | QgsPointXY(QgsPoint(20, 20)), 19 | QgsPointXY(QgsPoint(20, 10)), 20 | QgsPointXY(QgsPoint(10, 10)), 21 | ], 22 | 1239202.90, 23 | id="area_of_interest_with_small_size", 24 | ), 25 | pytest.param( 26 | "mid_polygon", 27 | [ 28 | QgsPointXY(QgsPoint(10, 10)), 29 | QgsPointXY(QgsPoint(10, 40)), 30 | QgsPointXY(QgsPoint(40, 40)), 31 | QgsPointXY(QgsPoint(40, 10)), 32 | QgsPointXY(QgsPoint(10, 10)), 33 | ], 34 | 11152826.13, 35 | id="area_of_interest_with_medium_size", 36 | ), 37 | pytest.param( 38 | "large_polygon", 39 | [ 40 | QgsPointXY(QgsPoint(10, 10)), 41 | QgsPointXY(QgsPoint(10, 60)), 42 | QgsPointXY(QgsPoint(60, 60)), 43 | QgsPointXY(QgsPoint(60, 10)), 44 | QgsPointXY(QgsPoint(10, 10)), 45 | ], 46 | 30980072.58, 47 | id="area_of_interest_with_large_size", 48 | ), 49 | pytest.param( 50 | "small_polygon", 51 | [ 52 | QgsPointXY(QgsPoint(0, 0)), 53 | QgsPointXY(QgsPoint(0, 0)), 54 | QgsPointXY(QgsPoint(0, 0)), 55 | QgsPointXY(QgsPoint(0, 0)), 56 | QgsPointXY(QgsPoint(0, 0)), 57 | ], 58 | 0.0, 59 | id="area_of_interest_with_zero_size", 60 | ), 61 | ], 62 | ) 63 | def test_aoi_area_size_calculation(name, polygon, expected_size): 64 | """Tests the filter for calculating the aoi size in square kilometers""" 65 | aoi_filter = PlanetAOIFilter() 66 | canvas = iface.mapCanvas() if iface else QgsMapCanvas() 67 | aoi_box = QgsRubberBand(canvas, QgsWkbTypes.PolygonGeometry) 68 | 69 | geometry = QgsGeometry.fromPolygonXY([polygon]) 70 | aoi_box.setToGeometry(geometry) 71 | 72 | aoi_filter._aoi_box = aoi_box 73 | size = aoi_filter.calculate_aoi_area() 74 | 75 | assert size == expected_size 76 | -------------------------------------------------------------------------------- /planet_explorer/tests/test_login.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from qgis.PyQt import QtCore 4 | 5 | from planet_explorer.tests.utils import qgis_debug_wait 6 | from planet_explorer.tests.utils import get_testing_credentials 7 | 8 | pytestmark = [pytest.mark.qgis_show_map(add_basemap=False, timeout=1)] 9 | 10 | 11 | TOOLBAR_BUTTONS = [ 12 | "showdailyimages_act", 13 | "showbasemaps_act", 14 | "showinspector_act", 15 | "showorders_act", 16 | "showtasking_act", 17 | "user_button", 18 | ] 19 | 20 | 21 | @pytest.mark.parametrize("use_mouse", [True, False], ids=["Mouse Click", "Hit Enter"]) 22 | def test_explorer_login(qtbot, explorer_dock_widget, qgis_debug_enabled, use_mouse): 23 | """ 24 | Verifies: 25 | - PLQGIS-TC03 26 | """ 27 | username, password = get_testing_credentials() 28 | 29 | dock_widget = explorer_dock_widget() 30 | 31 | qgis_debug_wait(qtbot, qgis_debug_enabled) 32 | qtbot.keyClicks(dock_widget.leUser, username) 33 | 34 | qgis_debug_wait(qtbot, qgis_debug_enabled) 35 | qtbot.keyClicks(dock_widget.lePass, password) 36 | 37 | qgis_debug_wait(qtbot, qgis_debug_enabled) 38 | if use_mouse: 39 | qtbot.mouseClick(dock_widget.btn_ok, QtCore.Qt.LeftButton) 40 | else: 41 | qtbot.keyClick(dock_widget.lePass, QtCore.Qt.Key_Enter) 42 | 43 | current = dock_widget.stckdWidgetViews.currentIndex() 44 | assert current == 1 45 | assert dock_widget.logged_in() 46 | 47 | 48 | @pytest.mark.qt_no_exception_capture 49 | def test_explorer_login_incorrect(qtbot, explorer_dock_widget, qgis_debug_enabled): 50 | """ 51 | As this is a negative test case, we do not capture exceptions. 52 | 53 | Verifies: 54 | - PLQGIS-TC03 55 | """ 56 | dock_widget = explorer_dock_widget() 57 | 58 | qgis_debug_wait(qtbot, qgis_debug_enabled) 59 | qtbot.keyClicks(dock_widget.leUser, "Iam") 60 | 61 | qgis_debug_wait(qtbot, qgis_debug_enabled) 62 | qtbot.keyClicks(dock_widget.lePass, "NotAUser") 63 | 64 | qgis_debug_wait(qtbot, qgis_debug_enabled) 65 | qtbot.mouseClick(dock_widget.btn_ok, QtCore.Qt.LeftButton) 66 | 67 | current = dock_widget.stckdWidgetViews.currentIndex() 68 | assert current == 0 69 | assert not dock_widget.logged_in() 70 | 71 | 72 | def test_explorer_reacts_to_login(qtbot, explorer_dock_widget, qgis_debug_enabled): 73 | """ 74 | Verifies: 75 | - PLQGIS-TC03 76 | """ 77 | dock_widget = explorer_dock_widget() 78 | 79 | current = dock_widget.stckdWidgetViews.currentIndex() 80 | assert current == 0 81 | username, password = get_testing_credentials() 82 | qgis_debug_wait(qtbot, qgis_debug_enabled) 83 | with qtbot.waitSignal(dock_widget.p_client.loginChanged): 84 | dock_widget.p_client.log_in(username, password) 85 | qgis_debug_wait(qtbot, qgis_debug_enabled) 86 | current = dock_widget.stckdWidgetViews.currentIndex() 87 | assert current == 1 88 | 89 | 90 | def test_explorer_logout( 91 | qtbot, plugin, logged_in_explorer_dock_widget, qgis_debug_enabled 92 | ): 93 | """ 94 | Unfortunately it is not possible to click the item in the QMenu, we can however trigger 95 | the event that would be emitted by a user click. 96 | 97 | cf. https://github.com/pytest-dev/pytest-qt/issues/195 98 | 99 | Verifies: 100 | - PLQGIS-TC20 101 | """ 102 | dock_widget = logged_in_explorer_dock_widget() 103 | assert not plugin.btnLogin.isVisible() 104 | # Verify things are enabled when logged in 105 | for btn in TOOLBAR_BUTTONS: 106 | assert getattr(plugin, btn).isEnabled() 107 | 108 | qgis_debug_wait(qtbot, qgis_debug_enabled) 109 | logout_action = plugin.user_button.menu().actions()[1] 110 | logout_action.trigger() 111 | qgis_debug_wait(qtbot, qgis_debug_enabled) 112 | 113 | qtbot.waitUntil(lambda: not dock_widget.logged_in(), timeout=10 * 1000) 114 | 115 | if qgis_debug_enabled: 116 | assert plugin.btnLogin.isVisible() 117 | assert plugin.btnLogin.isEnabled() 118 | # Verify things are not enabled when logged out 119 | for btn in TOOLBAR_BUTTONS: 120 | assert not getattr(plugin, btn).isEnabled() 121 | -------------------------------------------------------------------------------- /planet_explorer/tests/test_plugin.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from urllib.parse import urljoin 3 | from planet_explorer.tests.utils import get_recent_release_from_changelog 4 | 5 | import xml.etree.ElementTree as ET 6 | 7 | REPO_URL = "https://api.github.com/repos/planetlabs/qgis-planet-plugin/" 8 | 9 | CUSTOM_REPOSITORY_URL = ( 10 | "https://raw.githubusercontent.com/planetlabs/qgis-planet-plugin" 11 | "/release/docs/repository/plugins.xml" 12 | ) 13 | 14 | 15 | def test_import_planet(): 16 | try: 17 | import planet # noqa: F401 18 | 19 | assert True 20 | except ImportError: 21 | assert False 22 | 23 | 24 | def test_changelog_up_to_date(request): 25 | """ 26 | Verifies: 27 | - PLQGIS-TC19 28 | """ 29 | root_dir = request.config.rootdir 30 | if root_dir.basename == "tests": 31 | root_dir = root_dir / ".." / ".." 32 | most_recent_release_on_github = requests.get( 33 | urljoin(REPO_URL, "releases?per_page=1") 34 | ).json()[0]["name"] 35 | most_recent_release_in_changelog = get_recent_release_from_changelog(root_dir) 36 | assert ( 37 | most_recent_release_in_changelog == most_recent_release_on_github 38 | ), "Release on Github does not match the most recent changelog entry!" 39 | 40 | 41 | def test_plugin_repository_content(): 42 | repo_content_resp = requests.get(CUSTOM_REPOSITORY_URL) 43 | 44 | assert repo_content_resp.status_code == 200 45 | 46 | root = ET.fromstring(repo_content_resp.text) 47 | plugin = root.find("pyqgis_plugin") 48 | 49 | assert plugin is not None 50 | assert plugin.find("qgis_minimum_version").text == "3.10" 51 | assert plugin.find("icon").text == "resources/planet-logo-p.png" 52 | 53 | assert ( 54 | plugin.find("homepage").text 55 | == "https://developers.planet.com/docs/integrations/qgis/" 56 | ) 57 | assert ( 58 | plugin.find("repository").text 59 | == "https://github.com/planetlabs/qgis-planet-plugin" 60 | ) 61 | assert ( 62 | plugin.find("tracker").text 63 | == "https://github.com/planetlabs/qgis-planet-plugin/issues" 64 | ) 65 | assert plugin.find("tags").text == "landsat, raster, analytics, remote sensing" 66 | 67 | assert plugin.find("file_name").text is not None 68 | assert plugin.find("version") is not None 69 | assert plugin.find("experimental") is not None 70 | assert plugin.find("deprecated") is not None 71 | assert plugin.find("about") is not None 72 | assert plugin.find("description") is not None 73 | -------------------------------------------------------------------------------- /planet_explorer/tests/test_saved_search.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from qgis.PyQt import QtCore 3 | from planet_explorer.gui.pe_open_saved_search_dialog import OpenSavedSearchDialog 4 | from planet_explorer.gui.pe_save_search_dialog import SaveSearchDialog 5 | from planet_explorer.tests.utils import qgis_debug_wait, get_random_string 6 | 7 | pytestmark = [pytest.mark.qgis_show_map(add_basemap=False, timeout=1)] 8 | 9 | 10 | SAMPLE_AOI_SAVED_SEARCH_NAME = "QGIS Plugin Tests - Sample AOI" 11 | 12 | 13 | def test_load(qtbot, logged_in_explorer_dock_widget, qgis_debug_enabled, sample_aoi): 14 | """ 15 | Verifies: 16 | - PLQGIS-TC15 17 | """ 18 | dock_widget = logged_in_explorer_dock_widget() 19 | daily_images_widget = dock_widget.daily_images_widget 20 | qgis_debug_wait(qtbot, qgis_debug_enabled) 21 | dlg = OpenSavedSearchDialog() 22 | qtbot.add_widget(dlg) 23 | 24 | def _dlg_interact(): 25 | # find the saved search corresponding to the sample AOI 26 | for index in range(dlg.comboSavedSearch.count()): 27 | if dlg.comboSavedSearch.itemText(index) == SAMPLE_AOI_SAVED_SEARCH_NAME: 28 | dlg.comboSavedSearch.setCurrentIndex(index) 29 | break 30 | qtbot.mouseClick(dlg.btnLoad, QtCore.Qt.LeftButton) 31 | qgis_debug_wait(qtbot, qgis_debug_enabled) 32 | 33 | QtCore.QTimer.singleShot(1000, _dlg_interact) 34 | daily_images_widget.open_saved_searches(dlg=dlg) 35 | 36 | # make sure the proper AOI from the saved search is loaded by checking the filter 37 | qgis_debug_wait(qtbot, qgis_debug_enabled) 38 | assert daily_images_widget._aoi_filter.leAOI.text() == sample_aoi 39 | 40 | 41 | def test_create(qtbot, logged_in_explorer_dock_widget, qgis_debug_enabled, sample_aoi): 42 | """ 43 | Verifies: 44 | - PLQGIS-TC08 45 | """ 46 | dock_widget = logged_in_explorer_dock_widget() 47 | daily_images_widget = dock_widget.daily_images_widget 48 | saved_search_name = f"test-qgis-saved-search-{get_random_string()}" 49 | qgis_debug_wait(qtbot, qgis_debug_enabled) 50 | 51 | # execute some search 52 | qgis_debug_wait(qtbot, qgis_debug_enabled) 53 | qtbot.keyClicks(daily_images_widget._aoi_filter.leAOI, sample_aoi) 54 | qgis_debug_wait(qtbot, qgis_debug_enabled) 55 | qtbot.mouseClick(daily_images_widget.btnSearch, QtCore.Qt.LeftButton) 56 | qgis_debug_wait(qtbot, qgis_debug_enabled) 57 | 58 | dlg = SaveSearchDialog(daily_images_widget._request) 59 | qtbot.add_widget(dlg) 60 | 61 | def _dlg_interact(): 62 | # do stuff to save search 63 | qtbot.keyClicks(dlg.txtName, saved_search_name) 64 | qgis_debug_wait(qtbot, qgis_debug_enabled) 65 | 66 | # get the save button 67 | save_button = None 68 | for btn in dlg.buttonBox.buttons(): 69 | if "Save" in btn.text(): 70 | save_button = btn 71 | break 72 | # save the search 73 | qtbot.mouseClick(save_button, QtCore.Qt.LeftButton) 74 | qgis_debug_wait(qtbot, qgis_debug_enabled) 75 | 76 | QtCore.QTimer.singleShot(1000, _dlg_interact) 77 | daily_images_widget.searchResultsWidget._save_search(dlg=dlg) 78 | 79 | # Verify the search was created and delete it 80 | dlg = OpenSavedSearchDialog() 81 | qtbot.add_widget(dlg) 82 | 83 | def _dlg_interact(): 84 | # verify the search was added to the list 85 | saved_searches = [ 86 | dlg.comboSavedSearch.itemText(index) 87 | for index in range(dlg.comboSavedSearch.count()) 88 | ] 89 | assert saved_search_name in saved_searches 90 | dlg.comboSavedSearch.setCurrentIndex(saved_searches.index(saved_search_name)) 91 | qgis_debug_wait(qtbot, qgis_debug_enabled) 92 | 93 | # delete the search 94 | qtbot.mouseClick(dlg.btnDelete, QtCore.Qt.LeftButton) 95 | qgis_debug_wait(qtbot, qgis_debug_enabled) 96 | 97 | saved_searches = [ 98 | dlg.comboSavedSearch.itemText(index) 99 | for index in range(dlg.comboSavedSearch.count()) 100 | ] 101 | assert saved_search_name not in saved_searches 102 | 103 | qtbot.mouseClick(dlg.btnCancel, QtCore.Qt.LeftButton) 104 | qgis_debug_wait(qtbot, qgis_debug_enabled) 105 | 106 | QtCore.QTimer.singleShot(1000, _dlg_interact) 107 | daily_images_widget.open_saved_searches(dlg=dlg) 108 | -------------------------------------------------------------------------------- /planet_explorer/tests/test_tasking.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from planet_explorer.tests.utils import qgis_debug_wait 4 | from qgis.PyQt import QtCore 5 | from planet_explorer.gui.pe_tasking_dockwidget import WarningDialog 6 | from qgis.PyQt.QtWidgets import QTextBrowser 7 | 8 | pytestmark = [pytest.mark.qgis_show_map(add_basemap=False, timeout=1)] 9 | 10 | 11 | def test_tasking_widget( 12 | qtbot, 13 | logged_in_explorer_dock_widget, 14 | qgis_debug_enabled, 15 | sample_aoi, 16 | tasking_widget, 17 | qgis_canvas, 18 | qgis_version, 19 | ): 20 | """ 21 | Verifies: 22 | - PLQGIS-TC09 23 | """ 24 | dock_widget = logged_in_explorer_dock_widget() 25 | task_dockwidget = tasking_widget(dock_widget) 26 | dock_widget.hide() 27 | 28 | # click the 'Selection' button 29 | qtbot.mouseClick(task_dockwidget.btnMapTool, QtCore.Qt.LeftButton) 30 | qgis_debug_wait(qtbot, qgis_debug_enabled) 31 | 32 | assert task_dockwidget.btnCancel.isEnabled() 33 | assert not task_dockwidget.btnOpenDashboard.isEnabled() 34 | 35 | # click some point on the canvas 36 | qtbot.mouseClick(qgis_canvas.viewport(), QtCore.Qt.LeftButton) 37 | qgis_debug_wait(qtbot, qgis_debug_enabled) 38 | 39 | # TODO: investigate if this can be removed? 40 | # Test fails on 3.10 and 3.16 but functionality works file manually 41 | if qgis_version > 32000: 42 | assert task_dockwidget.btnCancel.isEnabled() 43 | assert task_dockwidget.btnOpenDashboard.isEnabled() 44 | assert ( 45 | "Selected Point Coordinates" 46 | in task_dockwidget.textBrowserPoint.toPlainText() 47 | ) 48 | 49 | # make sure the dialog appears 50 | dialog = WarningDialog(task_dockwidget.pt) 51 | 52 | def _dialog_interact(): 53 | text_browser = [ 54 | widget 55 | for widget in dialog.children() 56 | if isinstance(widget, QTextBrowser) 57 | ][0] 58 | # make sure text is displayed 59 | assert text_browser.toPlainText(), "No text displayed in dialog!" 60 | dialog.close() 61 | 62 | QtCore.QTimer.singleShot(1000, _dialog_interact) 63 | task_dockwidget._open_tasking_dashboard(dlg=dialog) 64 | -------------------------------------------------------------------------------- /planet_explorer/tests/utils.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import os 3 | import random 4 | import pathlib 5 | 6 | from planet_explorer import pe_utils 7 | from planet_explorer.gui import pe_explorer_dockwidget 8 | from planet_explorer.gui import pe_orders_monitor_dockwidget 9 | from planet_explorer.gui import pe_tasking_dockwidget 10 | from qgis.PyQt import QtCore 11 | 12 | 13 | def patch_iface(): 14 | pe_utils.iface.messageTimeout.return_value = 5 15 | 16 | 17 | def test_aoi(): 18 | return ( 19 | '{"coordinates":[[[-0.334369,40.151264],[-0.276291,40.151264],' 20 | "[-0.276291,40.172081],[-0.334369,40.172081],[-0.334369,40.151264]]]" 21 | ',"type":"Polygon"}' 22 | ) 23 | 24 | 25 | def get_testing_credentials(): 26 | if ( 27 | os.environ.get("PLANET_USER") is None 28 | or os.environ.get("PLANET_PASSWORD") is None 29 | ): 30 | raise ValueError( 31 | "PLANET_USER and PLANET_PASSWORD env vars are undefined! Cannot run tests." 32 | ) 33 | return os.environ["PLANET_USER"], os.environ["PLANET_PASSWORD"] 34 | 35 | 36 | def qgis_debug_wait(qtbot, qgis_debug_enabled, wait=1000): 37 | """Helper function to see what is going on when running tests.""" 38 | if qgis_debug_enabled: 39 | qtbot.wait(wait) 40 | 41 | 42 | def get_explorer_dockwidget(plugin_toolbar, login=True): 43 | """ 44 | Setup the explorer dock_widget for tests 45 | """ 46 | dock_widget = pe_explorer_dockwidget._get_widget_instance() # noqa 47 | current_geometry = dock_widget.geometry() 48 | toolbar_geometry = plugin_toolbar.geometry() 49 | dock_widget.setGeometry( 50 | current_geometry.x(), 51 | toolbar_geometry.height() + 1, 52 | current_geometry.width(), 53 | current_geometry.height(), 54 | ) 55 | dock_widget._setup_client() 56 | dock_widget.chkBxSaveCreds.setChecked(False) 57 | if login: 58 | username, password = get_testing_credentials() 59 | dock_widget.p_client.log_in(username, password) 60 | return dock_widget 61 | 62 | 63 | def get_order_monitor_widget(explorer_dockwidget): 64 | """ 65 | Setup orders monitor dock_widget for tests 66 | """ 67 | order_widget = pe_orders_monitor_dockwidget._get_widget_instance() # noqa 68 | current_geometry = order_widget.geometry() 69 | order_widget.setGeometry( 70 | explorer_dockwidget.geometry().width() + 1, 71 | explorer_dockwidget.geometry().y(), 72 | current_geometry.width(), 73 | current_geometry.height(), 74 | ) 75 | return order_widget 76 | 77 | 78 | def get_tasking_widget(explorer_dockwidget): 79 | """ 80 | Setup orders monitor dock_widget for tests 81 | """ 82 | tasking_widget = pe_tasking_dockwidget._get_widget_instance() # noqa 83 | current_geometry = tasking_widget.geometry() 84 | tasking_widget.setGeometry( 85 | explorer_dockwidget.geometry().width() + 1, 86 | explorer_dockwidget.geometry().y(), 87 | current_geometry.width(), 88 | current_geometry.height(), 89 | ) 90 | return tasking_widget 91 | 92 | 93 | def filter_basemaps_by_name(name, qtbot, basemaps_widget, qgis_debug_enabled): 94 | qtbot.mouseClick(basemaps_widget.btnAll, QtCore.Qt.LeftButton) 95 | qtbot.keyClicks(basemaps_widget.textBasemapsFilter, name) 96 | 97 | qtbot.mouseClick(basemaps_widget.btnBasemapsFilter, QtCore.Qt.LeftButton) 98 | qgis_debug_wait(qtbot, qgis_debug_enabled) 99 | 100 | qtbot.keyClicks(basemaps_widget.comboSeriesName, name[0]) 101 | qtbot.keyPress(basemaps_widget.comboSeriesName, QtCore.Qt.Key_Enter) 102 | qgis_debug_wait(qtbot, qgis_debug_enabled) 103 | 104 | 105 | def get_random_string(length=8): 106 | alphanumeric = "0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz" 107 | return "".join(random.choice(alphanumeric) for _ in range(length)).strip() 108 | 109 | 110 | def get_recent_release_from_changelog(root_dir: pathlib.Path): 111 | config = configparser.ConfigParser() 112 | config.read(root_dir / "planet_explorer" / "metadata.txt") 113 | changelog = config["general"]["changelog"] 114 | recent_release = None 115 | for entry in changelog.split("\n"): 116 | if entry.startswith("v"): 117 | recent_release = entry.split(" ")[0] 118 | break 119 | if not recent_release: 120 | raise RuntimeError( 121 | "Could not find most recent release in 'planet_explorer/metadata.txt'" 122 | ) 123 | else: 124 | return recent_release 125 | -------------------------------------------------------------------------------- /planet_explorer/ui/OGLdpf.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetlabs/qgis-planet-plugin/7c12eb6484758be4224d301bd70df6a0b54f94ca/planet_explorer/ui/OGLdpf.log -------------------------------------------------------------------------------- /planet_explorer/ui/pe_legacy_warning_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 521 10 | 310 11 | 12 | 13 | 14 | Saved Search 15 | 16 | 17 | 18 | 19 | 20 | <html><head/><body><p><span style=" font-size:12pt;">Update Saved Search to PSScene</span></p><p>We recently made some changes to our catalog. Your saved search includes legacy assets listed below. Please update to PSScene before continuing. All saved searches will be automatically updated on [date]</p><p><a href="psscene"><span style=" text-decoration: underline; color:#0000ff;">Learn more</span></a></p></body></html> 21 | 22 | 23 | true 24 | 25 | 26 | true 27 | 28 | 29 | 30 | 31 | 32 | 33 | Qt::Vertical 34 | 35 | 36 | QSizePolicy::Fixed 37 | 38 | 39 | 40 | 20 41 | 10 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 75 51 | true 52 | 53 | 54 | 55 | Legacy Assets to Update 56 | 57 | 58 | 59 | 60 | 61 | 62 | 20 63 | 64 | 65 | 66 | 67 | Qt::Horizontal 68 | 69 | 70 | 71 | 40 72 | 20 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 4-Band PlanetScope Scene 81 | 82 | 83 | 84 | 85 | 86 | 87 | 3-Band PlanetScope Scene 88 | 89 | 90 | 91 | 92 | 93 | 94 | Qt::Horizontal 95 | 96 | 97 | 98 | 40 99 | 20 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | Qt::Vertical 110 | 111 | 112 | 113 | 20 114 | 40 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | Qt::Horizontal 125 | 126 | 127 | 128 | 40 129 | 20 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | Continue without updating 138 | 139 | 140 | 141 | 142 | 143 | 144 | Update search 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /planet_explorer/ui/pe_legacy_warning_widget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 453 10 | 116 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | false 18 | 19 | 20 | LegacyWarningWidget{ background-color: rgb(200, 255, 255); 21 | border-width: 1; 22 | border-radius: 3; 23 | border-style: solid; 24 | border-color: rgb(10, 10, 10)} 25 | 26 | 27 | 28 | 29 | 9 30 | 31 | 32 | 9 33 | 34 | 35 | 9 36 | 37 | 38 | 9 39 | 40 | 41 | 42 | 43 | 44 | 0 45 | 0 46 | 47 | 48 | 49 | 50 | 16777215 51 | 50 52 | 53 | 54 | 55 | 56 | background-color: rgb(237, 238, 255); 57 | 58 | 59 | 60 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 61 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 62 | p, li { white-space: pre-wrap; } 63 | </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> 64 | <p align="center" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt; font-weight:600;">Your saved search includes legacy imagery types</span></p> 65 | <p align="center" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">PSScene3Bands, PSScene4Bands</span></p></body></html> 66 | 67 | 68 | 69 | 70 | 71 | 72 | false 73 | 74 | 75 | Click here to update search 76 | 77 | 78 | 79 | 80 | 81 | 82 | <a href="https://developers.planet.com/docs/data/psscene">Learn more about this change</a> 83 | 84 | 85 | Qt::AlignCenter 86 | 87 | 88 | true 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /planet_explorer/ui/pe_open_saved_search_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 435 11 | 12 | 13 | 14 | Open Search 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Select Saved Search 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 0 34 | 0 35 | 36 | 37 | 38 | Delete 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | <b>Data Range</b> 48 | 49 | 50 | 51 | 52 | 53 | 54 | range 55 | 56 | 57 | 58 | 59 | 60 | 61 | <b>Filters</b> 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 200 70 | 0 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | QFrame::StyledPanel 79 | 80 | 81 | QFrame::Raised 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | Qt::Horizontal 91 | 92 | 93 | 94 | 40 95 | 20 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | Load 104 | 105 | 106 | 107 | 108 | 109 | 110 | Cancel 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /planet_explorer/ui/pe_orders_monitor_dockwidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | PlanetOrdersMonitorDockWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 626 10 | 348 11 | 12 | 13 | 14 | Orders Monitor 15 | 16 | 17 | 18 | 19 | 0 20 | 21 | 22 | 0 23 | 24 | 25 | 0 26 | 27 | 28 | 0 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Only orders available to download 39 | 40 | 41 | 42 | 43 | 44 | 45 | Qt::Horizontal 46 | 47 | 48 | 49 | 40 50 | 20 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | Refresh 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /planet_explorer/ui/pe_planet_inspector_dockwidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | PlanetOrdersMonitorDockWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 626 10 | 348 11 | 12 | 13 | 14 | Planet Inspector 15 | 16 | 17 | 18 | 19 | 0 20 | 21 | 22 | 0 23 | 24 | 25 | 0 26 | 27 | 28 | 0 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Use Planet Inspector to discover meta data about previewed and connected Planet Basemaps. 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 0 47 | 0 48 | 49 | 50 | 51 | 52 | 53 | 54 | true 55 | 56 | 57 | 58 | 59 | 60 | 61 | Use this tool to explore Planet basemaps you have previewed or to which you have an established basemap streaming connection. 62 | 63 | 64 | true 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /planet_explorer/ui/show_curl_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 485 10 | 501 11 | 12 | 13 | 14 | API Request 15 | 16 | 17 | 18 | 19 | 20 | The text below represents the proper Planet API call to generate the search you have performed 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | Request tool 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | cURL 38 | 39 | 40 | 41 | 42 | Python 3 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | Qt::Horizontal 51 | 52 | 53 | 54 | 40 55 | 20 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | Close 71 | 72 | 73 | 74 | 75 | 76 | 77 | Qt::Horizontal 78 | 79 | 80 | 81 | 40 82 | 20 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | Copy snippet 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /planet_explorer/ui/test_dispatch_callback_watcher.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 447 10 | 438 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 0 24 | 0 25 | 26 | 27 | 28 | Start async call 29 | 30 | 31 | 32 | 33 | 34 | 35 | Cancel 36 | 37 | 38 | 39 | 40 | 41 | 42 | Qt::Horizontal 43 | 44 | 45 | 46 | 0 47 | 0 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 150 57 | 0 58 | 59 | 60 | 61 | 0 62 | 63 | 64 | -1 65 | 66 | 67 | false 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /planet_explorer/ui/test_thumbnail_cache.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 450 10 | 653 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 23 | 24 | 0 25 | 26 | 27 | 0 28 | 29 | 30 | 0 31 | 32 | 33 | 34 | 35 | Cancel 36 | 37 | 38 | 39 | 40 | 41 | 42 | Qt::Horizontal 43 | 44 | 45 | 46 | 0 47 | 0 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 150 57 | 0 58 | 59 | 60 | 61 | 0 62 | 63 | 64 | -1 65 | 66 | 67 | false 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /qgis3-conda-forge-env.yml: -------------------------------------------------------------------------------- 1 | name: qgis3-conda-forge 2 | channels: 3 | - conda-forge 4 | - defaults 5 | dependencies: 6 | - analytics-python=1.2.9 7 | - autopep8=1.4.4 8 | - click=7.0 9 | - conda-wrappers=1.1.3 10 | - doc8=0.8.0 11 | - geojsonio=0.0.3 12 | - iso8601=0.1.12 13 | - jinja2=2.10.1 14 | - jq=1.6 15 | - paver=1.2.4 16 | - pep8=1.7.1 17 | - pip=19.2.3 18 | - planet=1.4.1 19 | - pygments=2.4.2 20 | - pyjwt=1.7.1 21 | - pylint=2.3.1 22 | - qgis=3.4.12 23 | - retrying=1.3.3 24 | - rope=0.14.0 25 | - shapely=1.6.4 26 | - sphinx=2.2.0 27 | - pip: 28 | - qgiscommons==2.0.12 29 | - sentry-sdk==0.12.3 30 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click 2 | requests-futures<1.0 3 | planet~=1.4 4 | iso8601 5 | mercantile 6 | analytics-python 7 | sentry-sdk 8 | monotonic 9 | backoff 10 | httpx 11 | -------------------------------------------------------------------------------- /requirements_test.txt: -------------------------------------------------------------------------------- 1 | pytest<=7.1.3 2 | pytest-qgis 3 | # Version < 4 required for QGIS 3.10 4 | pytest-qt<4 5 | pytest-rerunfailures 6 | -------------------------------------------------------------------------------- /scripts/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd $(dirname $0)/.. 4 | export GITHUB_WORKSPACE=$PWD 5 | docker-compose -f .docker/docker-compose.gh.yml run -e QGIS_TEST_VERSION=latest -e PLANET_USER=${PLANET_USER} -e PLANET_PASSWORD=${PLANET_PASSWORD} qgis /usr/src/.docker/run-docker-tests.sh $@ 6 | docker-compose -f .docker/docker-compose.gh.yml rm -s -f 7 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 99 3 | exclude = planet_explorer/resources/*,pavement.py 4 | 5 | [isort] 6 | profile = black 7 | --------------------------------------------------------------------------------