voxcity 0.3.2__py3-none-any.whl → 0.3.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.
Potentially problematic release.
This version of voxcity might be problematic. Click here for more details.
- voxcity/download/eubucco.py +9 -17
- voxcity/download/gee.py +4 -3
- voxcity/download/mbfp.py +7 -7
- voxcity/download/oemj.py +22 -22
- voxcity/download/omt.py +10 -10
- voxcity/download/osm.py +23 -21
- voxcity/download/overture.py +7 -15
- voxcity/file/envimet.py +4 -4
- voxcity/file/geojson.py +83 -26
- voxcity/geo/draw.py +128 -22
- voxcity/geo/grid.py +9 -143
- voxcity/geo/utils.py +79 -66
- voxcity/sim/solar.py +187 -53
- voxcity/sim/view.py +183 -31
- voxcity/utils/weather.py +7 -7
- {voxcity-0.3.2.dist-info → voxcity-0.3.4.dist-info}/METADATA +61 -5
- voxcity-0.3.4.dist-info/RECORD +34 -0
- {voxcity-0.3.2.dist-info → voxcity-0.3.4.dist-info}/WHEEL +1 -1
- voxcity-0.3.2.dist-info/RECORD +0 -34
- {voxcity-0.3.2.dist-info → voxcity-0.3.4.dist-info}/AUTHORS.rst +0 -0
- {voxcity-0.3.2.dist-info → voxcity-0.3.4.dist-info}/LICENSE +0 -0
- {voxcity-0.3.2.dist-info → voxcity-0.3.4.dist-info}/top_level.txt +0 -0
voxcity/geo/utils.py
CHANGED
|
@@ -177,16 +177,15 @@ def transform_coords(transformer, lon, lat):
|
|
|
177
177
|
def create_polygon(vertices):
|
|
178
178
|
"""
|
|
179
179
|
Create a Shapely polygon from vertices.
|
|
180
|
-
|
|
180
|
+
Input vertices are already in (lon,lat) format required by Shapely.
|
|
181
181
|
|
|
182
182
|
Args:
|
|
183
|
-
vertices (list): List of (
|
|
183
|
+
vertices (list): List of (lon, lat) coordinate pairs
|
|
184
184
|
|
|
185
185
|
Returns:
|
|
186
186
|
Polygon: Shapely polygon object
|
|
187
187
|
"""
|
|
188
|
-
|
|
189
|
-
return Polygon(flipped_vertices)
|
|
188
|
+
return Polygon(vertices)
|
|
190
189
|
|
|
191
190
|
def create_geodataframe(polygon, crs=4326):
|
|
192
191
|
"""
|
|
@@ -202,14 +201,14 @@ def create_geodataframe(polygon, crs=4326):
|
|
|
202
201
|
"""
|
|
203
202
|
return gpd.GeoDataFrame({'geometry': [polygon]}, crs=from_epsg(crs))
|
|
204
203
|
|
|
205
|
-
def haversine_distance(lat1,
|
|
204
|
+
def haversine_distance(lon1, lat1, lon2, lat2):
|
|
206
205
|
"""
|
|
207
206
|
Calculate great-circle distance between two points using Haversine formula.
|
|
208
207
|
This is an approximation that treats the Earth as a perfect sphere.
|
|
209
208
|
|
|
210
209
|
Args:
|
|
211
|
-
|
|
212
|
-
|
|
210
|
+
lon1, lat1 (float): Coordinates of first point
|
|
211
|
+
lon2, lat2 (float): Coordinates of second point
|
|
213
212
|
|
|
214
213
|
Returns:
|
|
215
214
|
float: Distance in kilometers
|
|
@@ -323,15 +322,16 @@ def merge_geotiffs(geotiff_files, output_dir):
|
|
|
323
322
|
def convert_format_lat_lon(input_coords):
|
|
324
323
|
"""
|
|
325
324
|
Convert coordinate format and close polygon.
|
|
325
|
+
Input coordinates are already in [lon, lat] format.
|
|
326
326
|
|
|
327
327
|
Args:
|
|
328
328
|
input_coords (list): List of [lon, lat] coordinates
|
|
329
329
|
|
|
330
330
|
Returns:
|
|
331
|
-
list: List of [
|
|
331
|
+
list: List of [lon, lat] coordinates with first point repeated at end
|
|
332
332
|
"""
|
|
333
|
-
#
|
|
334
|
-
output_coords =
|
|
333
|
+
# Create list with coordinates in same order
|
|
334
|
+
output_coords = input_coords.copy()
|
|
335
335
|
# Close polygon by repeating first point at end
|
|
336
336
|
output_coords.append(output_coords[0])
|
|
337
337
|
return output_coords
|
|
@@ -344,7 +344,7 @@ def get_coordinates_from_cityname(place_name):
|
|
|
344
344
|
place_name (str): Name of city to geocode
|
|
345
345
|
|
|
346
346
|
Returns:
|
|
347
|
-
tuple: (
|
|
347
|
+
tuple: (longitude, latitude) or None if geocoding fails
|
|
348
348
|
"""
|
|
349
349
|
# Initialize geocoder with user agent
|
|
350
350
|
geolocator = Nominatim(user_agent="my_geocoding_script")
|
|
@@ -366,16 +366,16 @@ def get_city_country_name_from_rectangle(coordinates):
|
|
|
366
366
|
Get city and country name from rectangle coordinates.
|
|
367
367
|
|
|
368
368
|
Args:
|
|
369
|
-
coordinates (list): List of (
|
|
369
|
+
coordinates (list): List of (lon, lat) coordinates defining rectangle
|
|
370
370
|
|
|
371
371
|
Returns:
|
|
372
372
|
str: String in format "city/ country" or None if lookup fails
|
|
373
373
|
"""
|
|
374
374
|
# Calculate center point of rectangle
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
center_lat = sum(latitudes) / len(latitudes)
|
|
375
|
+
longitudes = [coord[0] for coord in coordinates]
|
|
376
|
+
latitudes = [coord[1] for coord in coordinates]
|
|
378
377
|
center_lon = sum(longitudes) / len(longitudes)
|
|
378
|
+
center_lat = sum(latitudes) / len(latitudes)
|
|
379
379
|
center_coord = (center_lat, center_lon)
|
|
380
380
|
|
|
381
381
|
# Initialize geocoder with rate limiting to avoid hitting API limits
|
|
@@ -401,17 +401,16 @@ def get_timezone_info(rectangle_coords):
|
|
|
401
401
|
Get timezone and central meridian info for a location.
|
|
402
402
|
|
|
403
403
|
Args:
|
|
404
|
-
rectangle_coords (list): List of (
|
|
404
|
+
rectangle_coords (list): List of (lon, lat) coordinates defining rectangle
|
|
405
405
|
|
|
406
406
|
Returns:
|
|
407
407
|
tuple: (timezone string, central meridian longitude string)
|
|
408
408
|
"""
|
|
409
409
|
# Calculate center point of rectangle
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
center_lat = sum(latitudes) / len(latitudes)
|
|
410
|
+
longitudes = [coord[0] for coord in rectangle_coords]
|
|
411
|
+
latitudes = [coord[1] for coord in rectangle_coords]
|
|
413
412
|
center_lon = sum(longitudes) / len(longitudes)
|
|
414
|
-
|
|
413
|
+
center_lat = sum(latitudes) / len(latitudes)
|
|
415
414
|
|
|
416
415
|
# Find timezone at center coordinates
|
|
417
416
|
tf = TimezoneFinder()
|
|
@@ -475,6 +474,7 @@ def create_building_polygons(filtered_buildings):
|
|
|
475
474
|
"""
|
|
476
475
|
building_polygons = []
|
|
477
476
|
idx = index.Index()
|
|
477
|
+
valid_count = 0
|
|
478
478
|
count = 0
|
|
479
479
|
|
|
480
480
|
# Find highest existing ID to avoid duplicates
|
|
@@ -487,63 +487,76 @@ def create_building_polygons(filtered_buildings):
|
|
|
487
487
|
else:
|
|
488
488
|
id_count = 1
|
|
489
489
|
|
|
490
|
-
for
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
if
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
490
|
+
for building in filtered_buildings:
|
|
491
|
+
try:
|
|
492
|
+
# Handle potential nested coordinate tuples
|
|
493
|
+
coords = building['geometry']['coordinates'][0]
|
|
494
|
+
# Flatten coordinates if they're nested tuples
|
|
495
|
+
if isinstance(coords[0], tuple):
|
|
496
|
+
coords = [list(c) for c in coords]
|
|
497
|
+
elif isinstance(coords[0][0], tuple):
|
|
498
|
+
coords = [list(c[0]) for c in coords]
|
|
499
|
+
|
|
500
|
+
# Create polygon from coordinates
|
|
501
|
+
polygon = Polygon(coords)
|
|
502
|
+
|
|
503
|
+
# Skip invalid geometries
|
|
504
|
+
if not polygon.is_valid:
|
|
505
|
+
print(f"Warning: Skipping invalid polygon geometry")
|
|
506
|
+
continue
|
|
507
|
+
|
|
508
|
+
height = building['properties'].get('height')
|
|
509
|
+
levels = building['properties'].get('levels')
|
|
510
|
+
floors = building['properties'].get('num_floors')
|
|
511
|
+
min_height = building['properties'].get('min_height')
|
|
512
|
+
min_level = building['properties'].get('min_level')
|
|
513
|
+
min_floor = building['properties'].get('min_floor')
|
|
514
|
+
|
|
515
|
+
if (height is None) or (height<=0):
|
|
516
|
+
if levels is not None:
|
|
517
|
+
height = floor_height * levels
|
|
518
|
+
elif floors is not None:
|
|
519
|
+
height = floor_height * floors
|
|
520
|
+
else:
|
|
521
|
+
count += 1
|
|
522
|
+
height = np.nan
|
|
523
|
+
|
|
524
|
+
if (min_height is None) or (min_height<=0):
|
|
525
|
+
if min_level is not None:
|
|
526
|
+
min_height = floor_height * float(min_level)
|
|
527
|
+
elif min_floor is not None:
|
|
528
|
+
min_height = floor_height * float(min_floor)
|
|
529
|
+
else:
|
|
530
|
+
min_height = 0
|
|
531
|
+
|
|
532
|
+
if building['properties'].get('id') is not None:
|
|
533
|
+
feature_id = building['properties']['id']
|
|
518
534
|
else:
|
|
519
|
-
|
|
535
|
+
feature_id = id_count
|
|
536
|
+
id_count += 1
|
|
520
537
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
feature_id = id_count
|
|
526
|
-
id_count += 1
|
|
527
|
-
|
|
528
|
-
# Check if building is inner part of another building
|
|
529
|
-
if building['properties'].get('is_inner') is not None:
|
|
530
|
-
is_inner = building['properties']['is_inner']
|
|
531
|
-
else:
|
|
532
|
-
is_inner = False
|
|
538
|
+
if building['properties'].get('is_inner') is not None:
|
|
539
|
+
is_inner = building['properties']['is_inner']
|
|
540
|
+
else:
|
|
541
|
+
is_inner = False
|
|
533
542
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
543
|
+
building_polygons.append((polygon, height, min_height, is_inner, feature_id))
|
|
544
|
+
idx.insert(valid_count, polygon.bounds)
|
|
545
|
+
valid_count += 1
|
|
546
|
+
|
|
547
|
+
except Exception as e:
|
|
548
|
+
print(f"Warning: Skipping invalid building geometry: {e}")
|
|
549
|
+
continue
|
|
537
550
|
|
|
538
551
|
return building_polygons, idx
|
|
539
552
|
|
|
540
|
-
def get_country_name(
|
|
553
|
+
def get_country_name(lon, lat):
|
|
541
554
|
"""
|
|
542
555
|
Get country name from coordinates using reverse geocoding.
|
|
543
556
|
|
|
544
557
|
Args:
|
|
545
|
-
lat (float): Latitude
|
|
546
558
|
lon (float): Longitude
|
|
559
|
+
lat (float): Latitude
|
|
547
560
|
|
|
548
561
|
Returns:
|
|
549
562
|
str: Country name or None if lookup fails
|