diff --git a/cli/cli.py b/cli/cli.py index be093917..8a91f5f4 100644 --- a/cli/cli.py +++ b/cli/cli.py @@ -1021,7 +1021,9 @@ def import_project_area_boundaries_command( result = import_project_area_boundaries(layer_url=layer_url) typer.echo(f"Fetched {result.fetched} feature(s).") typer.echo(f"Matched {result.matched} group row(s).") + typer.echo(f"Created {result.created} group(s).") typer.echo(f"Updated {result.updated} group project area(s).") + typer.echo(f"Skipped {result.skipped} unchanged group(s).") if result.unmatched_locations: typer.echo( "Unmatched locations: " + ", ".join(result.unmatched_locations), diff --git a/cli/project_area_import.py b/cli/project_area_import.py index 2f748530..ade08a86 100644 --- a/cli/project_area_import.py +++ b/cli/project_area_import.py @@ -25,6 +25,8 @@ class ProjectAreaImportResult: fetched: int matched: int updated: int + created: int + skipped: int unmatched_locations: tuple[str, ...] @@ -32,6 +34,12 @@ def _normalize_name(value: str) -> str: return value.strip().lower() +def _geoms_equal(geom1: str, geom2: str) -> bool: + from shapely import wkt + + return wkt.loads(geom1).equals(wkt.loads(geom2)) + + def _geojson_to_multipolygon_wkt(geometry: dict[str, Any]) -> str: geom = shape(geometry) if isinstance(geom, Polygon): @@ -77,6 +85,7 @@ def _fetch_project_area_features( def import_project_area_boundaries( layer_url: str = PROJECT_AREA_LAYER_URL, + group_type: str = "Geographic Area", ) -> ProjectAreaImportResult: with httpx.Client(timeout=60.0) as client: features = _fetch_project_area_features(client, layer_url) @@ -84,6 +93,8 @@ def import_project_area_boundaries( unmatched_locations: list[str] = [] matched = 0 updated = 0 + created = 0 + skipped = 0 with session_ctx() as session: for feature in features: @@ -94,24 +105,45 @@ def import_project_area_boundaries( if not location_name or geometry is None: continue + normalized_name = _normalize_name(location_name) groups = session.scalars( select(Group).where( - func.lower(func.trim(Group.name)) == _normalize_name(location_name) + func.lower(func.trim(Group.name)) == normalized_name, + Group.group_type == group_type, ) ).all() - if not groups: - unmatched_locations.append(location_name) - continue - - matched += len(groups) project_area = WKTElement( _geojson_to_multipolygon_wkt(geometry), srid=4326, ) + + if not groups: + new_group = Group( + name=location_name, + group_type=group_type, + project_area=project_area, + ) + session.add(new_group) + created += 1 + matched += 1 + continue + + matched += len(groups) for group in groups: - group.project_area = project_area - updated += 1 + old_wkt = None + if group.project_area is not None: + from shapely import wkb + + old_wkt = wkb.loads(bytes(group.project_area.data)).wkt + + new_wkt = project_area.desc + + if old_wkt is None or not _geoms_equal(old_wkt, new_wkt): + group.project_area = project_area + updated += 1 + else: + skipped += 1 session.commit() @@ -119,5 +151,7 @@ def import_project_area_boundaries( fetched=len(features), matched=matched, updated=updated, + created=created, + skipped=skipped, unmatched_locations=tuple(sorted(set(unmatched_locations))), )