giga-spatial 0.6.4__py3-none-any.whl → 0.6.6__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.4.dist-info → giga_spatial-0.6.6.dist-info}/METADATA +3 -1
- giga_spatial-0.6.6.dist-info/RECORD +50 -0
- gigaspatial/__init__.py +1 -1
- gigaspatial/config.py +29 -4
- gigaspatial/core/io/__init__.py +1 -0
- gigaspatial/core/io/data_api.py +3 -1
- gigaspatial/core/io/database.py +319 -0
- gigaspatial/generators/__init__.py +5 -1
- gigaspatial/generators/poi.py +300 -52
- gigaspatial/generators/zonal/__init__.py +2 -1
- gigaspatial/generators/zonal/admin.py +84 -0
- gigaspatial/generators/zonal/base.py +237 -81
- gigaspatial/generators/zonal/geometry.py +151 -53
- gigaspatial/generators/zonal/mercator.py +50 -19
- gigaspatial/grid/__init__.py +1 -1
- gigaspatial/grid/mercator_tiles.py +33 -10
- gigaspatial/handlers/__init__.py +8 -1
- gigaspatial/handlers/base.py +26 -6
- gigaspatial/handlers/boundaries.py +93 -18
- gigaspatial/handlers/ghsl.py +92 -15
- gigaspatial/handlers/rwi.py +5 -2
- gigaspatial/handlers/worldpop.py +771 -186
- gigaspatial/processing/algorithms.py +188 -0
- gigaspatial/processing/geo.py +204 -102
- gigaspatial/processing/tif_processor.py +220 -45
- giga_spatial-0.6.4.dist-info/RECORD +0 -47
- {giga_spatial-0.6.4.dist-info → giga_spatial-0.6.6.dist-info}/WHEEL +0 -0
- {giga_spatial-0.6.4.dist-info → giga_spatial-0.6.6.dist-info}/licenses/LICENSE +0 -0
- {giga_spatial-0.6.4.dist-info → giga_spatial-0.6.6.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Dict, List, Optional, Union
|
1
|
+
from typing import Dict, List, Optional, Union, Literal
|
2
2
|
from shapely.geometry import Polygon, MultiPolygon
|
3
3
|
|
4
4
|
import geopandas as gpd
|
@@ -6,7 +6,6 @@ import pandas as pd
|
|
6
6
|
import logging
|
7
7
|
|
8
8
|
from gigaspatial.core.io.data_store import DataStore
|
9
|
-
from gigaspatial.config import config as global_config
|
10
9
|
from gigaspatial.processing.geo import (
|
11
10
|
add_area_in_meters,
|
12
11
|
get_centroids,
|
@@ -14,6 +13,7 @@ from gigaspatial.processing.geo import (
|
|
14
13
|
from gigaspatial.handlers.ghsl import GHSLDataHandler
|
15
14
|
from gigaspatial.handlers.google_open_buildings import GoogleOpenBuildingsHandler
|
16
15
|
from gigaspatial.handlers.microsoft_global_buildings import MSBuildingsHandler
|
16
|
+
from gigaspatial.handlers.worldpop import WPPopulationHandler
|
17
17
|
from gigaspatial.generators.zonal.base import (
|
18
18
|
ZonalViewGenerator,
|
19
19
|
ZonalViewGeneratorConfig,
|
@@ -136,9 +136,9 @@ class GeometryBasedZonalViewGenerator(ZonalViewGenerator[T]):
|
|
136
136
|
List[T]: A list of zone identifiers in the order they appear in the
|
137
137
|
underlying GeoDataFrame.
|
138
138
|
"""
|
139
|
-
return self._zone_gdf
|
139
|
+
return self._zone_gdf.zone_id.tolist()
|
140
140
|
|
141
|
-
def
|
141
|
+
def get_zone_geodataframe(self) -> gpd.GeoDataFrame:
|
142
142
|
"""Convert zones to a GeoDataFrame with standardized column names.
|
143
143
|
|
144
144
|
Returns:
|
@@ -156,24 +156,23 @@ class GeometryBasedZonalViewGenerator(ZonalViewGenerator[T]):
|
|
156
156
|
year=2020,
|
157
157
|
resolution=100,
|
158
158
|
stat: str = "sum",
|
159
|
-
|
159
|
+
output_column: str = "built_surface_m2",
|
160
160
|
**kwargs,
|
161
|
-
) ->
|
161
|
+
) -> pd.DataFrame:
|
162
162
|
"""Map GHSL Built-up Surface data to zones.
|
163
163
|
|
164
164
|
Convenience method for mapping Global Human Settlement Layer Built-up Surface
|
165
165
|
data using appropriate default parameters for built surface analysis.
|
166
166
|
|
167
167
|
Args:
|
168
|
-
|
169
|
-
|
168
|
+
year: The year of the data (default: 2020)
|
169
|
+
resolution: The resolution in meters (default: 100)
|
170
170
|
stat (str): Statistic to calculate for built surface values within each zone.
|
171
171
|
Defaults to "sum" which gives total built surface area.
|
172
|
-
|
173
|
-
|
172
|
+
output_column (str): The output column name. Defaults to "built_surface_m2".
|
174
173
|
Returns:
|
175
|
-
|
176
|
-
Adds a column
|
174
|
+
pd.DataFrame: Updated view DataFrame and settlement classification.
|
175
|
+
Adds a column with `output_column` containing the aggregated values.
|
177
176
|
"""
|
178
177
|
handler = GHSLDataHandler(
|
179
178
|
product="GHS_BUILT_S",
|
@@ -184,32 +183,31 @@ class GeometryBasedZonalViewGenerator(ZonalViewGenerator[T]):
|
|
184
183
|
)
|
185
184
|
|
186
185
|
return self.map_ghsl(
|
187
|
-
handler=handler, stat=stat,
|
186
|
+
handler=handler, stat=stat, output_column=output_column, **kwargs
|
188
187
|
)
|
189
188
|
|
190
189
|
def map_smod(
|
191
190
|
self,
|
192
191
|
year=2020,
|
193
|
-
resolution=
|
192
|
+
resolution=1000,
|
194
193
|
stat: str = "median",
|
195
|
-
|
194
|
+
output_column: str = "smod_class",
|
196
195
|
**kwargs,
|
197
|
-
) ->
|
196
|
+
) -> pd.DataFrame:
|
198
197
|
"""Map GHSL Settlement Model data to zones.
|
199
198
|
|
200
199
|
Convenience method for mapping Global Human Settlement Layer Settlement Model
|
201
200
|
data using appropriate default parameters for settlement classification analysis.
|
202
201
|
|
203
202
|
Args:
|
204
|
-
|
205
|
-
|
203
|
+
year: The year of the data (default: 2020)
|
204
|
+
resolution: The resolution in meters (default: 1000)
|
206
205
|
stat (str): Statistic to calculate for settlement class values within each zone.
|
207
206
|
Defaults to "median" which gives the predominant settlement class.
|
208
|
-
|
209
|
-
|
207
|
+
output_column (str): The output column name. Defaults to "smod_class".
|
210
208
|
Returns:
|
211
|
-
|
212
|
-
Adds a column
|
209
|
+
pd.DataFrame: Updated view DataFrame and settlement classification.
|
210
|
+
Adds a column with `output_column` containing the aggregated values.
|
213
211
|
"""
|
214
212
|
handler = GHSLDataHandler(
|
215
213
|
product="GHS_SMOD",
|
@@ -221,32 +219,31 @@ class GeometryBasedZonalViewGenerator(ZonalViewGenerator[T]):
|
|
221
219
|
)
|
222
220
|
|
223
221
|
return self.map_ghsl(
|
224
|
-
handler=handler, stat=stat,
|
222
|
+
handler=handler, stat=stat, output_column=output_column, **kwargs
|
225
223
|
)
|
226
224
|
|
227
225
|
def map_ghsl(
|
228
226
|
self,
|
229
227
|
handler: GHSLDataHandler,
|
230
228
|
stat: str,
|
231
|
-
|
229
|
+
output_column: Optional[str] = None,
|
232
230
|
**kwargs,
|
233
|
-
) ->
|
231
|
+
) -> pd.DataFrame:
|
234
232
|
"""Map Global Human Settlement Layer data to zones.
|
235
233
|
|
236
234
|
Loads and processes GHSL raster data for the intersecting tiles, then samples
|
237
235
|
the raster values within each zone using the specified statistic.
|
238
236
|
|
239
237
|
Args:
|
240
|
-
|
241
|
-
product, year, resolution, and coordinate system to use.
|
238
|
+
hander (GHSLDataHandler): Handler for the GHSL data.
|
242
239
|
stat (str): Statistic to calculate for raster values within each zone.
|
243
240
|
Common options: "mean", "sum", "median", "min", "max".
|
244
|
-
|
241
|
+
output_column (str): The output column name.
|
245
242
|
If None, uses the GHSL product name in lowercase followed by underscore.
|
246
243
|
|
247
244
|
Returns:
|
248
|
-
|
249
|
-
Adds a column named
|
245
|
+
pd.DataFrame: Updated DataFrame with GHSL metrics.
|
246
|
+
Adds a column named as `output_column` containing the sampled values.
|
250
247
|
|
251
248
|
Note:
|
252
249
|
The method automatically determines which GHSL tiles intersect with the zones
|
@@ -265,21 +262,21 @@ class GeometryBasedZonalViewGenerator(ZonalViewGenerator[T]):
|
|
265
262
|
)
|
266
263
|
sampled_values = self.map_rasters(tif_processors=tif_processors, stat=stat)
|
267
264
|
|
268
|
-
|
269
|
-
|
265
|
+
column_name = (
|
266
|
+
output_column
|
267
|
+
if output_column
|
268
|
+
else f"{handler.config.product.lower()}_{stat}"
|
270
269
|
)
|
271
|
-
column_name = f"{name_prefix}{stat}"
|
272
|
-
self._zone_gdf[column_name] = sampled_values
|
273
270
|
|
274
|
-
self.
|
271
|
+
self.add_variable_to_view(sampled_values, column_name)
|
275
272
|
|
276
|
-
return self.
|
273
|
+
return self.view
|
277
274
|
|
278
275
|
def map_google_buildings(
|
279
276
|
self,
|
280
277
|
handler: Optional[GoogleOpenBuildingsHandler] = None,
|
281
278
|
use_polygons: bool = False,
|
282
|
-
) ->
|
279
|
+
) -> pd.DataFrame:
|
283
280
|
"""Map Google Open Buildings data to zones.
|
284
281
|
|
285
282
|
Processes Google Open Buildings dataset to calculate building counts and total
|
@@ -295,7 +292,7 @@ class GeometryBasedZonalViewGenerator(ZonalViewGenerator[T]):
|
|
295
292
|
area values from attributes for faster processing. Defaults to False.
|
296
293
|
|
297
294
|
Returns:
|
298
|
-
|
295
|
+
pd.DataFrame: Updated DataFrame with building metrics.
|
299
296
|
Adds columns:
|
300
297
|
- 'google_buildings_count': Number of buildings in each zone
|
301
298
|
- 'google_buildings_area_in_meters': Total building area in square meters
|
@@ -341,19 +338,20 @@ class GeometryBasedZonalViewGenerator(ZonalViewGenerator[T]):
|
|
341
338
|
self.logger.info(
|
342
339
|
"Calculating building areas with area-weighted aggregation"
|
343
340
|
)
|
344
|
-
area_result = self.map_polygons(
|
341
|
+
area_result = self.map_polygons(
|
342
|
+
buildings_gdf,
|
343
|
+
value_columns="area_in_meters",
|
344
|
+
aggregation="sum",
|
345
|
+
predicate="fractional",
|
346
|
+
)
|
345
347
|
|
346
348
|
self.logger.info("Counting buildings using points data")
|
347
349
|
count_result = self.map_points(points=buildings_df, predicate="within")
|
348
350
|
|
349
|
-
self.
|
350
|
-
self.
|
351
|
-
area_result
|
352
|
-
)
|
353
|
-
|
354
|
-
self.logger.info(f"Added Google building data")
|
351
|
+
self.add_variable_to_view(count_result, "google_buildings_count")
|
352
|
+
self.add_variable_to_view(area_result, "google_buildings_area_in_meters")
|
355
353
|
|
356
|
-
return self.
|
354
|
+
return self.view
|
357
355
|
|
358
356
|
def map_ms_buildings(
|
359
357
|
self,
|
@@ -400,7 +398,9 @@ class GeometryBasedZonalViewGenerator(ZonalViewGenerator[T]):
|
|
400
398
|
)
|
401
399
|
return self._zone_gdf.copy()
|
402
400
|
|
403
|
-
buildings_gdf = add_area_in_meters(
|
401
|
+
buildings_gdf = add_area_in_meters(
|
402
|
+
buildings_gdf, area_column_name="area_in_meters"
|
403
|
+
)
|
404
404
|
|
405
405
|
building_centroids = get_centroids(buildings_gdf)
|
406
406
|
|
@@ -421,7 +421,12 @@ class GeometryBasedZonalViewGenerator(ZonalViewGenerator[T]):
|
|
421
421
|
self.logger.info(
|
422
422
|
"Calculating building areas with area-weighted aggregation"
|
423
423
|
)
|
424
|
-
area_result = self.map_polygons(
|
424
|
+
area_result = self.map_polygons(
|
425
|
+
buildings_gdf,
|
426
|
+
value_columns="area_in_meters",
|
427
|
+
aggregation="sum",
|
428
|
+
predicate="fractional",
|
429
|
+
)
|
425
430
|
|
426
431
|
self.logger.info("Counting Microsoft buildings per zone")
|
427
432
|
|
@@ -429,11 +434,104 @@ class GeometryBasedZonalViewGenerator(ZonalViewGenerator[T]):
|
|
429
434
|
points=building_centroids, predicate="within"
|
430
435
|
)
|
431
436
|
|
432
|
-
self.
|
433
|
-
self.
|
434
|
-
|
437
|
+
self.add_variable_to_view(count_result, "ms_buildings_count")
|
438
|
+
self.add_variable_to_view(area_result, "ms_buildings_area_in_meters")
|
439
|
+
|
440
|
+
return self.view
|
441
|
+
|
442
|
+
def map_ghsl_pop(
|
443
|
+
self,
|
444
|
+
resolution=100,
|
445
|
+
stat: str = "sum",
|
446
|
+
output_column: str = "ghsl_pop",
|
447
|
+
predicate: Literal["intersects", "fractional"] = "intersects",
|
448
|
+
**kwargs,
|
449
|
+
):
|
450
|
+
handler = GHSLDataHandler(
|
451
|
+
product="GHS_POP",
|
452
|
+
resolution=resolution,
|
453
|
+
data_store=self.data_store,
|
454
|
+
**kwargs,
|
435
455
|
)
|
436
456
|
|
437
|
-
|
457
|
+
if predicate == "fractional":
|
458
|
+
if resolution == 100:
|
459
|
+
self.logger.warning(
|
460
|
+
"Fractional aggregations only supported for datasets with 1000m resolution. Using `intersects` as predicate"
|
461
|
+
)
|
462
|
+
predicate = "intersects"
|
463
|
+
else:
|
464
|
+
gdf_pop = handler.load_into_geodataframe(self.zone_gdf)
|
465
|
+
|
466
|
+
result = self.map_polygons(
|
467
|
+
gdf_pop,
|
468
|
+
value_columns="pixel_value",
|
469
|
+
aggregation="sum",
|
470
|
+
predicate="fractional",
|
471
|
+
)
|
472
|
+
|
473
|
+
self.add_variable_to_view(result, output_column)
|
474
|
+
return self.view
|
475
|
+
|
476
|
+
return self.map_ghsl(
|
477
|
+
handler=handler, stat=stat, output_column=output_column, **kwargs
|
478
|
+
)
|
479
|
+
|
480
|
+
def map_wp_pop(
|
481
|
+
self,
|
482
|
+
country: Union[str, List[str]],
|
483
|
+
resolution=1000,
|
484
|
+
predicate: Literal["intersects", "fractional"] = "intersects",
|
485
|
+
output_column: str = "population",
|
486
|
+
**kwargs,
|
487
|
+
):
|
488
|
+
if isinstance(country, str):
|
489
|
+
country = [country]
|
490
|
+
|
491
|
+
handler = WPPopulationHandler(
|
492
|
+
project="pop", resolution=resolution, data_store=self.data_store, **kwargs
|
493
|
+
)
|
494
|
+
|
495
|
+
self.logger.info(
|
496
|
+
f"Mapping WorldPop Population data (year: {handler.config.year}, resolution: {handler.config.resolution}m)"
|
497
|
+
)
|
498
|
+
|
499
|
+
if predicate == "fractional":
|
500
|
+
if resolution == 100:
|
501
|
+
self.logger.warning(
|
502
|
+
"Fractional aggregations only supported for datasets with 1000m resolution. Using `intersects` as predicate"
|
503
|
+
)
|
504
|
+
predicate = "intersects"
|
505
|
+
else:
|
506
|
+
gdf_pop = pd.concat(
|
507
|
+
[
|
508
|
+
handler.load_into_geodataframe(
|
509
|
+
c, ensure_available=self.config.ensure_available
|
510
|
+
)
|
511
|
+
for c in country
|
512
|
+
],
|
513
|
+
ignore_index=True,
|
514
|
+
)
|
515
|
+
|
516
|
+
result = self.map_polygons(
|
517
|
+
gdf_pop,
|
518
|
+
value_columns="pixel_value",
|
519
|
+
aggregation="sum",
|
520
|
+
predicate=predicate,
|
521
|
+
)
|
522
|
+
|
523
|
+
self.add_variable_to_view(result, output_column)
|
524
|
+
return self.view
|
525
|
+
|
526
|
+
tif_processors = []
|
527
|
+
for c in country:
|
528
|
+
tif_processors.extend(
|
529
|
+
handler.load_data(c, ensure_available=self.config.ensure_available)
|
530
|
+
)
|
531
|
+
|
532
|
+
self.logger.info(f"Sampling WorldPop Population data using 'sum' statistic")
|
533
|
+
sampled_values = self.map_rasters(tif_processors=tif_processors, stat="sum")
|
534
|
+
|
535
|
+
self.add_variable_to_view(sampled_values, output_column)
|
438
536
|
|
439
|
-
return self.
|
537
|
+
return self.view
|
@@ -15,25 +15,48 @@ from gigaspatial.generators.zonal.geometry import GeometryBasedZonalViewGenerato
|
|
15
15
|
|
16
16
|
|
17
17
|
class MercatorViewGenerator(GeometryBasedZonalViewGenerator[T]):
|
18
|
-
"""
|
18
|
+
"""
|
19
|
+
Generates zonal views using Mercator tiles as the zones.
|
19
20
|
|
20
|
-
This class
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
This class specializes in creating zonal views where the zones are defined by
|
22
|
+
Mercator tiles. It extends the `GeometryBasedZonalViewGenerator` and leverages
|
23
|
+
the `MercatorTiles` and `CountryMercatorTiles` classes to generate tiles based on
|
24
|
+
various input sources.
|
24
25
|
|
25
|
-
The
|
26
|
-
|
27
|
-
|
26
|
+
The primary input source defines the geographical area of interest. This can be
|
27
|
+
a country, a specific geometry, a set of points, or even a list of predefined
|
28
|
+
quadkeys. The `zoom_level` determines the granularity of the Mercator tiles.
|
28
29
|
|
29
30
|
Attributes:
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
31
|
+
source (Union[str, BaseGeometry, gpd.GeoDataFrame, List[Union[Point, Tuple[float, float]]], List[str]]):
|
32
|
+
Specifies the geographic area or specific tiles to use. Can be:
|
33
|
+
- A country name (str): Uses `CountryMercatorTiles` to generate tiles covering the country.
|
34
|
+
- A Shapely geometry (BaseGeometry): Uses `MercatorTiles.from_spatial` to create tiles intersecting the geometry.
|
35
|
+
- A GeoDataFrame (gpd.GeoDataFrame): Uses `MercatorTiles.from_spatial` to create tiles intersecting the geometries.
|
36
|
+
- A list of points (List[Union[Point, Tuple[float, float]]]): Uses `MercatorTiles.from_spatial` to create tiles containing the points.
|
37
|
+
- A list of quadkeys (List[str]): Uses `MercatorTiles.from_quadkeys` to use the specified tiles directly.
|
38
|
+
zoom_level (int): The zoom level of the Mercator tiles. Higher zoom levels result in smaller, more detailed tiles.
|
39
|
+
predicate (str): The spatial predicate used when filtering tiles based on a spatial source (e.g., "intersects", "contains"). Defaults to "intersects".
|
40
|
+
config (Optional[ZonalViewGeneratorConfig]): Configuration for the zonal view generation process.
|
41
|
+
data_store (Optional[DataStore]): A DataStore instance for accessing data.
|
42
|
+
logger (Optional[logging.Logger]): A logger instance for logging.
|
43
|
+
|
44
|
+
Methods:
|
45
|
+
_init_zone_data(source, zoom_level, predicate): Initializes the Mercator tile GeoDataFrame based on the input source.
|
46
|
+
# Inherits other methods from GeometryBasedZonalViewGenerator, such as:
|
47
|
+
# map_ghsl(), map_google_buildings(), map_ms_buildings(), aggregate_data(), save_view()
|
48
|
+
|
49
|
+
Example:
|
50
|
+
# Create a MercatorViewGenerator for tiles covering Germany at zoom level 6
|
51
|
+
generator = MercatorViewGenerator(source="Germany", zoom_level=6)
|
52
|
+
|
53
|
+
# Create a MercatorViewGenerator for tiles intersecting a specific polygon
|
54
|
+
polygon = ... # Define a Shapely Polygon
|
55
|
+
generator = MercatorViewGenerator(source=polygon, zoom_level=8)
|
56
|
+
|
57
|
+
# Create a MercatorViewGenerator from a list of quadkeys
|
58
|
+
quadkeys = ["0020023131023032", "0020023131023033"]
|
59
|
+
generator = MercatorViewGenerator(source=quadkeys, zoom_level=12)
|
37
60
|
"""
|
38
61
|
|
39
62
|
def __init__(
|
@@ -53,16 +76,19 @@ class MercatorViewGenerator(GeometryBasedZonalViewGenerator[T]):
|
|
53
76
|
):
|
54
77
|
|
55
78
|
super().__init__(
|
56
|
-
zone_data=self._init_zone_data(source, zoom_level, predicate),
|
79
|
+
zone_data=self._init_zone_data(source, zoom_level, predicate, data_store),
|
57
80
|
zone_id_column="quadkey",
|
58
81
|
config=config,
|
59
82
|
data_store=data_store,
|
60
83
|
logger=logger,
|
61
84
|
)
|
85
|
+
self.logger.info(f"Initialized MercatorViewGenerator")
|
62
86
|
|
63
|
-
def _init_zone_data(self, source, zoom_level, predicate):
|
87
|
+
def _init_zone_data(self, source, zoom_level, predicate, data_store=None):
|
64
88
|
if isinstance(source, str):
|
65
|
-
tiles = CountryMercatorTiles.create(
|
89
|
+
tiles = CountryMercatorTiles.create(
|
90
|
+
country=source, zoom_level=zoom_level, data_store=data_store
|
91
|
+
)
|
66
92
|
elif isinstance(source, (BaseGeometry, Iterable)):
|
67
93
|
if isinstance(source, Iterable) and all(
|
68
94
|
isinstance(qk, str) for qk in source
|
@@ -73,6 +99,11 @@ class MercatorViewGenerator(GeometryBasedZonalViewGenerator[T]):
|
|
73
99
|
source=source, zoom_level=zoom_level, predicate=predicate
|
74
100
|
)
|
75
101
|
else:
|
76
|
-
raise
|
102
|
+
raise TypeError(
|
103
|
+
f"Unsupported source type for MercatorViewGenerator. 'source' must be "
|
104
|
+
f"a country name (str), a Shapely geometry, a GeoDataFrame, "
|
105
|
+
f"a list of quadkeys (str), or a list of (lon, lat) tuples/Shapely Point objects. "
|
106
|
+
f"Received type: {type(source)}."
|
107
|
+
)
|
77
108
|
|
78
109
|
return tiles.to_geodataframe()
|
gigaspatial/grid/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
from gigaspatial.grid.mercator_tiles import
|
1
|
+
from gigaspatial.grid.mercator_tiles import MercatorTiles, CountryMercatorTiles
|
@@ -4,10 +4,10 @@ import mercantile
|
|
4
4
|
from shapely.geometry import box
|
5
5
|
from shapely.geometry.base import BaseGeometry
|
6
6
|
from shapely.strtree import STRtree
|
7
|
-
from shapely import
|
7
|
+
from shapely import Point
|
8
8
|
import json
|
9
9
|
from pathlib import Path
|
10
|
-
from pydantic import BaseModel, Field
|
10
|
+
from pydantic import BaseModel, Field
|
11
11
|
from typing import List, Union, Iterable, Optional, Tuple, ClassVar
|
12
12
|
import pycountry
|
13
13
|
|
@@ -31,6 +31,9 @@ class MercatorTiles(BaseModel):
|
|
31
31
|
if not quadkeys:
|
32
32
|
cls.logger.warning("No quadkeys provided to from_quadkeys.")
|
33
33
|
return cls(zoom_level=0, quadkeys=[])
|
34
|
+
cls.logger.info(
|
35
|
+
f"Initializing MercatorTiles from {len(quadkeys)} provided quadkeys."
|
36
|
+
)
|
34
37
|
return cls(zoom_level=len(quadkeys[0]), quadkeys=set(quadkeys))
|
35
38
|
|
36
39
|
@classmethod
|
@@ -120,14 +123,7 @@ class MercatorTiles(BaseModel):
|
|
120
123
|
cls.logger.info(
|
121
124
|
f"Creating MercatorTiles from {len(points)} points at zoom level: {zoom_level}"
|
122
125
|
)
|
123
|
-
quadkeys =
|
124
|
-
(
|
125
|
-
mercantile.quadkey(mercantile.tile(p.x, p.y, zoom_level))
|
126
|
-
if isinstance(p, Point)
|
127
|
-
else mercantile.quadkey(mercantile.tile(p[1], p[0], zoom_level))
|
128
|
-
)
|
129
|
-
for p in points
|
130
|
-
}
|
126
|
+
quadkeys = set(cls.get_quadkeys_from_points(points, zoom_level))
|
131
127
|
cls.logger.info(f"Generated {len(quadkeys)} unique quadkeys from points.")
|
132
128
|
return cls(zoom_level=zoom_level, quadkeys=list(quadkeys), **kwargs)
|
133
129
|
|
@@ -219,6 +215,29 @@ class MercatorTiles(BaseModel):
|
|
219
215
|
{"quadkey": self.quadkeys, "geometry": self.to_geoms()}, crs="EPSG:4326"
|
220
216
|
)
|
221
217
|
|
218
|
+
@staticmethod
|
219
|
+
def get_quadkeys_from_points(
|
220
|
+
points: List[Union[Point, Tuple[float, float]]], zoom_level: int
|
221
|
+
) -> List[str]:
|
222
|
+
"""Get list of quadkeys for the provided points at specified zoom level.
|
223
|
+
|
224
|
+
Args:
|
225
|
+
points: List of points as either shapely Points or (lon, lat) tuples
|
226
|
+
zoom_level: Zoom level for the quadkeys
|
227
|
+
|
228
|
+
Returns:
|
229
|
+
List of quadkey strings
|
230
|
+
"""
|
231
|
+
quadkeys = [
|
232
|
+
(
|
233
|
+
mercantile.quadkey(mercantile.tile(p.x, p.y, zoom_level))
|
234
|
+
if isinstance(p, Point)
|
235
|
+
else mercantile.quadkey(mercantile.tile(p[1], p[0], zoom_level))
|
236
|
+
)
|
237
|
+
for p in points
|
238
|
+
]
|
239
|
+
return quadkeys
|
240
|
+
|
222
241
|
def save(self, file: Union[str, Path], format: str = "json") -> None:
|
223
242
|
"""Save MercatorTiles to file in specified format."""
|
224
243
|
with self.data_store.open(str(file), "wb" if format == "parquet" else "w") as f:
|
@@ -270,6 +289,10 @@ class CountryMercatorTiles(MercatorTiles):
|
|
270
289
|
country=pycountry.countries.lookup(country).alpha_3,
|
271
290
|
)
|
272
291
|
|
292
|
+
cls.logger.info(
|
293
|
+
f"Initializing Mercator zones for country: {country} at zoom level {zoom_level}"
|
294
|
+
)
|
295
|
+
|
273
296
|
country_geom = (
|
274
297
|
AdminBoundaries.create(
|
275
298
|
country_code=country,
|
gigaspatial/handlers/__init__.py
CHANGED
@@ -21,7 +21,14 @@ from gigaspatial.handlers.osm import OSMLocationFetcher
|
|
21
21
|
from gigaspatial.handlers.overture import OvertureAmenityFetcher
|
22
22
|
from gigaspatial.handlers.mapbox_image import MapboxImageDownloader
|
23
23
|
from gigaspatial.handlers.maxar_image import MaxarConfig, MaxarImageDownloader
|
24
|
-
|
24
|
+
|
25
|
+
from gigaspatial.handlers.worldpop import (
|
26
|
+
WPPopulationConfig,
|
27
|
+
WPPopulationReader,
|
28
|
+
WPPopulationDownloader,
|
29
|
+
WPPopulationHandler,
|
30
|
+
WorldPopRestClient,
|
31
|
+
)
|
25
32
|
from gigaspatial.handlers.ookla_speedtest import (
|
26
33
|
OoklaSpeedtestTileConfig,
|
27
34
|
OoklaSpeedtestConfig,
|
gigaspatial/handlers/base.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
|
+
from dataclasses import dataclass, field
|
2
3
|
from pathlib import Path
|
3
4
|
from typing import Any, List, Optional, Union, Tuple, Callable, Iterable
|
4
5
|
import pandas as pd
|
@@ -13,7 +14,6 @@ from gigaspatial.core.io.data_store import DataStore
|
|
13
14
|
from gigaspatial.core.io.local_data_store import LocalDataStore
|
14
15
|
from gigaspatial.core.io.readers import read_dataset
|
15
16
|
from gigaspatial.processing.tif_processor import TifProcessor
|
16
|
-
from dataclasses import dataclass, field
|
17
17
|
|
18
18
|
|
19
19
|
@dataclass
|
@@ -584,6 +584,8 @@ class BaseHandler(ABC):
|
|
584
584
|
bool: True if data is available after this operation
|
585
585
|
"""
|
586
586
|
try:
|
587
|
+
data_units = None
|
588
|
+
data_paths = None
|
587
589
|
# Resolve what data units are needed
|
588
590
|
if hasattr(self.config, "get_relevant_data_units"):
|
589
591
|
data_units = self.config.get_relevant_data_units(source, **kwargs)
|
@@ -606,11 +608,29 @@ class BaseHandler(ABC):
|
|
606
608
|
if not missing_paths:
|
607
609
|
self.logger.info("All required data is already available")
|
608
610
|
return True
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
611
|
+
else:
|
612
|
+
# If force_download, treat all as missing
|
613
|
+
missing_paths = data_paths
|
614
|
+
|
615
|
+
if not missing_paths:
|
616
|
+
self.logger.info("No missing data to download.")
|
617
|
+
return True
|
618
|
+
|
619
|
+
# Download logic
|
620
|
+
if data_units is not None:
|
621
|
+
# Map data_units to their paths and select only those that are missing
|
622
|
+
unit_to_path = dict(zip(data_units, data_paths))
|
623
|
+
if force_download:
|
624
|
+
# Download all units if force_download
|
625
|
+
self.downloader.download_data_units(data_units, **kwargs)
|
626
|
+
else:
|
627
|
+
missing_units = [
|
628
|
+
unit
|
629
|
+
for unit, path in unit_to_path.items()
|
630
|
+
if path in missing_paths
|
631
|
+
]
|
632
|
+
if missing_units:
|
633
|
+
self.downloader.download_data_units(missing_units, **kwargs)
|
614
634
|
else:
|
615
635
|
self.downloader.download(source, **kwargs)
|
616
636
|
|