Skip to content

Commit

Permalink
Test/base example (#190)
Browse files Browse the repository at this point in the history
* Writing some initial tests for Map.py using pytest-qgis

* Add proper registration of WMS provider

* wip to test add_layers functionality

* Add python env file to use extlibs as pythonpath

* Debug adding provider

* Base example test implemented

* Update CI to authenticate to earthengine

* Add test to pythonpath

* Revert pytest call in CI

* Add pythonpath

* Simplify test execution in CI 

* Add future for CI dependencies

* Reorganized plugin code into a module

* Activate virtualenv to avoid system wide permissions error

* Update python version to 3.9

* Move plugin files to ee_plugin module

Clean up installed plugin to include only extlibs and python files.

* Modify path for paver package to avoid nested ee_plugin

* Add icons, i18n, and contrib to paver packaging

* Update path to ee_plugin module

* Add paver to dev requirements

* Bug 195/add vector layer (#196)

* Move fixtures to separate conftest.py file

The goal will be to reuse the fixture for other tests

* Add unit test reproducing the bud

* Fix bug adding vector layers by creating layers with the EE flag

* Simplify code paths layer is a QgsRasterLayer in any case

---------

Co-authored-by: Anthony Lukach <[email protected]>
  • Loading branch information
zacdezgeo and alukach authored Jan 17, 2025
1 parent eb7a27e commit fb49243
Show file tree
Hide file tree
Showing 33 changed files with 161 additions and 40 deletions.
17 changes: 15 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,27 @@ jobs:
- name: Checkout code
uses: actions/checkout@v3

- name: Set up QGIS environment
- name: Set up QGIS environment
run: |
sudo apt update
sudo apt install -y qgis python3-qgis python3-pyqt5 python3-pytest
- name: Set Environment Variables
run: |
echo "QGIS_HOME=/usr" >> $GITHUB_ENV
echo "QT_QPA_PLATFORM=offscreen" >> $GITHUB_ENV
echo "DISPLAY=:99" >> $GITHUB_ENV
- name: Start Virtual Display
run: Xvfb :99 -screen 0 1024x768x24 &

- name: Configure Earth Engine Credentials
run: |
mkdir -p ~/.config/earthengine
echo '${{ secrets.EE_CREDENTIALS_JSON }}' > ~/.config/earthengine/credentials
- name: Install dependencies
run: |
python3 -m pip install --upgrade pip
pip install -r requirements-dev.txt
- name: Run pre-commit checks
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ temp
venv
.DS_Store
htmlcov
.coverage
1 change: 1 addition & 0 deletions .python.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PYTHONPATH=${PYTHONPATH}:./extlibs:/Applications/QGIS-LTR.app/Contents/MacOS/lib/python3.9
10 changes: 10 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"python.envFile": "${workspaceFolder}/.python.env",
"terminal.integrated.env.osx": {
"PYTHONPATH": "${PYTHONPATH}:./extlibs:/Applications/QGIS-LTR.app/Contents/MacOS/lib/python3.9"
},
"python.testing.pytestEnabled": true,
"python.testing.pytestArgs": [
"test"
],
}
4 changes: 2 additions & 2 deletions Map.py → ee_plugin/Map.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ def addLayer(eeObject, visParams=None, name=None, shown=True, opacity=1.0):
>>> from ee_plugin import Map
>>> Map.addLayer(.....)
"""
from .utils import add_or_update_ee_layer
from . import utils

add_or_update_ee_layer(eeObject, visParams, name, shown, opacity)
utils.add_or_update_ee_layer(eeObject, visParams, name, shown, opacity)


def centerObject(feature, zoom=None):
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions ee_plugin.py → ee_plugin/ee_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction

from ee_plugin import provider

# read the plugin version from metadata
cfg = configparser.ConfigParser()
cfg.read(os.path.join(os.path.dirname(__file__), "metadata.txt"))
Expand All @@ -37,6 +35,8 @@ def __init__(self, iface):
application at run time.
:type iface: QgsInterface
"""
from . import provider

# Save reference to the QGIS interface
self.iface = iface

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes.
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions provider.py → ee_plugin/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def __init__(self, *args, **kwargs):

# create WMS provider
self.wms = QgsProviderRegistry.instance().createProvider("wms", *args, **kwargs)
assert self.wms, f"Failed to create WMS provider: {args}, {kwargs}"

@classmethod
def description(cls):
Expand Down
23 changes: 3 additions & 20 deletions utils.py → ee_plugin/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
QgsRectangle,
)

import ee_plugin
from .ee_plugin import VERSION as ee_plugin_version


def get_ee_image_url(image):
Expand All @@ -27,7 +27,6 @@ def get_ee_image_url(image):

def update_ee_layer_properties(layer, eeObject, visParams, shown, opacity):
layer.dataProvider().set_ee_object(eeObject)

layer.setCustomProperty("ee-layer", True)

if opacity is not None:
Expand All @@ -38,7 +37,7 @@ def update_ee_layer_properties(layer, eeObject, visParams, shown, opacity):
# serialize EE code
ee_object = eeObject.serialize()
ee_object_vis = json.dumps(visParams)
layer.setCustomProperty("ee-plugin-version", ee_plugin.ee_plugin.VERSION)
layer.setCustomProperty("ee-plugin-version", ee_plugin_version)
layer.setCustomProperty("ee-object", ee_object)
layer.setCustomProperty("ee-object-vis", ee_object_vis)

Expand All @@ -51,23 +50,7 @@ def add_ee_image_layer(image, name, shown, opacity):
check_version()

url = "type=xyz&url=" + get_ee_image_url(image)

# EE raster data provider
if image.ee_type == ee.Image:
layer = QgsRasterLayer(url, name, "EE")
# EE vector data provider
if image.ee_type in [ee.Geometry, ee.Feature]:
# TODO
layer = QgsRasterLayer(url, name, "wms")
# EE raster collection data provider
if image.ee_type == ee.ImageCollection:
# TODO
layer = QgsRasterLayer(url, name, "wms")
# EE vector collection data provider
if image.ee_type == ee.FeatureCollection:
# TODO
layer = QgsRasterLayer(url, name, "wms")

layer = QgsRasterLayer(url, name, "EE")
QgsProject.instance().addMapLayer(layer)

if shown is not None:
Expand Down
8 changes: 4 additions & 4 deletions pavement.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
options(
plugin=Bunch(
name="ee_plugin",
ext_libs=path("extlibs"),
source_dir=path("."),
ext_libs=path(os.path.join("ee_plugin", "extlibs")),
source_dir=path("ee_plugin"),
package_dir=path("."),
tests=["test", "tests"],
excludes=[
Expand Down Expand Up @@ -76,7 +76,7 @@ def setup():
def install(options):
"""install plugin to qgis"""
plugin_name = options.plugin.name
src = path(__file__).dirname()
src = options.plugin.source_dir
if platform.system() == "Windows":
dst = (
path(
Expand Down Expand Up @@ -149,5 +149,5 @@ def filter_excludes(files):
for root, dirs, files in os.walk(src_dir):
for f in filter_excludes(files):
relpath = os.path.relpath(root, ".")
zipFile.write(path(root) / f, path("ee_plugin") / path(relpath) / f)
zipFile.write(path(root) / f, path(relpath) / f)
filter_excludes(dirs)
3 changes: 0 additions & 3 deletions pytest.ini

This file was deleted.

8 changes: 6 additions & 2 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
-r requirements.txt # Include all main requirements

# Development dependencies
pytest
pytest==7.4.3 # pytest-qgis does not support pytest>=8
pytest-cov
pre-commit
pytest-qgis==2.1.0
pre-commit
pyqt5
future
paver
Binary file renamed .coverage → symbology-style.db
Binary file not shown.
30 changes: 30 additions & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import ee
from pytest import fixture
from qgis.utils import plugins
from PyQt5.QtCore import QSettings, QCoreApplication

from ee_plugin.ee_plugin import GoogleEarthEnginePlugin


@fixture(scope="session", autouse=True)
def setup_ee():
"""Initialize the Earth Engine API."""
print("Initializing Earth Engine API...")
ee.Initialize()
ee.Authenticate(auth_mode="localhost", quiet=True)


@fixture(scope="session", autouse=True)
def load_ee_plugin(qgis_app, setup_ee):
"""Load Earth Engine plugin and configure QSettings."""

# Set QSettings values required by the plugin
QCoreApplication.setOrganizationName("QGIS")
QCoreApplication.setApplicationName("QGIS Testing")
QSettings().setValue("locale/userLocale", "en")

# Initialize and register the plugin
plugin = GoogleEarthEnginePlugin(qgis_app)
plugins["ee_plugin"] = plugin
plugin.check_version()
yield qgis_app
7 changes: 6 additions & 1 deletion test/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ def test_read_init(self):
]

file_path = os.path.abspath(
os.path.join(os.path.dirname(__file__), os.pardir, "metadata.txt")
os.path.join(
os.path.dirname(__file__),
os.pardir,
"ee_plugin",
"metadata.txt",
)
)
LOGGER.info(file_path)
metadata = []
Expand Down
31 changes: 31 additions & 0 deletions test/test_map.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from ee_plugin import Map


def test_add_layer():
"""Test adding a layer to the map."""
import ee

image = ee.Image("USGS/SRTMGL1_003")
vis_params = {
"min": 0,
"max": 4000,
"palette": ["006633", "E5FFCC", "662A00", "D8D8D8", "F5F5F5"],
}

# Add the layer to the map
Map.addLayer(image, vis_params, "DEM")


def test_get_bounds():
"""Test getting the bounds of the map."""
bounds = Map.getBounds()
assert len(bounds) == 4, "Bounds do not have the expected format."
assert all(
isinstance(coord, (float, int)) for coord in bounds
), "Bounds coordinates are not numeric."


def test_get_scale():
"""Test getting the map scale."""
scale = Map.getScale()
assert scale > 0, "Scale should be a positive number."
1 change: 1 addition & 0 deletions test/test_qgis_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def test_qgis_environment(self):
r = QgsProviderRegistry.instance()
self.assertIn("gdal", r.providerList())
self.assertIn("ogr", r.providerList())
self.assertIn("wms", r.providerList()) # needed for our EE provider

def test_projection(self):
"""Test that QGIS properly parses a wkt string."""
Expand Down
6 changes: 3 additions & 3 deletions test/test_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ class GoogleEarthEngineResourcesTest(unittest.TestCase):
def test_icon_png(self):
"""Test image paths exist"""
paths = (
"icons/google-cloud-project.svg",
"icons/google-cloud.svg",
"./icons/earth-engine.svg",
"ee_plugin/icons/google-cloud-project.svg",
"ee_plugin/icons/google-cloud.svg",
"ee_plugin/icons/earth-engine.svg",
)
for path in paths:
icon = QIcon(path)
Expand Down
2 changes: 1 addition & 1 deletion test/test_translations.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def test_qgis_translations(self):
"""Test that translations work."""
parent_path = os.path.join(__file__, os.path.pardir, os.path.pardir)
dir_path = os.path.abspath(parent_path)
file_path = os.path.join(dir_path, "i18n", "af.qm")
file_path = os.path.join(dir_path, "ee_plugin", "i18n", "af.qm")
translator = QTranslator()
translator.load(file_path)
QCoreApplication.installTranslator(translator)
Expand Down
44 changes: 44 additions & 0 deletions test/test_vector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import ee
import pytest

from ee_plugin import Map

# initialize the Earth Engine API is required to use the ee.Geometry module
# and runs before fixtures
ee.Initialize()

geometries = [
(ee.Geometry.Point([1.5, 1.5]), {"color": "1eff05"}, "point"),
(
ee.Geometry.LineString([[-35, -10], [35, -10], [35, 10], [-35, 10]]),
{"color": "FF0000"},
"lineString",
),
(
ee.Geometry.LinearRing(
[[-35, -10], [35, -10], [35, 10], [-35, 10], [-35, -10]]
),
{"color": "ee38ff"},
"linearRing",
),
(ee.Geometry.Rectangle([-40, -20, 40, 20]), {"color": "ffa05c"}, "rectangle"),
(
ee.Geometry.Polygon([[[-5, 40], [65, 40], [65, 60], [-5, 60], [-5, 40]]]),
{"color": "FF0000"},
"geodesic polygon",
),
(
ee.Geometry(
ee.Geometry.Polygon([[[-5, 40], [65, 40], [65, 60], [-5, 60], [-5, 40]]]),
None,
False,
),
{"color": "000000"},
"planar polygon",
),
]


@pytest.mark.parametrize("geometry, vis_params, layer_name", geometries)
def test_add_geometry_layer(geometry, vis_params, layer_name):
Map.addLayer(geometry, vis_params, layer_name), "Layer added successfully"

0 comments on commit fb49243

Please sign in to comment.