giga-spatial 0.6.0__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.
Files changed (47) hide show
  1. giga_spatial-0.6.0.dist-info/METADATA +141 -0
  2. giga_spatial-0.6.0.dist-info/RECORD +47 -0
  3. giga_spatial-0.6.0.dist-info/WHEEL +5 -0
  4. giga_spatial-0.6.0.dist-info/licenses/LICENSE +661 -0
  5. giga_spatial-0.6.0.dist-info/top_level.txt +1 -0
  6. gigaspatial/__init__.py +1 -0
  7. gigaspatial/config.py +226 -0
  8. gigaspatial/core/__init__.py +0 -0
  9. gigaspatial/core/io/__init__.py +5 -0
  10. gigaspatial/core/io/adls_data_store.py +325 -0
  11. gigaspatial/core/io/data_api.py +113 -0
  12. gigaspatial/core/io/data_store.py +147 -0
  13. gigaspatial/core/io/local_data_store.py +92 -0
  14. gigaspatial/core/io/readers.py +265 -0
  15. gigaspatial/core/io/writers.py +128 -0
  16. gigaspatial/core/schemas/__init__.py +0 -0
  17. gigaspatial/core/schemas/entity.py +244 -0
  18. gigaspatial/generators/__init__.py +2 -0
  19. gigaspatial/generators/poi.py +636 -0
  20. gigaspatial/generators/zonal/__init__.py +3 -0
  21. gigaspatial/generators/zonal/base.py +370 -0
  22. gigaspatial/generators/zonal/geometry.py +439 -0
  23. gigaspatial/generators/zonal/mercator.py +78 -0
  24. gigaspatial/grid/__init__.py +1 -0
  25. gigaspatial/grid/mercator_tiles.py +286 -0
  26. gigaspatial/handlers/__init__.py +40 -0
  27. gigaspatial/handlers/base.py +761 -0
  28. gigaspatial/handlers/boundaries.py +305 -0
  29. gigaspatial/handlers/ghsl.py +772 -0
  30. gigaspatial/handlers/giga.py +145 -0
  31. gigaspatial/handlers/google_open_buildings.py +472 -0
  32. gigaspatial/handlers/hdx.py +241 -0
  33. gigaspatial/handlers/mapbox_image.py +208 -0
  34. gigaspatial/handlers/maxar_image.py +291 -0
  35. gigaspatial/handlers/microsoft_global_buildings.py +548 -0
  36. gigaspatial/handlers/ookla_speedtest.py +199 -0
  37. gigaspatial/handlers/opencellid.py +290 -0
  38. gigaspatial/handlers/osm.py +356 -0
  39. gigaspatial/handlers/overture.py +126 -0
  40. gigaspatial/handlers/rwi.py +157 -0
  41. gigaspatial/handlers/unicef_georepo.py +806 -0
  42. gigaspatial/handlers/worldpop.py +266 -0
  43. gigaspatial/processing/__init__.py +4 -0
  44. gigaspatial/processing/geo.py +1054 -0
  45. gigaspatial/processing/sat_images.py +39 -0
  46. gigaspatial/processing/tif_processor.py +477 -0
  47. gigaspatial/processing/utils.py +49 -0
@@ -0,0 +1,291 @@
1
+ from typing import Optional, Tuple, List, Union, Any, Literal
2
+ from pydantic import BaseModel, Field, HttpUrl, field_validator
3
+ from pathlib import Path
4
+ import mercantile
5
+ from tqdm import tqdm
6
+ import geopandas as gpd
7
+ import pandas as pd
8
+ from owslib.wms import WebMapService
9
+ from time import sleep
10
+ from gigaspatial.grid.mercator_tiles import MercatorTiles
11
+ from gigaspatial.core.io.data_store import DataStore
12
+ from gigaspatial.core.io.local_data_store import LocalDataStore
13
+ from gigaspatial.processing.geo import (
14
+ convert_to_geodataframe,
15
+ buffer_geodataframe,
16
+ )
17
+ from gigaspatial.processing.sat_images import calculate_pixels_at_location
18
+ from gigaspatial.config import config as global_config
19
+
20
+
21
+ class MaxarConfig(BaseModel):
22
+ """Configuration for Maxar Image Downloader using Pydantic"""
23
+
24
+ username: str = Field(
25
+ default=global_config.MAXAR_USERNAME, description="Maxar API username"
26
+ )
27
+ password: str = Field(
28
+ default=global_config.MAXAR_PASSWORD, description="Maxar API password"
29
+ )
30
+ connection_string: str = Field(
31
+ default=global_config.MAXAR_CONNECTION_STRING,
32
+ description="Maxar WMS connection string",
33
+ )
34
+
35
+ base_url: HttpUrl = Field(
36
+ default="https://evwhs.digitalglobe.com/mapservice/wmsaccess?",
37
+ description="Base URL for Maxar WMS service",
38
+ )
39
+
40
+ layers: List[Literal["DigitalGlobe:ImageryFootprint", "DigitalGlobe:Imagery"]] = (
41
+ Field(
42
+ default=["DigitalGlobe:Imagery"],
43
+ description="List of layers to request from WMS",
44
+ )
45
+ )
46
+
47
+ feature_profile: str = Field(
48
+ default="Most_Aesthetic_Mosaic_Profile",
49
+ description="Feature profile to use for WMS requests",
50
+ )
51
+
52
+ coverage_cql_filter: str = Field(
53
+ default="", description="CQL filter for coverage selection"
54
+ )
55
+
56
+ exceptions: str = Field(
57
+ default="application/vnd.ogc.se_xml",
58
+ description="Exception handling format for WMS",
59
+ )
60
+
61
+ transparent: bool = Field(
62
+ default=True,
63
+ description="Whether the requested images should have transparency",
64
+ )
65
+
66
+ image_format: Literal["image/png", "image/jpeg", "image/geotiff"] = Field(
67
+ default="image/png",
68
+ )
69
+
70
+ data_crs: Literal["EPSG:4326", "EPSG:3395", "EPSG:3857", "CAR:42004"] = Field(
71
+ default="EPSG:4326"
72
+ )
73
+
74
+ max_retries: int = Field(
75
+ default=3, description="Number of retries for failed image downloads"
76
+ )
77
+
78
+ retry_delay: int = Field(default=5, description="Delay in seconds between retries")
79
+
80
+ @field_validator("username", "password", "connection_string")
81
+ @classmethod
82
+ def validate_non_empty(cls, value: str, field) -> str:
83
+ """Ensure required credentials are provided"""
84
+ if not value or value.strip() == "":
85
+ raise ValueError(
86
+ f"{field.name} cannot be empty. Please provide a valid {field.name}."
87
+ )
88
+ return value
89
+
90
+ @property
91
+ def wms_url(self) -> str:
92
+ """Generate the full WMS URL with connection string"""
93
+ return f"{self.base_url}connectid={self.connection_string}"
94
+
95
+ @property
96
+ def suffix(self) -> str:
97
+ return f".{self.image_format.split('/')[1]}"
98
+
99
+
100
+ class MaxarImageDownloader:
101
+ """Class to download images from Maxar"""
102
+
103
+ def __init__(
104
+ self,
105
+ config: Optional[MaxarConfig] = None,
106
+ data_store: Optional[DataStore] = None,
107
+ ):
108
+ """
109
+ Initialize the downloader with Maxar config.
110
+
111
+ Args:
112
+ config: MaxarConfig instance containing credentials and settings
113
+ data_store: Instance of DataStore for accessing data storage
114
+ """
115
+ self.config = config or MaxarConfig()
116
+ self.wms = WebMapService(
117
+ self.config.wms_url,
118
+ username=self.config.username,
119
+ password=self.config.password,
120
+ )
121
+ self.data_store = data_store or LocalDataStore()
122
+ self.logger = global_config.get_logger(self.__class__.__name__)
123
+
124
+ def _download_single_image(self, bbox, output_path: Union[Path, str], size) -> bool:
125
+ """Download a single image from bbox and pixel size"""
126
+ for attempt in range(self.config.max_retries):
127
+ try:
128
+ img_data = self.wms.getmap(
129
+ bbox=bbox,
130
+ layers=self.config.layers,
131
+ srs=self.config.data_crs,
132
+ size=size,
133
+ featureProfile=self.config.feature_profile,
134
+ coverage_cql_filter=self.config.coverage_cql_filter,
135
+ exceptions=self.config.exceptions,
136
+ transparent=self.config.transparent,
137
+ format=self.config.image_format,
138
+ )
139
+ self.data_store.write_file(str(output_path), img_data.read())
140
+ return True
141
+ except Exception as e:
142
+ self.logger.warning(
143
+ f"Attempt {attempt + 1} of downloading {output_path.name} failed: {str(e)}"
144
+ )
145
+ if attempt < self.max_retries - 1:
146
+ sleep(self.config.retry_delay)
147
+ else:
148
+ self.logger.warning(
149
+ f"Failed to download {output_path.name} after {self.config.max_retries} attemps: {str(e)}"
150
+ )
151
+ return False
152
+
153
+ def download_images_by_tiles(
154
+ self,
155
+ mercator_tiles: "MercatorTiles",
156
+ output_dir: Union[str, Path],
157
+ image_size: Tuple[int, int] = (512, 512),
158
+ image_prefix: str = "maxar_image_",
159
+ ) -> None:
160
+ """
161
+ Download images for given mercator tiles using the specified style
162
+
163
+ Args:
164
+ mercator_tiles: MercatorTiles instance containing quadkeys
165
+ output_dir: Directory to save images
166
+ image_size: Tuple of (width, height) for output images
167
+ image_prefix: Prefix for output image names
168
+ """
169
+ output_dir = Path(output_dir)
170
+
171
+ image_size_str = f"{image_size[0]}x{image_size[1]}"
172
+ total_tiles = len(mercator_tiles.quadkeys)
173
+
174
+ self.logger.info(
175
+ f"Downloading {total_tiles} tiles with size {image_size_str}..."
176
+ )
177
+
178
+ def _get_tile_bounds(quadkey: str) -> Tuple[float]:
179
+ """Get tile bounds from quadkey"""
180
+ tile = mercantile.quadkey_to_tile(quadkey)
181
+ bounds = mercantile.bounds(tile)
182
+ return (bounds.west, bounds.south, bounds.east, bounds.north)
183
+
184
+ def download_image(
185
+ quadkey: str, image_size: Tuple[int, int], suffix: str = self.config.suffix
186
+ ) -> bool:
187
+ bounds = _get_tile_bounds(quadkey)
188
+ file_name = f"{image_prefix}{quadkey}{suffix}"
189
+
190
+ success = self._download_single_image(
191
+ bounds, output_dir / file_name, image_size
192
+ )
193
+
194
+ return success
195
+
196
+ successful_downloads = 0
197
+ with tqdm(total=total_tiles) as pbar:
198
+ for quadkey in mercator_tiles.quadkeys:
199
+ if download_image(quadkey, image_size):
200
+ successful_downloads += 1
201
+ pbar.update(1)
202
+
203
+ self.logger.info(
204
+ f"Successfully downloaded {successful_downloads}/{total_tiles} images!"
205
+ )
206
+
207
+ def download_images_by_bounds(
208
+ self,
209
+ gdf: gpd.GeoDataFrame,
210
+ output_dir: Union[str, Path],
211
+ image_size: Tuple[int, int] = (512, 512),
212
+ image_prefix: str = "maxar_image_",
213
+ ) -> None:
214
+ """
215
+ Download images for given points using the specified style
216
+
217
+ Args:
218
+ gdf_points: GeoDataFrame containing bounding box polygons
219
+ output_dir: Directory to save images
220
+ image_size: Tuple of (width, height) for output images
221
+ image_prefix: Prefix for output image names
222
+ """
223
+ output_dir = Path(output_dir)
224
+
225
+ image_size_str = f"{image_size[0]}x{image_size[1]}"
226
+ total_images = len(gdf)
227
+
228
+ self.logger.info(
229
+ f"Downloading {total_images} images with size {image_size_str}..."
230
+ )
231
+
232
+ def download_image(
233
+ idx: Any,
234
+ bounds: Tuple[float, float, float, float],
235
+ image_size,
236
+ suffix: str = self.config.suffix,
237
+ ) -> bool:
238
+ file_name = f"{image_prefix}{idx}{suffix}"
239
+ success = self._download_single_image(
240
+ bounds, output_dir / file_name, image_size
241
+ )
242
+ return success
243
+
244
+ gdf = gdf.to_crs(self.config.data_crs)
245
+
246
+ successful_downloads = 0
247
+ with tqdm(total=total_images) as pbar:
248
+ for row in gdf.itertuples():
249
+ if download_image(row.Index, tuple(row.geometry.bounds), image_size):
250
+ successful_downloads += 1
251
+ pbar.update(1)
252
+
253
+ self.logger.info(
254
+ f"Successfully downloaded {successful_downloads}/{total_images} images!"
255
+ )
256
+
257
+ def download_images_by_coordinates(
258
+ self,
259
+ data: Union[pd.DataFrame, List[Tuple[float, float]]],
260
+ res_meters_pixel: float,
261
+ output_dir: Union[str, Path],
262
+ image_size: Tuple[int, int] = (512, 512),
263
+ image_prefix: str = "maxar_image_",
264
+ ) -> None:
265
+ """
266
+ Download images for given coordinates by creating bounded boxes around points
267
+
268
+ Args:
269
+ data: Either a DataFrame with either latitude/longitude columns or a geometry column or a list of (lat, lon) tuples
270
+ res_meters_pixel: resolution in meters per pixel
271
+ output_dir: Directory to save images
272
+ image_size: Tuple of (width, height) for output images
273
+ image_prefix: Prefix for output image names
274
+ """
275
+
276
+ if isinstance(data, pd.DataFrame):
277
+ coordinates_df = data
278
+ else:
279
+ coordinates_df = pd.DataFrame(data, columns=["latitude", "longitude"])
280
+
281
+ gdf = convert_to_geodataframe(coordinates_df)
282
+
283
+ buffered_gdf = buffer_geodataframe(
284
+ gdf, res_meters_pixel / 2, cap_style="square"
285
+ )
286
+
287
+ buffered_gdf = buffered_gdf.to_crs(self.config.data_crs)
288
+
289
+ self.download_images_by_bounds(
290
+ buffered_gdf, output_dir, image_size, image_prefix
291
+ )