"""Tests for islands, coastal features, and UNESCO sites download paths.""" from __future__ import annotations from typing import Any from unittest.mock import MagicMock, patch import geopandas as gpd from shapely.geometry import Polygon from python_pkg.geo_data._poland_water import ( get_polish_coastal_features, get_polish_islands, get_polish_unesco_sites, ) def _make_relation_element(name: str, *, include_outer: bool = True) -> dict[str, Any]: """Create a mock OSM relation element.""" members = [] if include_outer: members.append( { "role": "outer", "geometry": [ {"lon": 0, "lat": 0}, {"lon": 1, "lat": 0}, {"lon": 1, "lat": 1}, {"lon": 0, "lat": 1}, ], } ) return {"type": "relation", "tags": {"name": name}, "members": members} _POLY = Polygon([(20, 50), (21, 50), (21, 51), (20, 51)]) class TestGetPolishIslands: """Tests for get_polish_islands.""" @patch("python_pkg.geo_data._poland_water.gpd.read_file") @patch("python_pkg.geo_data._poland_water.CACHE_DIR") def test_cached_with_area( self, mock_cache_dir: MagicMock, mock_read: MagicMock ) -> None: mock_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) mock_path.exists.return_value = True mock_gdf = gpd.GeoDataFrame( {"name": ["Wolin"], "area_km2": [265.0]}, geometry=[_POLY], crs="EPSG:4326", ) mock_read.return_value = mock_gdf result = get_polish_islands() assert result.iloc[0]["area_km2"] == 265.0 @patch("python_pkg.geo_data._poland_water.gpd.read_file") @patch("python_pkg.geo_data._poland_water.CACHE_DIR") def test_cached_without_area( self, mock_cache_dir: MagicMock, mock_read: MagicMock ) -> None: mock_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) mock_path.exists.return_value = True mock_gdf = gpd.GeoDataFrame( {"name": ["Wolin"]}, geometry=[_POLY], crs="EPSG:4326", ) mock_read.return_value = mock_gdf result = get_polish_islands() assert len(result) == 1 def test_downloads_islands(self) -> None: with ( patch("python_pkg.geo_data._poland_water.sys.stdout"), patch("python_pkg.geo_data._poland_water.CACHE_DIR") as mock_cache_dir, patch("python_pkg.geo_data._poland_water._overpass_query") as mock_query, patch("python_pkg.geo_data._poland_water._ensure_cache_dir"), patch( "python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features" ) as mock_from_features, patch( "python_pkg.geo_data._poland_water._add_area_column" ) as mock_add_area, ): mock_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) mock_path.exists.return_value = False mock_query.return_value = { "elements": [ { "type": "way", "tags": {"name": "Wolin"}, "geometry": [ {"lon": 0, "lat": 0}, {"lon": 1, "lat": 0}, {"lon": 1, "lat": 1}, {"lon": 0, "lat": 1}, ], }, # Duplicate { "type": "way", "tags": {"name": "Wolin"}, "geometry": [ {"lon": 0, "lat": 0}, {"lon": 1, "lat": 0}, {"lon": 1, "lat": 1}, {"lon": 0, "lat": 1}, ], }, # No name {"type": "way", "tags": {}, "geometry": []}, # Geometry fails { "type": "way", "tags": {"name": "Tiny"}, "geometry": [{"lon": 0, "lat": 0}], }, ] } mock_gdf = gpd.GeoDataFrame( {"name": ["Wolin"]}, geometry=[_POLY], crs="EPSG:4326", ) mock_from_features.return_value = mock_gdf gdf_with_area = mock_gdf.copy() gdf_with_area["area_km2"] = [265.0] mock_add_area.return_value = gdf_with_area result = get_polish_islands() assert len(result) == 1 def test_downloads_islands_empty(self) -> None: with ( patch("python_pkg.geo_data._poland_water.sys.stdout"), patch("python_pkg.geo_data._poland_water.CACHE_DIR") as mock_cache_dir, patch("python_pkg.geo_data._poland_water._overpass_query") as mock_query, patch("python_pkg.geo_data._poland_water._ensure_cache_dir"), patch( "python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features" ) as mock_from_features, patch( "python_pkg.geo_data._poland_water._add_area_column" ) as mock_add_area, ): mock_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) mock_path.exists.return_value = False mock_query.return_value = {"elements": []} empty_gdf = gpd.GeoDataFrame({"name": [], "geometry": []}) mock_from_features.return_value = empty_gdf mock_add_area.return_value = empty_gdf result = get_polish_islands() assert len(result) == 0 class TestGetPolishCoastalFeatures: """Tests for get_polish_coastal_features.""" @patch("python_pkg.geo_data._poland_water.gpd.read_file") @patch("python_pkg.geo_data._poland_water.CACHE_DIR") def test_cached_with_length( self, mock_cache_dir: MagicMock, mock_read: MagicMock ) -> None: mock_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) mock_path.exists.return_value = True mock_gdf = gpd.GeoDataFrame( {"name": ["Mierzeja Helska"], "length_km": [35.0]}, geometry=[_POLY], crs="EPSG:4326", ) mock_read.return_value = mock_gdf result = get_polish_coastal_features() assert result.iloc[0]["length_km"] == 35.0 @patch("python_pkg.geo_data._poland_water.gpd.read_file") @patch("python_pkg.geo_data._poland_water.CACHE_DIR") def test_cached_without_length( self, mock_cache_dir: MagicMock, mock_read: MagicMock ) -> None: mock_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) mock_path.exists.return_value = True mock_gdf = gpd.GeoDataFrame( {"name": ["Mierzeja Helska"]}, geometry=[_POLY], crs="EPSG:4326", ) mock_read.return_value = mock_gdf result = get_polish_coastal_features() assert len(result) == 1 def test_downloads_coastal_features(self) -> None: with ( patch("python_pkg.geo_data._poland_water.sys.stdout"), patch("python_pkg.geo_data._poland_water.CACHE_DIR") as mock_cache_dir, patch("python_pkg.geo_data._poland_water._overpass_query") as mock_query, patch("python_pkg.geo_data._poland_water._ensure_cache_dir"), patch( "python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features" ) as mock_from_features, patch( "python_pkg.geo_data._poland_water._add_length_column" ) as mock_add_length, ): mock_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) mock_path.exists.return_value = False mock_query.return_value = { "elements": [ # Peninsula (polygon type) { "type": "way", "tags": {"name": "Hel", "natural": "peninsula"}, "geometry": [ {"lon": 0, "lat": 0}, {"lon": 1, "lat": 0}, {"lon": 1, "lat": 1}, {"lon": 0, "lat": 1}, ], }, # Cliff (line type) { "type": "way", "tags": {"name": "Klif Orłowski", "natural": "cliff"}, "geometry": [ {"lon": 0, "lat": 0}, {"lon": 1, "lat": 1}, ], }, # Duplicate { "type": "way", "tags": {"name": "Hel", "natural": "peninsula"}, "geometry": [ {"lon": 0, "lat": 0}, {"lon": 1, "lat": 0}, {"lon": 1, "lat": 1}, {"lon": 0, "lat": 1}, ], }, # No name { "type": "way", "tags": {"natural": "cliff"}, "geometry": [], }, # Geometry fails (no geometry key) { "type": "node", "tags": {"name": "X", "natural": "cliff"}, }, ] } mock_gdf = gpd.GeoDataFrame( {"name": ["Hel", "Klif Orłowski"]}, geometry=[_POLY, _POLY], crs="EPSG:4326", ) mock_from_features.return_value = mock_gdf gdf_with_length = mock_gdf.copy() gdf_with_length["length_km"] = [35.0, 5.0] mock_add_length.return_value = gdf_with_length result = get_polish_coastal_features() assert len(result) == 2 def test_downloads_coastal_features_empty(self) -> None: with ( patch("python_pkg.geo_data._poland_water.sys.stdout"), patch("python_pkg.geo_data._poland_water.CACHE_DIR") as mock_cache_dir, patch("python_pkg.geo_data._poland_water._overpass_query") as mock_query, patch("python_pkg.geo_data._poland_water._ensure_cache_dir"), patch( "python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features" ) as mock_from_features, patch( "python_pkg.geo_data._poland_water._add_length_column" ) as mock_add_length, ): mock_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) mock_path.exists.return_value = False mock_query.return_value = {"elements": []} empty_gdf = gpd.GeoDataFrame({"name": [], "geometry": []}) mock_from_features.return_value = empty_gdf mock_add_length.return_value = empty_gdf result = get_polish_coastal_features() assert len(result) == 0 class TestGetPolishUnescoSites: """Tests for get_polish_unesco_sites.""" @patch("python_pkg.geo_data._poland_water.gpd.read_file") @patch("python_pkg.geo_data._poland_water.CACHE_DIR") def test_cached(self, mock_cache_dir: MagicMock, mock_read: MagicMock) -> None: mock_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) mock_path.exists.return_value = True mock_gdf = MagicMock(spec=gpd.GeoDataFrame) mock_read.return_value = mock_gdf result = get_polish_unesco_sites() assert result is mock_gdf @patch("python_pkg.geo_data._poland_water.gpd.GeoDataFrame.from_features") @patch("python_pkg.geo_data._poland_water._ensure_cache_dir") @patch("python_pkg.geo_data._poland_water._overpass_query") @patch("python_pkg.geo_data._poland_water.CACHE_DIR") @patch("python_pkg.geo_data._poland_water.sys.stdout") def test_downloads_unesco_sites( self, mock_stdout: MagicMock, mock_cache_dir: MagicMock, mock_query: MagicMock, mock_ensure: MagicMock, mock_from_features: MagicMock, ) -> None: mock_path = MagicMock() mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path) mock_path.exists.return_value = False mock_query.return_value = { "elements": [ # Node type { "type": "node", "tags": {"name": "Kopalnia Soli Wieliczka"}, "lon": 20.0, "lat": 50.0, }, # Relation type _make_relation_element("Stare Miasto w Krakowie"), # Way type with enough coords { "type": "way", "tags": {"name": "Auschwitz"}, "geometry": [ {"lon": 19, "lat": 50}, {"lon": 19.1, "lat": 50}, {"lon": 19.1, "lat": 50.1}, {"lon": 19, "lat": 50.1}, ], }, # Way already closed { "type": "way", "tags": {"name": "Zamość"}, "geometry": [ {"lon": 23, "lat": 50.7}, {"lon": 23.1, "lat": 50.7}, {"lon": 23.1, "lat": 50.8}, {"lon": 23, "lat": 50.7}, ], }, # Way too few coords { "type": "way", "tags": {"name": "TooShort"}, "geometry": [ {"lon": 19, "lat": 50}, {"lon": 19.1, "lat": 50}, ], }, # Duplicate { "type": "node", "tags": {"name": "Kopalnia Soli Wieliczka"}, "lon": 20.0, "lat": 50.0, }, # No name {"type": "node", "tags": {}, "lon": 0, "lat": 0}, # Unknown type {"type": "area", "tags": {"name": "Ignored"}}, # Relation without outer rings _make_relation_element("NoOuter", include_outer=False), # Way without geometry key {"type": "way", "tags": {"name": "NoGeom"}}, ] } mock_gdf = MagicMock(spec=gpd.GeoDataFrame) mock_from_features.return_value = mock_gdf result = get_polish_unesco_sites() assert result is mock_gdf