voxcity 0.5.14__py3-none-any.whl → 0.5.16__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.
Potentially problematic release.
This version of voxcity might be problematic. Click here for more details.
- voxcity/downloader/citygml.py +202 -28
- voxcity/downloader/eubucco.py +91 -14
- voxcity/downloader/gee.py +164 -22
- voxcity/downloader/mbfp.py +55 -9
- voxcity/downloader/oemj.py +110 -24
- voxcity/downloader/omt.py +74 -7
- voxcity/downloader/osm.py +109 -23
- voxcity/downloader/overture.py +108 -23
- voxcity/downloader/utils.py +37 -7
- voxcity/exporter/envimet.py +180 -61
- voxcity/exporter/magicavoxel.py +138 -28
- voxcity/exporter/obj.py +159 -36
- voxcity/generator.py +159 -76
- voxcity/geoprocessor/draw.py +180 -27
- voxcity/geoprocessor/grid.py +178 -38
- voxcity/geoprocessor/mesh.py +347 -43
- voxcity/geoprocessor/network.py +196 -63
- voxcity/geoprocessor/polygon.py +365 -88
- voxcity/geoprocessor/utils.py +283 -72
- voxcity/simulator/solar.py +596 -201
- voxcity/simulator/view.py +278 -723
- voxcity/utils/lc.py +183 -0
- voxcity/utils/material.py +99 -32
- voxcity/utils/visualization.py +2578 -1988
- voxcity/utils/weather.py +816 -615
- {voxcity-0.5.14.dist-info → voxcity-0.5.16.dist-info}/METADATA +11 -13
- voxcity-0.5.16.dist-info/RECORD +38 -0
- {voxcity-0.5.14.dist-info → voxcity-0.5.16.dist-info}/WHEEL +1 -1
- voxcity-0.5.14.dist-info/RECORD +0 -38
- {voxcity-0.5.14.dist-info → voxcity-0.5.16.dist-info}/licenses/AUTHORS.rst +0 -0
- {voxcity-0.5.14.dist-info → voxcity-0.5.16.dist-info}/licenses/LICENSE +0 -0
- {voxcity-0.5.14.dist-info → voxcity-0.5.16.dist-info}/top_level.txt +0 -0
voxcity/geoprocessor/utils.py
CHANGED
|
@@ -3,12 +3,33 @@ Utility functions for geographic operations and coordinate transformations.
|
|
|
3
3
|
|
|
4
4
|
This module provides various utility functions for working with geographic data,
|
|
5
5
|
including coordinate transformations, distance calculations, geocoding, and building
|
|
6
|
-
polygon processing.
|
|
6
|
+
polygon processing. It supports operations such as:
|
|
7
|
+
|
|
8
|
+
- Tile coordinate calculations and quadkey conversions
|
|
9
|
+
- Geographic distance calculations (Haversine and geodetic)
|
|
10
|
+
- Coordinate system transformations
|
|
11
|
+
- Polygon and GeoDataFrame operations
|
|
12
|
+
- Raster file processing and merging
|
|
13
|
+
- Geocoding and reverse geocoding
|
|
14
|
+
- Timezone and location information retrieval
|
|
15
|
+
- Building polygon validation and processing
|
|
16
|
+
|
|
17
|
+
The module uses several external libraries for geographic operations:
|
|
18
|
+
- pyproj: For coordinate transformations and geodetic calculations
|
|
19
|
+
- geopandas: For handling geographic data frames
|
|
20
|
+
- rasterio: For raster file operations
|
|
21
|
+
- shapely: For geometric operations
|
|
22
|
+
- geopy: For geocoding services
|
|
23
|
+
- timezonefinder: For timezone lookups
|
|
7
24
|
"""
|
|
8
25
|
|
|
26
|
+
# Standard library imports
|
|
9
27
|
import os
|
|
10
28
|
import math
|
|
11
29
|
from math import radians, sin, cos, sqrt, atan2
|
|
30
|
+
from datetime import datetime
|
|
31
|
+
|
|
32
|
+
# Third-party geographic processing libraries
|
|
12
33
|
import numpy as np
|
|
13
34
|
from pyproj import Geod, Transformer
|
|
14
35
|
import geopandas as gpd
|
|
@@ -19,32 +40,40 @@ from rasterio.mask import mask
|
|
|
19
40
|
from shapely.geometry import Polygon, box
|
|
20
41
|
from fiona.crs import from_epsg
|
|
21
42
|
from rtree import index
|
|
43
|
+
|
|
44
|
+
# Geocoding and location services
|
|
22
45
|
from geopy.geocoders import Nominatim
|
|
23
46
|
from geopy.exc import GeocoderTimedOut, GeocoderServiceError
|
|
24
47
|
from geopy.extra.rate_limiter import RateLimiter
|
|
25
|
-
import warnings
|
|
26
48
|
import reverse_geocoder as rg
|
|
27
49
|
import pycountry
|
|
28
50
|
|
|
51
|
+
# Timezone handling
|
|
29
52
|
from timezonefinder import TimezoneFinder
|
|
30
53
|
import pytz
|
|
31
|
-
from datetime import datetime
|
|
32
54
|
|
|
55
|
+
# Suppress rasterio warnings for non-georeferenced files
|
|
56
|
+
import warnings
|
|
33
57
|
warnings.filterwarnings("ignore", category=rasterio.errors.NotGeoreferencedWarning)
|
|
34
58
|
|
|
35
|
-
|
|
59
|
+
# Global constants
|
|
60
|
+
floor_height = 2.5 # Standard floor height in meters used for building height calculations
|
|
36
61
|
|
|
37
62
|
def tile_from_lat_lon(lat, lon, level_of_detail):
|
|
38
63
|
"""
|
|
39
64
|
Convert latitude/longitude coordinates to tile coordinates at a given zoom level.
|
|
65
|
+
Uses the Web Mercator projection (EPSG:3857) commonly used in web mapping.
|
|
40
66
|
|
|
41
67
|
Args:
|
|
42
|
-
lat (float): Latitude in degrees
|
|
43
|
-
lon (float): Longitude in degrees
|
|
44
|
-
level_of_detail (int): Zoom level
|
|
68
|
+
lat (float): Latitude in degrees (-90 to 90)
|
|
69
|
+
lon (float): Longitude in degrees (-180 to 180)
|
|
70
|
+
level_of_detail (int): Zoom level (0-23, where 0 is the entire world)
|
|
45
71
|
|
|
46
72
|
Returns:
|
|
47
|
-
tuple: (tile_x, tile_y) tile coordinates
|
|
73
|
+
tuple: (tile_x, tile_y) tile coordinates in the global tile grid
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
>>> tile_x, tile_y = tile_from_lat_lon(35.6762, 139.6503, 12) # Tokyo at zoom 12
|
|
48
77
|
"""
|
|
49
78
|
# Convert latitude to radians and calculate sine
|
|
50
79
|
sin_lat = math.sin(lat * math.pi / 180)
|
|
@@ -70,11 +99,20 @@ def quadkey_to_tile(quadkey):
|
|
|
70
99
|
Each digit in the quadkey represents a tile at a zoom level, with each subsequent digit
|
|
71
100
|
representing a more detailed zoom level.
|
|
72
101
|
|
|
102
|
+
The quadkey numbering scheme:
|
|
103
|
+
- 0: Top-left quadrant
|
|
104
|
+
- 1: Top-right quadrant
|
|
105
|
+
- 2: Bottom-left quadrant
|
|
106
|
+
- 3: Bottom-right quadrant
|
|
107
|
+
|
|
73
108
|
Args:
|
|
74
|
-
quadkey (str): Quadkey string
|
|
109
|
+
quadkey (str): Quadkey string (e.g., "120" for zoom level 3)
|
|
75
110
|
|
|
76
111
|
Returns:
|
|
77
112
|
tuple: (tile_x, tile_y, level_of_detail) tile coordinates and zoom level
|
|
113
|
+
|
|
114
|
+
Example:
|
|
115
|
+
>>> x, y, zoom = quadkey_to_tile("120") # Returns coordinates at zoom level 3
|
|
78
116
|
"""
|
|
79
117
|
tile_x = tile_y = 0
|
|
80
118
|
level_of_detail = len(quadkey)
|
|
@@ -101,25 +139,42 @@ def quadkey_to_tile(quadkey):
|
|
|
101
139
|
def initialize_geod():
|
|
102
140
|
"""
|
|
103
141
|
Initialize a Geod object for geodetic calculations using WGS84 ellipsoid.
|
|
104
|
-
The WGS84 ellipsoid is the standard reference system used by GPS
|
|
142
|
+
The WGS84 ellipsoid (EPSG:4326) is the standard reference system used by GPS
|
|
143
|
+
and most modern mapping applications.
|
|
144
|
+
|
|
145
|
+
The Geod object provides methods for:
|
|
146
|
+
- Forward geodetic calculations (direct)
|
|
147
|
+
- Inverse geodetic calculations (inverse)
|
|
148
|
+
- Area calculations
|
|
149
|
+
- Line length calculations
|
|
105
150
|
|
|
106
151
|
Returns:
|
|
107
|
-
Geod: Initialized Geod object
|
|
152
|
+
Geod: Initialized Geod object for WGS84 calculations
|
|
153
|
+
|
|
154
|
+
Example:
|
|
155
|
+
>>> geod = initialize_geod()
|
|
156
|
+
>>> fwd_az, back_az, dist = geod.inv(lon1, lat1, lon2, lat2)
|
|
108
157
|
"""
|
|
109
158
|
return Geod(ellps='WGS84')
|
|
110
159
|
|
|
111
160
|
def calculate_distance(geod, lon1, lat1, lon2, lat2):
|
|
112
161
|
"""
|
|
113
|
-
Calculate geodetic distance between two points.
|
|
114
|
-
Uses inverse geodetic computation to find the shortest distance along the ellipsoid
|
|
162
|
+
Calculate geodetic distance between two points on the Earth's surface.
|
|
163
|
+
Uses inverse geodetic computation to find the shortest distance along the ellipsoid,
|
|
164
|
+
which is more accurate than great circle (spherical) calculations.
|
|
115
165
|
|
|
116
166
|
Args:
|
|
117
|
-
geod (Geod): Geod object for calculations
|
|
118
|
-
lon1, lat1 (float): Coordinates of first point
|
|
119
|
-
lon2, lat2 (float): Coordinates of second point
|
|
167
|
+
geod (Geod): Geod object for calculations, initialized with WGS84
|
|
168
|
+
lon1, lat1 (float): Coordinates of first point in decimal degrees
|
|
169
|
+
lon2, lat2 (float): Coordinates of second point in decimal degrees
|
|
120
170
|
|
|
121
171
|
Returns:
|
|
122
|
-
float: Distance in meters
|
|
172
|
+
float: Distance in meters between the two points along the ellipsoid
|
|
173
|
+
|
|
174
|
+
Example:
|
|
175
|
+
>>> geod = initialize_geod()
|
|
176
|
+
>>> distance = calculate_distance(geod, 139.6503, 35.6762,
|
|
177
|
+
... -74.0060, 40.7128) # Tokyo to NYC
|
|
123
178
|
"""
|
|
124
179
|
# inv() returns forward azimuth, back azimuth, and distance
|
|
125
180
|
_, _, dist = geod.inv(lon1, lat1, lon2, lat2)
|
|
@@ -127,43 +182,65 @@ def calculate_distance(geod, lon1, lat1, lon2, lat2):
|
|
|
127
182
|
|
|
128
183
|
def normalize_to_one_meter(vector, distance_in_meters):
|
|
129
184
|
"""
|
|
130
|
-
Normalize a vector to represent one meter.
|
|
131
|
-
Useful for creating unit vectors in geographic calculations
|
|
185
|
+
Normalize a vector to represent one meter in geographic space.
|
|
186
|
+
Useful for creating unit vectors in geographic calculations, particularly
|
|
187
|
+
when working with distance-based operations or scaling geographic features.
|
|
132
188
|
|
|
133
189
|
Args:
|
|
134
|
-
vector (numpy.ndarray): Vector to normalize
|
|
135
|
-
distance_in_meters (float): Current distance in meters
|
|
190
|
+
vector (numpy.ndarray): Vector to normalize, typically a direction vector
|
|
191
|
+
distance_in_meters (float): Current distance in meters that the vector represents
|
|
136
192
|
|
|
137
193
|
Returns:
|
|
138
|
-
numpy.ndarray: Normalized vector
|
|
194
|
+
numpy.ndarray: Normalized vector where magnitude represents 1 meter
|
|
195
|
+
|
|
196
|
+
Example:
|
|
197
|
+
>>> direction = np.array([3.0, 4.0]) # Vector of length 5
|
|
198
|
+
>>> unit_meter = normalize_to_one_meter(direction, 5.0)
|
|
139
199
|
"""
|
|
140
200
|
return vector * (1 / distance_in_meters)
|
|
141
201
|
|
|
142
202
|
def setup_transformer(from_crs, to_crs):
|
|
143
203
|
"""
|
|
144
|
-
Set up a coordinate transformer between two CRS.
|
|
145
|
-
The always_xy=True parameter ensures consistent handling of coordinate order
|
|
204
|
+
Set up a coordinate transformer between two Coordinate Reference Systems (CRS).
|
|
205
|
+
The always_xy=True parameter ensures consistent handling of coordinate order
|
|
206
|
+
by always using (x,y) or (longitude,latitude) order regardless of CRS definition.
|
|
207
|
+
|
|
208
|
+
Common CRS codes:
|
|
209
|
+
- EPSG:4326 - WGS84 (latitude/longitude)
|
|
210
|
+
- EPSG:3857 - Web Mercator
|
|
211
|
+
- EPSG:2263 - NY State Plane
|
|
146
212
|
|
|
147
213
|
Args:
|
|
148
|
-
from_crs: Source coordinate reference system
|
|
149
|
-
to_crs: Target coordinate reference system
|
|
214
|
+
from_crs: Source coordinate reference system (EPSG code, proj4 string, or CRS dict)
|
|
215
|
+
to_crs: Target coordinate reference system (EPSG code, proj4 string, or CRS dict)
|
|
150
216
|
|
|
151
217
|
Returns:
|
|
152
|
-
Transformer: Initialized transformer object
|
|
218
|
+
Transformer: Initialized transformer object for coordinate conversion
|
|
219
|
+
|
|
220
|
+
Example:
|
|
221
|
+
>>> transformer = setup_transformer("EPSG:4326", "EPSG:3857")
|
|
222
|
+
>>> x, y = transformer.transform(longitude, latitude)
|
|
153
223
|
"""
|
|
154
224
|
return Transformer.from_crs(from_crs, to_crs, always_xy=True)
|
|
155
225
|
|
|
156
226
|
def transform_coords(transformer, lon, lat):
|
|
157
227
|
"""
|
|
158
|
-
Transform coordinates using provided transformer.
|
|
159
|
-
Includes
|
|
228
|
+
Transform coordinates using provided transformer with error handling.
|
|
229
|
+
Includes validation for infinite values that may result from invalid transformations
|
|
230
|
+
or coordinates outside the valid range for the target CRS.
|
|
160
231
|
|
|
161
232
|
Args:
|
|
162
|
-
transformer (Transformer): Coordinate transformer
|
|
163
|
-
lon, lat (float): Input coordinates
|
|
233
|
+
transformer (Transformer): Coordinate transformer from setup_transformer()
|
|
234
|
+
lon, lat (float): Input coordinates in the source CRS
|
|
164
235
|
|
|
165
236
|
Returns:
|
|
166
|
-
tuple: (x, y) transformed coordinates or (None, None) if transformation fails
|
|
237
|
+
tuple: (x, y) transformed coordinates in the target CRS, or (None, None) if transformation fails
|
|
238
|
+
|
|
239
|
+
Example:
|
|
240
|
+
>>> transformer = setup_transformer("EPSG:4326", "EPSG:3857")
|
|
241
|
+
>>> x, y = transform_coords(transformer, -74.0060, 40.7128) # NYC coordinates
|
|
242
|
+
>>> if x is not None:
|
|
243
|
+
... print(f"Transformed coordinates: ({x}, {y})")
|
|
167
244
|
"""
|
|
168
245
|
try:
|
|
169
246
|
x, y = transformer.transform(lon, lat)
|
|
@@ -176,28 +253,44 @@ def transform_coords(transformer, lon, lat):
|
|
|
176
253
|
|
|
177
254
|
def create_polygon(vertices):
|
|
178
255
|
"""
|
|
179
|
-
Create a Shapely polygon from vertices.
|
|
180
|
-
Input vertices
|
|
256
|
+
Create a Shapely polygon from a list of vertices.
|
|
257
|
+
Input vertices must be in (longitude, latitude) format as required by Shapely.
|
|
258
|
+
The polygon will be automatically closed if the first and last vertices don't match.
|
|
181
259
|
|
|
182
260
|
Args:
|
|
183
|
-
vertices (list): List of (
|
|
261
|
+
vertices (list): List of (longitude, latitude) coordinate pairs forming the polygon.
|
|
262
|
+
The coordinates should be in counter-clockwise order for exterior rings
|
|
263
|
+
and clockwise order for interior rings (holes).
|
|
184
264
|
|
|
185
265
|
Returns:
|
|
186
|
-
Polygon: Shapely polygon object
|
|
266
|
+
Polygon: Shapely polygon object that can be used for spatial operations
|
|
267
|
+
|
|
268
|
+
Example:
|
|
269
|
+
>>> vertices = [(0, 0), (1, 0), (1, 1), (0, 1)] # Square
|
|
270
|
+
>>> polygon = create_polygon(vertices)
|
|
271
|
+
>>> print(f"Polygon area: {polygon.area}")
|
|
187
272
|
"""
|
|
188
273
|
return Polygon(vertices)
|
|
189
274
|
|
|
190
275
|
def create_geodataframe(polygon, crs=4326):
|
|
191
276
|
"""
|
|
192
|
-
Create a GeoDataFrame from a polygon.
|
|
193
|
-
Default CRS is WGS84 (EPSG:4326).
|
|
277
|
+
Create a GeoDataFrame from a Shapely polygon.
|
|
278
|
+
Default CRS is WGS84 (EPSG:4326) for geographic coordinates.
|
|
279
|
+
The GeoDataFrame provides additional functionality for spatial operations,
|
|
280
|
+
data analysis, and export to various geographic formats.
|
|
194
281
|
|
|
195
282
|
Args:
|
|
196
|
-
polygon (Polygon): Shapely polygon object
|
|
197
|
-
crs (int): Coordinate reference system EPSG code
|
|
283
|
+
polygon (Polygon): Shapely polygon object to convert
|
|
284
|
+
crs (int): Coordinate reference system EPSG code (default: 4326 for WGS84)
|
|
198
285
|
|
|
199
286
|
Returns:
|
|
200
|
-
GeoDataFrame: GeoDataFrame containing the polygon
|
|
287
|
+
GeoDataFrame: GeoDataFrame containing the polygon with specified CRS
|
|
288
|
+
|
|
289
|
+
Example:
|
|
290
|
+
>>> vertices = [(0, 0), (1, 0), (1, 1), (0, 1)]
|
|
291
|
+
>>> polygon = create_polygon(vertices)
|
|
292
|
+
>>> gdf = create_geodataframe(polygon)
|
|
293
|
+
>>> gdf.to_file("polygon.geojson", driver="GeoJSON")
|
|
201
294
|
"""
|
|
202
295
|
return gpd.GeoDataFrame({'geometry': [polygon]}, crs=from_epsg(crs))
|
|
203
296
|
|
|
@@ -229,14 +322,19 @@ def haversine_distance(lon1, lat1, lon2, lat2):
|
|
|
229
322
|
|
|
230
323
|
def get_raster_bbox(raster_path):
|
|
231
324
|
"""
|
|
232
|
-
Get bounding box of a raster file.
|
|
233
|
-
Returns a rectangular polygon representing the spatial extent of the raster
|
|
325
|
+
Get the bounding box of a raster file in its native coordinate system.
|
|
326
|
+
Returns a rectangular polygon representing the spatial extent of the raster,
|
|
327
|
+
which can be used for spatial queries and intersection tests.
|
|
234
328
|
|
|
235
329
|
Args:
|
|
236
|
-
raster_path (str): Path to raster file
|
|
330
|
+
raster_path (str): Path to the raster file (GeoTIFF, IMG, etc.)
|
|
237
331
|
|
|
238
332
|
Returns:
|
|
239
|
-
box: Shapely box representing the raster bounds
|
|
333
|
+
box: Shapely box representing the raster bounds in the raster's CRS
|
|
334
|
+
|
|
335
|
+
Example:
|
|
336
|
+
>>> bbox = get_raster_bbox("elevation.tif")
|
|
337
|
+
>>> print(f"Raster extent: {bbox.bounds}") # (minx, miny, maxx, maxy)
|
|
240
338
|
"""
|
|
241
339
|
with rasterio.open(raster_path) as src:
|
|
242
340
|
bounds = src.bounds
|
|
@@ -244,15 +342,21 @@ def get_raster_bbox(raster_path):
|
|
|
244
342
|
|
|
245
343
|
def raster_intersects_polygon(raster_path, polygon):
|
|
246
344
|
"""
|
|
247
|
-
Check if a raster intersects with a polygon.
|
|
248
|
-
|
|
345
|
+
Check if a raster file's extent intersects with a given polygon.
|
|
346
|
+
Automatically handles coordinate system transformations by converting
|
|
347
|
+
the raster bounds to WGS84 (EPSG:4326) if needed before the intersection test.
|
|
249
348
|
|
|
250
349
|
Args:
|
|
251
|
-
raster_path (str): Path to raster file
|
|
252
|
-
polygon (Polygon): Shapely polygon to
|
|
350
|
+
raster_path (str): Path to the raster file to check
|
|
351
|
+
polygon (Polygon): Shapely polygon to test intersection with (in WGS84)
|
|
253
352
|
|
|
254
353
|
Returns:
|
|
255
|
-
bool: True if raster intersects polygon, False otherwise
|
|
354
|
+
bool: True if raster intersects or contains the polygon, False otherwise
|
|
355
|
+
|
|
356
|
+
Example:
|
|
357
|
+
>>> aoi = create_polygon([(lon1, lat1), (lon2, lat2), ...]) # Area of interest
|
|
358
|
+
>>> if raster_intersects_polygon("dem.tif", aoi):
|
|
359
|
+
... print("Raster covers the area of interest")
|
|
256
360
|
"""
|
|
257
361
|
with rasterio.open(raster_path) as src:
|
|
258
362
|
bounds = src.bounds
|
|
@@ -265,12 +369,17 @@ def raster_intersects_polygon(raster_path, polygon):
|
|
|
265
369
|
|
|
266
370
|
def save_raster(input_path, output_path):
|
|
267
371
|
"""
|
|
268
|
-
|
|
269
|
-
|
|
372
|
+
Create a copy of a raster file at a new location.
|
|
373
|
+
Performs a direct file copy without any transformation or modification,
|
|
374
|
+
preserving all metadata, georeferencing, and pixel values.
|
|
270
375
|
|
|
271
376
|
Args:
|
|
272
377
|
input_path (str): Source raster file path
|
|
273
|
-
output_path (str): Destination
|
|
378
|
+
output_path (str): Destination path for the copied raster
|
|
379
|
+
|
|
380
|
+
Example:
|
|
381
|
+
>>> save_raster("original.tif", "backup/copy.tif")
|
|
382
|
+
>>> print("Copied original file to: backup/copy.tif")
|
|
274
383
|
"""
|
|
275
384
|
import shutil
|
|
276
385
|
shutil.copy(input_path, output_path)
|
|
@@ -278,11 +387,23 @@ def save_raster(input_path, output_path):
|
|
|
278
387
|
|
|
279
388
|
def merge_geotiffs(geotiff_files, output_dir):
|
|
280
389
|
"""
|
|
281
|
-
Merge multiple GeoTIFF files into a single
|
|
390
|
+
Merge multiple GeoTIFF files into a single mosaic.
|
|
391
|
+
Handles edge matching and overlapping areas between adjacent rasters.
|
|
392
|
+
The output will have the same coordinate system and data type as the input files.
|
|
393
|
+
|
|
394
|
+
Important considerations:
|
|
395
|
+
- All input files should have the same coordinate system
|
|
396
|
+
- All input files should have the same data type
|
|
397
|
+
- Overlapping areas are handled by taking the first value encountered
|
|
282
398
|
|
|
283
399
|
Args:
|
|
284
|
-
geotiff_files (list): List of GeoTIFF
|
|
285
|
-
output_dir (str): Directory
|
|
400
|
+
geotiff_files (list): List of paths to GeoTIFF files to merge
|
|
401
|
+
output_dir (str): Directory where the merged output will be saved
|
|
402
|
+
|
|
403
|
+
Example:
|
|
404
|
+
>>> files = ["tile1.tif", "tile2.tif", "tile3.tif"]
|
|
405
|
+
>>> merge_geotiffs(files, "output_directory")
|
|
406
|
+
>>> print("Merged output saved to: output_directory/lulc.tif")
|
|
286
407
|
"""
|
|
287
408
|
if not geotiff_files:
|
|
288
409
|
return
|
|
@@ -338,13 +459,25 @@ def convert_format_lat_lon(input_coords):
|
|
|
338
459
|
|
|
339
460
|
def get_coordinates_from_cityname(place_name):
|
|
340
461
|
"""
|
|
341
|
-
|
|
462
|
+
Geocode a city name to get its coordinates using OpenStreetMap's Nominatim service.
|
|
463
|
+
Includes rate limiting and error handling to comply with Nominatim's usage policy.
|
|
464
|
+
|
|
465
|
+
Note:
|
|
466
|
+
- Results may vary based on the specificity of the place name
|
|
467
|
+
- For better results, include country or state information
|
|
468
|
+
- Service has usage limits and may timeout
|
|
342
469
|
|
|
343
470
|
Args:
|
|
344
|
-
place_name (str): Name of city to geocode
|
|
471
|
+
place_name (str): Name of the city to geocode (e.g., "Tokyo, Japan")
|
|
345
472
|
|
|
346
473
|
Returns:
|
|
347
|
-
tuple: (
|
|
474
|
+
tuple: (latitude, longitude) coordinates or None if geocoding fails
|
|
475
|
+
|
|
476
|
+
Example:
|
|
477
|
+
>>> coords = get_coordinates_from_cityname("Paris, France")
|
|
478
|
+
>>> if coords:
|
|
479
|
+
... lat, lon = coords
|
|
480
|
+
... print(f"Paris coordinates: {lat}, {lon}")
|
|
348
481
|
"""
|
|
349
482
|
# Initialize geocoder with user agent
|
|
350
483
|
geolocator = Nominatim(user_agent="my_geocoding_script")
|
|
@@ -363,13 +496,25 @@ def get_coordinates_from_cityname(place_name):
|
|
|
363
496
|
|
|
364
497
|
def get_city_country_name_from_rectangle(coordinates):
|
|
365
498
|
"""
|
|
366
|
-
Get city and country name
|
|
499
|
+
Get the city and country name for a location defined by a rectangle.
|
|
500
|
+
Uses reverse geocoding to find the nearest named place to the rectangle's center.
|
|
501
|
+
|
|
502
|
+
The function:
|
|
503
|
+
1. Calculates the center point of the rectangle
|
|
504
|
+
2. Performs reverse geocoding with rate limiting
|
|
505
|
+
3. Extracts city and country information from the result
|
|
367
506
|
|
|
368
507
|
Args:
|
|
369
|
-
coordinates (list): List of (
|
|
508
|
+
coordinates (list): List of (longitude, latitude) coordinates defining the rectangle
|
|
370
509
|
|
|
371
510
|
Returns:
|
|
372
511
|
str: String in format "city/ country" or None if lookup fails
|
|
512
|
+
|
|
513
|
+
Example:
|
|
514
|
+
>>> coords = [(139.65, 35.67), (139.66, 35.67),
|
|
515
|
+
... (139.66, 35.68), (139.65, 35.68)]
|
|
516
|
+
>>> location = get_city_country_name_from_rectangle(coords)
|
|
517
|
+
>>> print(f"Location: {location}") # e.g., "Shibuya/ Japan"
|
|
373
518
|
"""
|
|
374
519
|
# Calculate center point of rectangle
|
|
375
520
|
longitudes = [coord[0] for coord in coordinates]
|
|
@@ -398,13 +543,29 @@ def get_city_country_name_from_rectangle(coordinates):
|
|
|
398
543
|
|
|
399
544
|
def get_timezone_info(rectangle_coords):
|
|
400
545
|
"""
|
|
401
|
-
Get timezone and central meridian
|
|
546
|
+
Get timezone and central meridian information for a location.
|
|
547
|
+
Uses the rectangle's center point to determine the local timezone and
|
|
548
|
+
calculates the central meridian based on the UTC offset.
|
|
549
|
+
|
|
550
|
+
The function provides:
|
|
551
|
+
1. Local timezone identifier (e.g., "America/New_York")
|
|
552
|
+
2. UTC offset (e.g., "UTC-04:00")
|
|
553
|
+
3. Central meridian longitude for the timezone
|
|
402
554
|
|
|
403
555
|
Args:
|
|
404
|
-
rectangle_coords (list): List of (
|
|
556
|
+
rectangle_coords (list): List of (longitude, latitude) coordinates defining the area
|
|
405
557
|
|
|
406
558
|
Returns:
|
|
407
|
-
tuple: (timezone string, central meridian longitude string)
|
|
559
|
+
tuple: (timezone string with UTC offset, central meridian longitude string)
|
|
560
|
+
|
|
561
|
+
Raises:
|
|
562
|
+
ValueError: If timezone cannot be determined for the given location
|
|
563
|
+
|
|
564
|
+
Example:
|
|
565
|
+
>>> coords = [(139.65, 35.67), (139.66, 35.67),
|
|
566
|
+
... (139.66, 35.68), (139.65, 35.68)]
|
|
567
|
+
>>> tz, meridian = get_timezone_info(coords)
|
|
568
|
+
>>> print(f"Timezone: {tz}, Meridian: {meridian}") # e.g., "UTC+09:00, 135.00000"
|
|
408
569
|
"""
|
|
409
570
|
# Calculate center point of rectangle
|
|
410
571
|
longitudes = [coord[0] for coord in rectangle_coords]
|
|
@@ -434,13 +595,29 @@ def get_timezone_info(rectangle_coords):
|
|
|
434
595
|
|
|
435
596
|
def validate_polygon_coordinates(geometry):
|
|
436
597
|
"""
|
|
437
|
-
Validate and
|
|
598
|
+
Validate and ensure proper closure of polygon coordinate rings.
|
|
599
|
+
Performs validation and correction of GeoJSON polygon geometries according to
|
|
600
|
+
the GeoJSON specification requirements.
|
|
601
|
+
|
|
602
|
+
Validation checks:
|
|
603
|
+
1. Geometry type (Polygon or MultiPolygon)
|
|
604
|
+
2. Ring closure (first point equals last point)
|
|
605
|
+
3. Minimum number of points (4, including closure)
|
|
438
606
|
|
|
439
607
|
Args:
|
|
440
|
-
geometry (dict): GeoJSON geometry object
|
|
608
|
+
geometry (dict): GeoJSON geometry object with 'type' and 'coordinates' properties
|
|
441
609
|
|
|
442
610
|
Returns:
|
|
443
|
-
bool: True if
|
|
611
|
+
bool: True if polygon coordinates are valid or were successfully corrected,
|
|
612
|
+
False if validation failed
|
|
613
|
+
|
|
614
|
+
Example:
|
|
615
|
+
>>> geom = {
|
|
616
|
+
... "type": "Polygon",
|
|
617
|
+
... "coordinates": [[[0,0], [1,0], [1,1], [0,1]]] # Not closed
|
|
618
|
+
... }
|
|
619
|
+
>>> if validate_polygon_coordinates(geom):
|
|
620
|
+
... print("Polygon is valid") # Will close the ring automatically
|
|
444
621
|
"""
|
|
445
622
|
if geometry['type'] == 'Polygon':
|
|
446
623
|
for ring in geometry['coordinates']:
|
|
@@ -465,12 +642,40 @@ def validate_polygon_coordinates(geometry):
|
|
|
465
642
|
def create_building_polygons(filtered_buildings):
|
|
466
643
|
"""
|
|
467
644
|
Create building polygons with properties from filtered GeoJSON features.
|
|
645
|
+
Processes a list of GeoJSON building features to create Shapely polygons
|
|
646
|
+
with associated height and other properties, while also building a spatial index.
|
|
647
|
+
|
|
648
|
+
Processing steps:
|
|
649
|
+
1. Extract and validate coordinates
|
|
650
|
+
2. Create Shapely polygons
|
|
651
|
+
3. Process building properties (height, levels, etc.)
|
|
652
|
+
4. Build spatial index for efficient querying
|
|
653
|
+
|
|
654
|
+
Height calculation rules:
|
|
655
|
+
- Use explicit height if available
|
|
656
|
+
- Calculate from levels * floor_height if height not available
|
|
657
|
+
- Calculate from floors * floor_height if levels not available
|
|
658
|
+
- Use NaN if no height information available
|
|
468
659
|
|
|
469
660
|
Args:
|
|
470
|
-
filtered_buildings (list): List of GeoJSON building features
|
|
661
|
+
filtered_buildings (list): List of GeoJSON building features with properties
|
|
471
662
|
|
|
472
663
|
Returns:
|
|
473
|
-
tuple: (
|
|
664
|
+
tuple: (
|
|
665
|
+
list of tuples (polygon, height, min_height, is_inner, feature_id),
|
|
666
|
+
rtree spatial index for the polygons
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
Example:
|
|
670
|
+
>>> buildings = [
|
|
671
|
+
... {
|
|
672
|
+
... "type": "Feature",
|
|
673
|
+
... "geometry": {"type": "Polygon", "coordinates": [...]},
|
|
674
|
+
... "properties": {"height": 30, "levels": 10}
|
|
675
|
+
... },
|
|
676
|
+
... # ... more buildings ...
|
|
677
|
+
... ]
|
|
678
|
+
>>> polygons, spatial_idx = create_building_polygons(buildings)
|
|
474
679
|
"""
|
|
475
680
|
building_polygons = []
|
|
476
681
|
idx = index.Index()
|
|
@@ -553,13 +758,19 @@ def create_building_polygons(filtered_buildings):
|
|
|
553
758
|
def get_country_name(lon, lat):
|
|
554
759
|
"""
|
|
555
760
|
Get country name from coordinates using reverse geocoding.
|
|
761
|
+
Uses a local database for fast reverse geocoding to country level,
|
|
762
|
+
then converts the country code to full name using pycountry.
|
|
556
763
|
|
|
557
764
|
Args:
|
|
558
|
-
lon (float): Longitude
|
|
559
|
-
lat (float): Latitude
|
|
765
|
+
lon (float): Longitude in decimal degrees
|
|
766
|
+
lat (float): Latitude in decimal degrees
|
|
560
767
|
|
|
561
768
|
Returns:
|
|
562
|
-
str:
|
|
769
|
+
str: Full country name or None if lookup fails
|
|
770
|
+
|
|
771
|
+
Example:
|
|
772
|
+
>>> country = get_country_name(139.6503, 35.6762)
|
|
773
|
+
>>> print(f"Country: {country}") # "Japan"
|
|
563
774
|
"""
|
|
564
775
|
# Use reverse geocoder to get country code
|
|
565
776
|
results = rg.search((lat, lon))
|