giga-spatial 0.6.2__py3-none-any.whl → 0.6.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {giga_spatial-0.6.2.dist-info → giga_spatial-0.6.4.dist-info}/METADATA +18 -8
- {giga_spatial-0.6.2.dist-info → giga_spatial-0.6.4.dist-info}/RECORD +15 -15
- gigaspatial/__init__.py +1 -1
- gigaspatial/config.py +6 -0
- gigaspatial/handlers/__init__.py +7 -3
- gigaspatial/handlers/boundaries.py +196 -43
- gigaspatial/handlers/ghsl.py +7 -6
- gigaspatial/handlers/giga.py +641 -0
- gigaspatial/handlers/hdx.py +411 -143
- gigaspatial/handlers/maxar_image.py +1 -2
- gigaspatial/handlers/rwi.py +119 -121
- gigaspatial/processing/tif_processor.py +88 -2
- {giga_spatial-0.6.2.dist-info → giga_spatial-0.6.4.dist-info}/WHEEL +0 -0
- {giga_spatial-0.6.2.dist-info → giga_spatial-0.6.4.dist-info}/licenses/LICENSE +0 -0
- {giga_spatial-0.6.2.dist-info → giga_spatial-0.6.4.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: giga-spatial
|
3
|
-
Version: 0.6.
|
3
|
+
Version: 0.6.4
|
4
4
|
Summary: A package for spatial data download & processing
|
5
5
|
Home-page: https://github.com/unicef/giga-spatial
|
6
6
|
Author: Utku Can Ozturk
|
@@ -51,17 +51,27 @@ Dynamic: summary
|
|
51
51
|
|
52
52
|
# GigaSpatial
|
53
53
|
|
54
|
-
|
55
|
-
Giga is a UNICEF-ITU initiative to connect every school to the Internet and every young person to information, opportunity and choice.
|
56
|
-
Giga maps schools' Internet access in real time, creates models for innovative financing, and supports governments contracting for connectivity.
|
54
|
+
## About Giga
|
57
55
|
|
58
|
-
|
56
|
+
[Giga](https://giga.global/) is a UNICEF-ITU initiative to connect every school to the Internet and every young person to information, opportunity and choice.
|
57
|
+
Giga maps schools' Internet access in real time, creates models for innovative financing, and supports governments contracting for connectivity.
|
59
58
|
|
60
59
|
## About GigaSpatial
|
61
60
|
|
62
61
|
**GigaSpatial** is a Python package developed as part of the Giga Applied Science Team to handle geospatial data efficiently. It provides tools for downloading, processing, and analyzing geospatial data, enabling users to work with datasets such as OpenStreetMap (OSM), Global Human Settlement Layer (GHSL), Microsoft Global Buildings, Google Open Buildings, and more. The package is designed to support Giga's mission by providing robust geospatial capabilities for mapping and analyzing school connectivity.
|
63
62
|
|
64
|
-
|
63
|
+
## Installation
|
64
|
+
|
65
|
+
See the [installation docs](https://unicef.github.io/giga-spatial/getting-started/installation/) for all details. GigaSpatial requires Python 3.10 or above and depends on the following key packages:
|
66
|
+
|
67
|
+
- geopandas
|
68
|
+
- pandas
|
69
|
+
- shapely
|
70
|
+
- rasterio
|
71
|
+
|
72
|
+
We recommend using a virtual environment for installation. See the [installation docs](https://unicef.github.io/giga-spatial/getting-started/installation/) for more details.
|
73
|
+
|
74
|
+
## Key Features
|
65
75
|
- **Data Downloading**: Download geospatial data from various sources including GHSL, Microsoft Global Buildings, Google Open Buildings, OpenCellID, and HDX datasets.
|
66
76
|
- **Data Processing**: Process and transform geospatial data, such as GeoTIFF files and vector data, with support for compression and efficient handling.
|
67
77
|
- **View Generators**:
|
@@ -74,7 +84,7 @@ Giga maps schools' Internet access in real time, creates models for innovative f
|
|
74
84
|
- Centralized configuration via environment variables or `.env` file
|
75
85
|
- Easy setup of API keys and paths
|
76
86
|
|
77
|
-
|
87
|
+
## Supported Datasets
|
78
88
|
|
79
89
|
The `gigaspatial` package supports data from the following providers:
|
80
90
|
|
@@ -84,7 +94,7 @@ The `gigaspatial` package supports data from the following providers:
|
|
84
94
|
|
85
95
|
---
|
86
96
|
|
87
|
-
|
97
|
+
## View Generators
|
88
98
|
|
89
99
|
The **view generators** in GigaSpatial are designed to enrich the spatial context of school locations and map data into grid or POI locations. This enables users to analyze and visualize geospatial data in meaningful ways.
|
90
100
|
|
@@ -1,6 +1,6 @@
|
|
1
|
-
giga_spatial-0.6.
|
2
|
-
gigaspatial/__init__.py,sha256=
|
3
|
-
gigaspatial/config.py,sha256=
|
1
|
+
giga_spatial-0.6.4.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
2
|
+
gigaspatial/__init__.py,sha256=WMmvm2Keb76yMz8OL_h4fKT34Xpi-1BVfCiTn2QGzz4,22
|
3
|
+
gigaspatial/config.py,sha256=PR6n6NDDD4560zWEbaFiYSitr9PAKik915cxCCMZNQc,8392
|
4
4
|
gigaspatial/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
5
|
gigaspatial/core/io/__init__.py,sha256=y4QNWx6US1-adTuAO_NZwLmjzSQj25HNDL5hUGvEHZc,263
|
6
6
|
gigaspatial/core/io/adls_data_store.py,sha256=Zv-D_8d_2h57HnCUTJb0JWWjXqR_0XH4F8Nu_UFZK9E,11975
|
@@ -19,29 +19,29 @@ gigaspatial/generators/zonal/geometry.py,sha256=XPcX5lT7X7Z1vn72sN-VKLb2hDP9F_w3
|
|
19
19
|
gigaspatial/generators/zonal/mercator.py,sha256=R_KlaqF4lnc0cRqVfcNVO8i0Re21_6w7pnclVKSohcY,3125
|
20
20
|
gigaspatial/grid/__init__.py,sha256=H8SnNAMDafJXJ9bUp2zU0Z3t6s8niqY5rGP5nFhnbLA,45
|
21
21
|
gigaspatial/grid/mercator_tiles.py,sha256=Z_3M4sy1tyxywAo2wmBb6niBP3x-IWgwMkmUp8LOSDg,10492
|
22
|
-
gigaspatial/handlers/__init__.py,sha256=
|
22
|
+
gigaspatial/handlers/__init__.py,sha256=R2rugXR5kF4lLkSO1fjpVDYK_jWdD8U2NbXbW71Ezv8,1523
|
23
23
|
gigaspatial/handlers/base.py,sha256=rL94c3wDjsqzLp4na8FfYXW6tNjVGX6v4M-Ce4LrAro,26413
|
24
|
-
gigaspatial/handlers/boundaries.py,sha256=
|
25
|
-
gigaspatial/handlers/ghsl.py,sha256=
|
26
|
-
gigaspatial/handlers/giga.py,sha256=
|
24
|
+
gigaspatial/handlers/boundaries.py,sha256=UM0lFcTzy64ADdMnPOkzLGJ-OG5P7KyoZtA91GTWxYs,17242
|
25
|
+
gigaspatial/handlers/ghsl.py,sha256=GHao8lkmj1C0-QFqNwH9jr0Lqzu6NTj_7ooQdj1h6ok,27760
|
26
|
+
gigaspatial/handlers/giga.py,sha256=F5ZfcE37a24X-c6Xhyt72C9eZZbyN_gV7w_InxKFMQQ,28348
|
27
27
|
gigaspatial/handlers/google_open_buildings.py,sha256=Liqk7qJhDtB4Ia4uhBe44LFcf-XVKBjRfj-pWlE5erY,16594
|
28
|
-
gigaspatial/handlers/hdx.py,sha256=
|
28
|
+
gigaspatial/handlers/hdx.py,sha256=LTEs_xZF1yPhD8dAdZ_YN8Vcan7iB5_tZ8NjF_ip6u0,18001
|
29
29
|
gigaspatial/handlers/mapbox_image.py,sha256=M_nkJ_b1PD8FG1ajVgSycCb0NRTAI_SLpHdzszNetKA,7786
|
30
|
-
gigaspatial/handlers/maxar_image.py,sha256=
|
30
|
+
gigaspatial/handlers/maxar_image.py,sha256=kcc8uGljQB0Yh0MKBA7lT7KwBbNZwFzuyBklR3db1P4,10204
|
31
31
|
gigaspatial/handlers/microsoft_global_buildings.py,sha256=bQ5WHIv3v0wWrZZUbZkKPRjgdlqIxlK7CV_0zSvdrTw,20292
|
32
32
|
gigaspatial/handlers/ookla_speedtest.py,sha256=EcvSAxJZ9GPfzYnT_C85Qgy2ecc9ndf70Pklk53OdC8,6506
|
33
33
|
gigaspatial/handlers/opencellid.py,sha256=KuJqd-5-RO5ZzyDaBSrTgCK2ib5N_m3RUcPlX5heWwI,10683
|
34
34
|
gigaspatial/handlers/osm.py,sha256=sLNMkOVh1v50jrWw7Z0-HILY5QTQjgKCHCeAfXj5jA8,14084
|
35
35
|
gigaspatial/handlers/overture.py,sha256=lKeNw00v5Qia7LdWORuYihnlKEqxE9m38tdeRrvag9k,4218
|
36
|
-
gigaspatial/handlers/rwi.py,sha256=
|
36
|
+
gigaspatial/handlers/rwi.py,sha256=GDpQH9K96QZD3yezJOBiy5yZvYmrj4xbjUNSjYfNAh0,4875
|
37
37
|
gigaspatial/handlers/unicef_georepo.py,sha256=ODYNvkU_UKgOHXT--0MqmJ4Uk6U1_mp9xgehbTzKpX8,31924
|
38
38
|
gigaspatial/handlers/worldpop.py,sha256=oJ39NGajXi0rn829ZoFiaeG4_wavyPvljdActpxs12I,9850
|
39
39
|
gigaspatial/processing/__init__.py,sha256=QDVL-QbLCrIb19lrajP7LrHNdGdnsLeGcvAs_jQpdRM,183
|
40
40
|
gigaspatial/processing/geo.py,sha256=D-S3IlhQwLIxrCcxy6NhNmKLrOIjoRHfK_eZJGKpe2U,36947
|
41
41
|
gigaspatial/processing/sat_images.py,sha256=YUbH5MFNzl6NX49Obk14WaFcr1s3SyGJIOk-kRpbBNg,1429
|
42
|
-
gigaspatial/processing/tif_processor.py,sha256=
|
42
|
+
gigaspatial/processing/tif_processor.py,sha256=zqcP_ioo9KHNJ6H0uba4UghW4MToTRwq1iE-nZbb8zA,21101
|
43
43
|
gigaspatial/processing/utils.py,sha256=HC85vGKQakxlkoQAkZmeAXWHsenAwTIRn7jPKUA7x20,1500
|
44
|
-
giga_spatial-0.6.
|
45
|
-
giga_spatial-0.6.
|
46
|
-
giga_spatial-0.6.
|
47
|
-
giga_spatial-0.6.
|
44
|
+
giga_spatial-0.6.4.dist-info/METADATA,sha256=WQUWSdjlmfh09kkX20cgudrGHWmldXlNbh4DNjB0Xgo,7467
|
45
|
+
giga_spatial-0.6.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
46
|
+
giga_spatial-0.6.4.dist-info/top_level.txt,sha256=LZsccgw6H4zXT7m6Y4XChm-Y5LjHAwZ2hkGN_B3ExmI,12
|
47
|
+
giga_spatial-0.6.4.dist-info/RECORD,,
|
gigaspatial/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.6.
|
1
|
+
__version__ = "0.6.4"
|
gigaspatial/config.py
CHANGED
@@ -32,6 +32,12 @@ class Config(BaseSettings):
|
|
32
32
|
GIGA_SCHOOL_LOCATION_API_KEY: str = Field(
|
33
33
|
default="", alias="GIGA_SCHOOL_LOCATION_API_KEY"
|
34
34
|
)
|
35
|
+
GIGA_SCHOOL_PROFILE_API_KEY: str = Field(
|
36
|
+
default="", alias="GIGA_SCHOOL_PROFILE_API_KEY"
|
37
|
+
)
|
38
|
+
GIGA_SCHOOL_MEASUREMENTS_API_KEY: str = Field(
|
39
|
+
default="", alias="GIGA_SCHOOL_MEASUREMENTS_API_KEY"
|
40
|
+
)
|
35
41
|
|
36
42
|
ROOT_DATA_DIR: Path = Field(
|
37
43
|
default=Path("."),
|
gigaspatial/handlers/__init__.py
CHANGED
@@ -31,10 +31,14 @@ from gigaspatial.handlers.opencellid import (
|
|
31
31
|
OpenCellIDDownloader,
|
32
32
|
OpenCellIDReader,
|
33
33
|
)
|
34
|
-
from gigaspatial.handlers.hdx import HDXConfig, HDXDownloader, HDXReader
|
35
|
-
from gigaspatial.handlers.rwi import RWIConfig,
|
34
|
+
from gigaspatial.handlers.hdx import HDXConfig, HDXDownloader, HDXReader, HDXHandler
|
35
|
+
from gigaspatial.handlers.rwi import RWIConfig, RWIDownloader, RWIReader, RWIHandler
|
36
36
|
from gigaspatial.handlers.unicef_georepo import (
|
37
37
|
GeoRepoClient,
|
38
38
|
get_country_boundaries_by_iso3,
|
39
39
|
)
|
40
|
-
from gigaspatial.handlers.giga import
|
40
|
+
from gigaspatial.handlers.giga import (
|
41
|
+
GigaSchoolLocationFetcher,
|
42
|
+
GigaSchoolProfileFetcher,
|
43
|
+
GigaSchoolMeasurementsFetcher,
|
44
|
+
)
|
@@ -4,10 +4,12 @@ import geopandas as gpd
|
|
4
4
|
from pathlib import Path
|
5
5
|
from urllib.error import HTTPError
|
6
6
|
from shapely.geometry import Polygon, MultiPolygon, shape
|
7
|
+
import tempfile
|
7
8
|
import pycountry
|
8
9
|
|
9
10
|
from gigaspatial.core.io.data_store import DataStore
|
10
11
|
from gigaspatial.core.io.readers import read_dataset
|
12
|
+
from gigaspatial.handlers.hdx import HDXConfig
|
11
13
|
from gigaspatial.config import config
|
12
14
|
|
13
15
|
|
@@ -61,8 +63,31 @@ class AdminBoundaries(BaseModel):
|
|
61
63
|
"name_en": "name_en",
|
62
64
|
"country_code": "iso_3166_1_alpha_3",
|
63
65
|
},
|
66
|
+
"geoBoundaries": {
|
67
|
+
"id": "shapeID",
|
68
|
+
"name": "shapeName",
|
69
|
+
"country_code": "shapeGroup",
|
70
|
+
},
|
64
71
|
}
|
65
72
|
|
73
|
+
def to_geodataframe(self) -> gpd.GeoDataFrame:
|
74
|
+
"""Convert the AdminBoundaries to a GeoDataFrame."""
|
75
|
+
if not self.boundaries:
|
76
|
+
if hasattr(self, "_empty_schema"):
|
77
|
+
columns = self._empty_schema
|
78
|
+
else:
|
79
|
+
columns = ["id", "name", "country_code", "geometry"]
|
80
|
+
if self.level > 0:
|
81
|
+
columns.append("parent_id")
|
82
|
+
|
83
|
+
return gpd.GeoDataFrame(columns=columns, geometry="geometry", crs=4326)
|
84
|
+
|
85
|
+
return gpd.GeoDataFrame(
|
86
|
+
[boundary.model_dump() for boundary in self.boundaries],
|
87
|
+
geometry="geometry",
|
88
|
+
crs=4326,
|
89
|
+
)
|
90
|
+
|
66
91
|
@classmethod
|
67
92
|
def get_schema_config(cls) -> Dict[str, Dict[str, str]]:
|
68
93
|
"""Return field mappings for different data sources"""
|
@@ -100,6 +125,7 @@ class AdminBoundaries(BaseModel):
|
|
100
125
|
cls.logger.warning(
|
101
126
|
f"Error loading GADM data for {country_code} at admin level {admin_level}: {str(e)}"
|
102
127
|
)
|
128
|
+
cls.logger.info("Falling back to empty instance")
|
103
129
|
return cls._create_empty_instance(country_code, admin_level, "gadm")
|
104
130
|
|
105
131
|
@classmethod
|
@@ -138,6 +164,7 @@ class AdminBoundaries(BaseModel):
|
|
138
164
|
cls.logger.warning(
|
139
165
|
f"No data found at {path} for admin level {admin_level}: {str(e)}"
|
140
166
|
)
|
167
|
+
cls.logger.info("Falling back to empty instance")
|
141
168
|
return cls._create_empty_instance(None, admin_level, "internal")
|
142
169
|
|
143
170
|
@classmethod
|
@@ -202,6 +229,69 @@ class AdminBoundaries(BaseModel):
|
|
202
229
|
|
203
230
|
return cls(boundaries=boundaries, level=admin_level)
|
204
231
|
|
232
|
+
@classmethod
|
233
|
+
def from_geoboundaries(cls, country_code, admin_level: int = 0):
|
234
|
+
cls.logger.info(
|
235
|
+
f"Searching for geoBoundaries data for country: {country_code}, admin level: {admin_level}"
|
236
|
+
)
|
237
|
+
|
238
|
+
country_datasets = HDXConfig.search_datasets(
|
239
|
+
query=f'dataseries_name:"geoBoundaries - Subnational Administrative Boundaries" AND groups:"{country_code.lower()}"',
|
240
|
+
rows=1,
|
241
|
+
)
|
242
|
+
if not country_datasets:
|
243
|
+
cls.logger.error(f"No datasets found for country: {country_code}")
|
244
|
+
raise ValueError(
|
245
|
+
"No resources found for the specified country. Please check your search parameters and try again."
|
246
|
+
)
|
247
|
+
|
248
|
+
cls.logger.info(f"Found dataset: {country_datasets[0].get('title', 'Unknown')}")
|
249
|
+
|
250
|
+
resources = [
|
251
|
+
resource
|
252
|
+
for resource in country_datasets[0].get_resources()
|
253
|
+
if (
|
254
|
+
resource.data["name"]
|
255
|
+
== f"geoBoundaries-{country_code.upper()}-ADM{admin_level}.geojson"
|
256
|
+
)
|
257
|
+
]
|
258
|
+
|
259
|
+
if not resources:
|
260
|
+
cls.logger.error(
|
261
|
+
f"No resources found for {country_code} at admin level {admin_level}"
|
262
|
+
)
|
263
|
+
raise ValueError(
|
264
|
+
"No resources found for the specified criteria. Please check your search parameters and try again."
|
265
|
+
)
|
266
|
+
|
267
|
+
cls.logger.info(f"Found resource: {resources[0].data.get('name', 'Unknown')}")
|
268
|
+
|
269
|
+
try:
|
270
|
+
cls.logger.info("Downloading and processing boundary data...")
|
271
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
272
|
+
url, local_path = resources[0].download(folder=tmpdir)
|
273
|
+
cls.logger.debug(f"Downloaded file to temporary path: {local_path}")
|
274
|
+
with open(local_path, "rb") as f:
|
275
|
+
gdf = gpd.read_file(f)
|
276
|
+
|
277
|
+
gdf = cls._map_fields(gdf, "geoBoundaries", admin_level)
|
278
|
+
boundaries = [
|
279
|
+
AdminBoundary(**row_dict) for row_dict in gdf.to_dict("records")
|
280
|
+
]
|
281
|
+
cls.logger.info(
|
282
|
+
f"Successfully created {len(boundaries)} AdminBoundary objects"
|
283
|
+
)
|
284
|
+
return cls(boundaries=boundaries, level=admin_level)
|
285
|
+
|
286
|
+
except (ValueError, HTTPError, FileNotFoundError) as e:
|
287
|
+
cls.logger.warning(
|
288
|
+
f"Error loading geoBoundaries data for {country_code} at admin level {admin_level}: {str(e)}"
|
289
|
+
)
|
290
|
+
cls.logger.info("Falling back to empty instance")
|
291
|
+
return cls._create_empty_instance(
|
292
|
+
country_code, admin_level, "geoBoundaries"
|
293
|
+
)
|
294
|
+
|
205
295
|
@classmethod
|
206
296
|
def create(
|
207
297
|
cls,
|
@@ -211,45 +301,126 @@ class AdminBoundaries(BaseModel):
|
|
211
301
|
path: Optional[Union[str, "Path"]] = None,
|
212
302
|
**kwargs,
|
213
303
|
) -> "AdminBoundaries":
|
214
|
-
"""Factory method to create AdminBoundaries instance from either GADM or data store.
|
304
|
+
"""Factory method to create AdminBoundaries instance from either GADM or data store.
|
305
|
+
|
306
|
+
Args:
|
307
|
+
country_code: ISO country code (2 or 3 letter) or country name
|
308
|
+
admin_level: Administrative level (0=country, 1=state/province, etc.)
|
309
|
+
data_store: Optional data store instance for loading from existing data
|
310
|
+
path: Optional path to data file (used with data_store)
|
311
|
+
**kwargs: Additional arguments passed to the underlying creation methods
|
312
|
+
|
313
|
+
Returns:
|
314
|
+
AdminBoundaries: Configured instance
|
315
|
+
|
316
|
+
Raises:
|
317
|
+
ValueError: If neither country_code nor (data_store, path) are provided,
|
318
|
+
or if country_code lookup fails
|
319
|
+
|
320
|
+
Example:
|
321
|
+
# From country code
|
322
|
+
boundaries = AdminBoundaries.create(country_code="USA", admin_level=1)
|
323
|
+
|
324
|
+
# From data store
|
325
|
+
boundaries = AdminBoundaries.create(data_store=store, path="data.shp")
|
326
|
+
"""
|
215
327
|
cls.logger.info(
|
216
|
-
f"Creating AdminBoundaries instance. Country: {country_code},
|
328
|
+
f"Creating AdminBoundaries instance. Country: {country_code}, "
|
329
|
+
f"admin level: {admin_level}, data_store provided: {data_store is not None}, "
|
330
|
+
f"path provided: {path is not None}"
|
217
331
|
)
|
218
|
-
|
332
|
+
|
333
|
+
# Validate input parameters
|
334
|
+
if not country_code and not data_store:
|
335
|
+
raise ValueError("Either country_code or data_store must be provided.")
|
336
|
+
|
337
|
+
if data_store and not path and not country_code:
|
338
|
+
raise ValueError(
|
339
|
+
"If data_store is provided, either path or country_code must also be specified."
|
340
|
+
)
|
341
|
+
|
342
|
+
# Handle data store path first
|
219
343
|
if data_store is not None:
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
344
|
+
iso3_code = None
|
345
|
+
if country_code:
|
346
|
+
try:
|
347
|
+
iso3_code = pycountry.countries.lookup(country_code).alpha_3
|
348
|
+
except LookupError as e:
|
349
|
+
raise ValueError(f"Invalid country code '{country_code}': {e}")
|
350
|
+
|
351
|
+
# Generate path if not provided
|
352
|
+
if path is None and iso3_code:
|
225
353
|
path = config.get_admin_path(
|
226
354
|
country_code=iso3_code,
|
227
355
|
admin_level=admin_level,
|
228
356
|
)
|
357
|
+
|
229
358
|
return cls.from_data_store(data_store, path, admin_level, **kwargs)
|
230
|
-
elif country_code is not None:
|
231
|
-
from gigaspatial.handlers.unicef_georepo import GeoRepoClient
|
232
359
|
|
360
|
+
# Handle country code path
|
361
|
+
if country_code is not None:
|
233
362
|
try:
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
363
|
+
iso3_code = pycountry.countries.lookup(country_code).alpha_3
|
364
|
+
except LookupError as e:
|
365
|
+
raise ValueError(f"Invalid country code '{country_code}': {e}")
|
366
|
+
|
367
|
+
# Try GeoRepo first
|
368
|
+
if cls._try_georepo(iso3_code, admin_level):
|
369
|
+
return cls.from_georepo(iso3_code, admin_level=admin_level)
|
370
|
+
|
371
|
+
# Fallback to GADM
|
372
|
+
try:
|
373
|
+
cls.logger.info("Attempting to load from GADM.")
|
374
|
+
return cls.from_gadm(iso3_code, admin_level, **kwargs)
|
375
|
+
except Exception as e:
|
242
376
|
cls.logger.warning(
|
243
|
-
f"
|
377
|
+
f"GADM loading failed: {e}. Falling back to geoBoundaries."
|
244
378
|
)
|
379
|
+
|
380
|
+
# Final fallback to geoBoundaries
|
381
|
+
try:
|
382
|
+
return cls.from_geoboundaries(iso3_code, admin_level)
|
245
383
|
except Exception as e:
|
246
|
-
cls.logger.
|
384
|
+
cls.logger.error(f"All data sources failed. geoBoundaries error: {e}")
|
385
|
+
raise RuntimeError(
|
386
|
+
f"Failed to load administrative boundaries for {country_code} "
|
387
|
+
f"from all available sources (GeoRepo, GADM, geoBoundaries)."
|
388
|
+
) from e
|
247
389
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
390
|
+
# This should never be reached due to validation above
|
391
|
+
raise ValueError("Unexpected error: no valid data source could be determined.")
|
392
|
+
|
393
|
+
@classmethod
|
394
|
+
def _try_georepo(cls, iso3_code: str, admin_level: int) -> bool:
|
395
|
+
"""Helper method to test GeoRepo availability.
|
396
|
+
|
397
|
+
Args:
|
398
|
+
iso3_code: ISO3 country code
|
399
|
+
admin_level: Administrative level
|
400
|
+
|
401
|
+
Returns:
|
402
|
+
bool: True if GeoRepo is available and working, False otherwise
|
403
|
+
"""
|
404
|
+
try:
|
405
|
+
from gigaspatial.handlers.unicef_georepo import GeoRepoClient
|
406
|
+
|
407
|
+
client = GeoRepoClient()
|
408
|
+
if client.check_connection():
|
409
|
+
cls.logger.info("GeoRepo connection successful.")
|
410
|
+
return True
|
411
|
+
else:
|
412
|
+
cls.logger.info("GeoRepo connection failed.")
|
413
|
+
return False
|
414
|
+
|
415
|
+
except ImportError:
|
416
|
+
cls.logger.info("GeoRepo client not available (import failed).")
|
417
|
+
return False
|
418
|
+
except ValueError as e:
|
419
|
+
cls.logger.warning(f"GeoRepo initialization failed: {e}")
|
420
|
+
return False
|
421
|
+
except Exception as e:
|
422
|
+
cls.logger.warning(f"GeoRepo error: {e}")
|
423
|
+
return False
|
253
424
|
|
254
425
|
@classmethod
|
255
426
|
def _create_empty_instance(
|
@@ -288,21 +459,3 @@ class AdminBoundaries(BaseModel):
|
|
288
459
|
field_mapping[v] = k
|
289
460
|
|
290
461
|
return gdf.rename(columns=field_mapping)
|
291
|
-
|
292
|
-
def to_geodataframe(self) -> gpd.GeoDataFrame:
|
293
|
-
"""Convert the AdminBoundaries to a GeoDataFrame."""
|
294
|
-
if not self.boundaries:
|
295
|
-
if hasattr(self, "_empty_schema"):
|
296
|
-
columns = self._empty_schema
|
297
|
-
else:
|
298
|
-
columns = ["id", "name", "country_code", "geometry"]
|
299
|
-
if self.level > 0:
|
300
|
-
columns.append("parent_id")
|
301
|
-
|
302
|
-
return gpd.GeoDataFrame(columns=columns, geometry="geometry", crs=4326)
|
303
|
-
|
304
|
-
return gpd.GeoDataFrame(
|
305
|
-
[boundary.model_dump() for boundary in self.boundaries],
|
306
|
-
geometry="geometry",
|
307
|
-
crs=4326,
|
308
|
-
)
|
gigaspatial/handlers/ghsl.py
CHANGED
@@ -74,8 +74,6 @@ class GHSLDataConfig(BaseHandlerConfig):
|
|
74
74
|
|
75
75
|
def __post_init__(self):
|
76
76
|
super().__post_init__()
|
77
|
-
self.TILES_URL = self.TILES_URL.format(self.coord_system.value)
|
78
|
-
self._load_tiles()
|
79
77
|
|
80
78
|
def _load_tiles(self):
|
81
79
|
"""Load GHSL tiles from tiles shapefile."""
|
@@ -158,6 +156,9 @@ class GHSLDataConfig(BaseHandlerConfig):
|
|
158
156
|
)
|
159
157
|
self.coord_system = CoordSystem.Mollweide
|
160
158
|
|
159
|
+
self.TILES_URL = self.TILES_URL.format(self.coord_system.value)
|
160
|
+
self._load_tiles()
|
161
|
+
|
161
162
|
return self
|
162
163
|
|
163
164
|
@property
|
@@ -176,7 +177,7 @@ class GHSLDataConfig(BaseHandlerConfig):
|
|
176
177
|
self, points: Iterable[Union[Point, tuple]], **kwargs
|
177
178
|
) -> List[dict]:
|
178
179
|
"""
|
179
|
-
Return intersecting tiles
|
180
|
+
Return intersecting tiles f or a list of points.
|
180
181
|
"""
|
181
182
|
return self._get_relevant_tiles(points)
|
182
183
|
|
@@ -240,8 +241,8 @@ class GHSLDataConfig(BaseHandlerConfig):
|
|
240
241
|
ValueError: If the input `source` is not one of the supported types.
|
241
242
|
"""
|
242
243
|
if isinstance(source, gpd.GeoDataFrame):
|
243
|
-
if source.crs != "EPSG:4326":
|
244
|
-
|
244
|
+
# if source.crs != "EPSG:4326":
|
245
|
+
# source = source.to_crs("EPSG:4326")
|
245
246
|
search_geom = source.geometry.unary_union
|
246
247
|
elif isinstance(
|
247
248
|
source,
|
@@ -282,7 +283,7 @@ class GHSLDataConfig(BaseHandlerConfig):
|
|
282
283
|
else ("3ss" if self.resolution == 100 else "30ss")
|
283
284
|
)
|
284
285
|
product_folder = f"{self.product}_GLOBE_{self.release}"
|
285
|
-
product_name = f"{self.product}_E{self.year}_GLOBE_{self.release}_{self.coord_system}_{resolution_str}"
|
286
|
+
product_name = f"{self.product}_E{self.year}_GLOBE_{self.release}_{self.coord_system.value}_{resolution_str}"
|
286
287
|
product_version = 2 if self.product == "GHS_SMOD" else 1
|
287
288
|
|
288
289
|
return {
|