Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.10"
python-version: "3.12"
- name: Install nox
run: python -m pip install nox
- name: build the documentation and check for warnings
Expand Down
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ dependencies = [
"jupyter-server-proxy", # required for localtileserver
"planet>=2.0,<3.0",
"pyarrow",
"localtileserver>=0.7.0", # first pure rio version
"pygaul>=0.3.1", # use the class implementation
"localtileserver>=0.10.1", # first version with rio-tiler v7 support
"pygaul>=0.4.2", # GAUL 2024 with iso3_code and gaul0_code columns
"pygadm>=0.5.0", # use the class implementation
# miscellaneous
"python-box",
Expand All @@ -47,7 +47,6 @@ dependencies = [
"anyascii", # to decode international names with non latin characters
"natsort",
"typing-extensions",
"rio-tiler<7",
"solara",
"ee-client>=2.5.0", # supports caching
"colorlog",
Expand Down
27 changes: 18 additions & 9 deletions sepal_ui/aoi/aoi_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,22 +389,29 @@ def _from_admin(self, admin: str) -> Self:
"""Set the object according to the given an administrative code in the GADM/GAUL codes.

Args:
admin: the admin code corresponding to FAO GAUl (if gee) or GADM
admin: the admin code corresponding to FAO GAUL (if gee) or GADM
"""
if not admin:
raise Exception(ms.aoi_sel.exception.no_admlyr)

# get the data from either the pygaul or the pygadm libs
# pygaul needs extra work as ISO codes are not included in the GEE dataset
if self.gee:
self.feature_collection = pygaul.AdmItems(admin=admin)
self.feature_collection = pygaul.Items(admin=admin)

# get the ADM0_CODE to get the ISO code
# get the iso3_code directly from GAUL 2024 dataset
feature = self.feature_collection.first()
properties = self.gee_interface.get_info(feature.toDictionary(feature.propertyNames()))

iso = json.loads(self.MAPPING.read_text())[str(properties.get("ADM0_CODE"))]
names = [value for prop, value in properties.items() if "NAME" in prop]
# GAUL 2024 includes iso3_code directly, fallback to mapping for disputed areas
iso = properties.get("iso3_code", "")
if not iso or (
isinstance(iso, str) and iso.startswith("x")
): # 'x' prefix means disputed/unknown
gaul0_code = str(properties.get("gaul0_code", ""))
iso = json.loads(self.MAPPING.read_text()).get(gaul0_code, "UNK")

# GAUL 2024 uses lowercase column names: gaul0_name, gaul1_name, gaul2_name
names = [value for prop, value in properties.items() if "_name" in prop]

# generate the name from the columns
names = [su.normalize_str(name) for name in names]
Expand Down Expand Up @@ -649,7 +656,9 @@ def _load_gdf(self):
self._gdf = gpd.GeoDataFrame.from_features(features).set_crs(epsg=4326)

if self.method in ["ADMIN0", "ADMIN1", "ADMIN2"]:

gaul_country = str(self._gdf.ADM0_CODE.unique()[0])
iso = json.loads(self.MAPPING.read_text())[gaul_country]
# GAUL 2024 includes iso3_code directly, fallback to mapping for disputed areas
iso = self._gdf.iso3_code.unique()[0] if "iso3_code" in self._gdf.columns else None
if not iso or (isinstance(iso, str) and iso.startswith("x")):
gaul_country = str(self._gdf.gaul0_code.unique()[0])
iso = json.loads(self.MAPPING.read_text()).get(gaul_country, "UNK")
self._gdf["ISO"] = iso
4 changes: 2 additions & 2 deletions sepal_ui/aoi/aoi_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class AdminField(sw.Select):
def __init__(self, level: int, parent: Optional[sw.Select] = None, gee: bool = True) -> None:
"""An admin level selector.

It is binded to ee (GAUL 2015) or not (GADM). Allows to select administrative codes taking into account the administrative parent code and displaying humanly readable administrative names.
It is binded to ee (GAUL 2024) or not (GADM). Allows to select administrative codes taking into account the administrative parent code and displaying humanly readable administrative names.

Args:
level: The administrative level of the field
Expand Down Expand Up @@ -138,7 +138,7 @@ def get_items(self, filter_: str = "") -> Self:
Args:
filter\_: The code of the parent v_model to filter the current results
"""
AdmNames = pygaul.AdmNames if self.gee else pygadm.Names
AdmNames = pygaul.Names if self.gee else pygadm.Names
df = AdmNames(admin=filter_, content_level=self.level)
df = df.sort_values(by=[df.columns[0]])

Expand Down
2 changes: 1 addition & 1 deletion tests/data/warning_list.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
ERROR: Document or section may not begin with a transition.
WARNING: Document or section may not begin with a transition. [docutils]
WARNING: document isn't included in any toctree
30 changes: 18 additions & 12 deletions tests/test_aoi/test_AoiModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ def test_init_ee(gee_dir: Path) -> None:
assert aoi_model.name == "feature_collection_data_0"

# with a default admin
admin = "110" # GAUL Vatican city
# GAUL 2024 code for Holy See - codes may change between dataset versions
admin = "307"
aoi_model = aoi.AoiModel(admin=admin, folder=gee_dir)
assert aoi_model.name == "VAT"

Expand Down Expand Up @@ -112,10 +113,10 @@ def test_get_fields(test_model: aoi.AoiModel) -> None:
aoi_model = aoi.AoiModel()
aoi_model.get_fields("toto")

# init
column = "ADM0_CODE"
# init (GAUL 2024 uses lowercase column names)
column = "gaul0_code"
res = test_model.get_fields(column)
assert res == [110]
assert res == [307]

return

Expand All @@ -132,11 +133,14 @@ def test_get_selected(test_model: aoi.AoiModel) -> None:
aoi_model = aoi.AoiModel()
aoi_model.get_fields("toto", "toto")

# select the vatican feature in GAUL 2015
ee_vat = ee.FeatureCollection("FAO/GAUL/2015/level0").filter(ee.Filter.eq("ADM0_CODE", "110"))
# select the vatican feature in GAUL 2024 (test_model uses pygaul which is GAUL 2024)
# Asset path: projects/sat-io/open-datasets/FAO/GAUL/GAUL_2024_L0
ee_vat = ee.FeatureCollection("projects/sat-io/open-datasets/FAO/GAUL/GAUL_2024_L0").filter(
ee.Filter.eq("gaul0_code", 307)
)

# select the geometry associated with Vatican city (all of it)
column, field = ("ADM0_CODE", "110")
# select the geometry associated with Vatican city (all of it) using GAUL 2024 column names
column, field = ("gaul0_code", 307)
feature = test_model.get_selected(column, field)

# assert they are the same
Expand Down Expand Up @@ -263,8 +267,8 @@ def test_from_admin(gee_dir: Path) -> None:
with pytest.raises(Exception):
aoi_model._from_admin(0)

# test france
aoi_model._from_admin("110")
# GAUL 2024 code for Holy See - codes may change between dataset versions
aoi_model._from_admin("307")
assert aoi_model.name == "VAT"

return
Expand Down Expand Up @@ -469,15 +473,17 @@ def square() -> dict:

@pytest.fixture(scope="function")
def test_model(gee_dir: Path) -> aoi.AoiModel:
"""Create a test AoiModel based on GEE using Vatican.
"""Create a test AoiModel based on GEE using Holy See (Vatican).

Args:
gee_dir: the path to the session gee_dir folder (including hash)

Returns:
the model object
"""
admin = "110" # vatican city (smalest adm0 feature)
# GAUL 2024 code for Holy See (Vatican) - smallest adm0 feature, ideal for fast tests
# Note: GAUL codes may change between dataset versions (was 110 in GAUL 2015)
admin = "307"
return aoi.AoiModel(admin=admin, folder=gee_dir)


Expand Down
13 changes: 6 additions & 7 deletions tests/test_aoi/test_AoiModel/test_get_columns.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
- ADM0_CODE
- ADM0_NAME
- DISP_AREA
- EXP0_YEAR
- STATUS
- STR0_YEAR
- Shape_Leng
- continent
- disp_en
- gaul0_code
- gaul0_name
- iso3_code
- map_code
6 changes: 3 additions & 3 deletions tests/test_aoi/test_AoiModel/test_total_bounds.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
- 12.4458
- 12.4459
- 41.9002
- 12.4577
- 41.9067
- 12.4583
- 41.9074
8 changes: 4 additions & 4 deletions tests/test_aoi/test_AoiView.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ def test_init_ee(gee_dir: Path) -> None:
view = aoi.AoiView(folder=gee_dir)
assert isinstance(view, aoi.AoiView)

# test model name when using view
view = aoi.AoiView(admin="110", folder=gee_dir)
# GAUL 2024 code for Holy See - codes may change between dataset versions
view = aoi.AoiView(admin="307", folder=gee_dir)
assert view.model.name == "VAT"

return
Expand All @@ -79,9 +79,9 @@ def test_admin_ee(gee_dir: Path) -> None:
Args:
gee_dir: the session gee directory where assets are saved
"""
# test if admin0 is in Gaul
# test if admin0 is in Gaul (GAUL 2024 uses gaul0_code)
view = aoi.AoiView(folder=gee_dir)
first_gaul_item = {"text": "Abyei", "value": "102"}
first_gaul_item = {"text": "Abyei", "value": "100"}
assert first_gaul_item == view.w_admin_0.items[0]

return
Expand Down