voxcity 0.3.3__py3-none-any.whl → 0.3.5__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 +25 -39
- voxcity/geo/__init_.py +2 -1
- voxcity/geo/draw.py +41 -45
- voxcity/geo/grid.py +68 -145
- voxcity/geo/network.py +193 -0
- voxcity/geo/utils.py +79 -66
- voxcity/sim/solar.py +5 -5
- voxcity/sim/view.py +5 -5
- voxcity/utils/__init_.py +2 -1
- voxcity/utils/material.py +139 -0
- voxcity/utils/visualization.py +128 -354
- voxcity/utils/weather.py +7 -7
- voxcity/voxcity.py +56 -8
- {voxcity-0.3.3.dist-info → voxcity-0.3.5.dist-info}/METADATA +6 -5
- voxcity-0.3.5.dist-info/RECORD +36 -0
- {voxcity-0.3.3.dist-info → voxcity-0.3.5.dist-info}/WHEEL +1 -1
- voxcity-0.3.3.dist-info/RECORD +0 -34
- {voxcity-0.3.3.dist-info → voxcity-0.3.5.dist-info}/AUTHORS.rst +0 -0
- {voxcity-0.3.3.dist-info → voxcity-0.3.5.dist-info}/LICENSE +0 -0
- {voxcity-0.3.3.dist-info → voxcity-0.3.5.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
|
voxcity/sim/solar.py
CHANGED
|
@@ -374,7 +374,7 @@ def get_global_solar_irradiance_map(
|
|
|
374
374
|
|
|
375
375
|
return global_map
|
|
376
376
|
|
|
377
|
-
def get_solar_positions_astral(times,
|
|
377
|
+
def get_solar_positions_astral(times, lon, lat):
|
|
378
378
|
"""
|
|
379
379
|
Compute solar azimuth and elevation using Astral for given times and location.
|
|
380
380
|
|
|
@@ -385,8 +385,8 @@ def get_solar_positions_astral(times, lat, lon):
|
|
|
385
385
|
|
|
386
386
|
Args:
|
|
387
387
|
times (DatetimeIndex): Array of timezone-aware datetime objects.
|
|
388
|
-
lat (float): Latitude in degrees.
|
|
389
388
|
lon (float): Longitude in degrees.
|
|
389
|
+
lat (float): Latitude in degrees.
|
|
390
390
|
|
|
391
391
|
Returns:
|
|
392
392
|
DataFrame: DataFrame with columns 'azimuth' and 'elevation' containing solar positions.
|
|
@@ -406,7 +406,7 @@ def get_solar_positions_astral(times, lat, lon):
|
|
|
406
406
|
def get_cumulative_global_solar_irradiance(
|
|
407
407
|
voxel_data,
|
|
408
408
|
meshsize,
|
|
409
|
-
df,
|
|
409
|
+
df, lon, lat, tz,
|
|
410
410
|
direct_normal_irradiance_scaling=1.0,
|
|
411
411
|
diffuse_irradiance_scaling=1.0,
|
|
412
412
|
**kwargs
|
|
@@ -424,8 +424,8 @@ def get_cumulative_global_solar_irradiance(
|
|
|
424
424
|
voxel_data (ndarray): 3D array of voxel values.
|
|
425
425
|
meshsize (float): Size of each voxel in meters.
|
|
426
426
|
df (DataFrame): EPW weather data.
|
|
427
|
-
lat (float): Latitude in degrees.
|
|
428
427
|
lon (float): Longitude in degrees.
|
|
428
|
+
lat (float): Latitude in degrees.
|
|
429
429
|
tz (float): Timezone offset in hours.
|
|
430
430
|
direct_normal_irradiance_scaling (float): Scaling factor for direct normal irradiance.
|
|
431
431
|
diffuse_irradiance_scaling (float): Scaling factor for diffuse horizontal irradiance.
|
|
@@ -498,7 +498,7 @@ def get_cumulative_global_solar_irradiance(
|
|
|
498
498
|
df_period_utc = df_period_local.tz_convert(pytz.UTC)
|
|
499
499
|
|
|
500
500
|
# Compute solar positions for period
|
|
501
|
-
solar_positions = get_solar_positions_astral(df_period_utc.index,
|
|
501
|
+
solar_positions = get_solar_positions_astral(df_period_utc.index, lon, lat)
|
|
502
502
|
|
|
503
503
|
# Create kwargs for diffuse calculation
|
|
504
504
|
diffuse_kwargs = kwargs.copy()
|
voxcity/sim/view.py
CHANGED
|
@@ -78,7 +78,7 @@ def calculate_transmittance(length, tree_k=0.6, tree_lad=1.0):
|
|
|
78
78
|
def trace_ray_generic(voxel_data, origin, direction, hit_values, meshsize, tree_k, tree_lad, inclusion_mode=True):
|
|
79
79
|
"""Trace a ray through a voxel grid and check for hits with specified values.
|
|
80
80
|
|
|
81
|
-
Uses DDA
|
|
81
|
+
Uses DDA algorithm to efficiently traverse voxels along ray path.
|
|
82
82
|
Handles tree transmittance using Beer-Lambert law.
|
|
83
83
|
|
|
84
84
|
The DDA algorithm:
|
|
@@ -749,11 +749,11 @@ def get_landmark_visibility_map(voxcity_grid_ori, building_id_grid, building_geo
|
|
|
749
749
|
return None
|
|
750
750
|
|
|
751
751
|
# Calculate center point of rectangle
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
center_lat = (min(lats) + max(lats)) / 2
|
|
752
|
+
lons = [coord[0] for coord in rectangle_vertices]
|
|
753
|
+
lats = [coord[1] for coord in rectangle_vertices]
|
|
755
754
|
center_lon = (min(lons) + max(lons)) / 2
|
|
756
|
-
|
|
755
|
+
center_lat = (min(lats) + max(lats)) / 2
|
|
756
|
+
target_point = (center_lon, center_lat)
|
|
757
757
|
|
|
758
758
|
# Find buildings at center point
|
|
759
759
|
landmark_ids = find_building_containing_point(features, target_point)
|
voxcity/utils/__init_.py
CHANGED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
def get_material_dict():
|
|
4
|
+
"""
|
|
5
|
+
Returns a dictionary mapping material names to their corresponding ID values.
|
|
6
|
+
"""
|
|
7
|
+
return {
|
|
8
|
+
"unknown": -3,
|
|
9
|
+
"brick": -11,
|
|
10
|
+
"wood": -12,
|
|
11
|
+
"concrete": -13,
|
|
12
|
+
"metal": -14,
|
|
13
|
+
"stone": -15,
|
|
14
|
+
"glass": -16,
|
|
15
|
+
"plaster": -17,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def get_modulo_numbers(window_ratio):
|
|
19
|
+
"""
|
|
20
|
+
Determines the appropriate modulo numbers for x, y, z based on window_ratio.
|
|
21
|
+
|
|
22
|
+
Parameters:
|
|
23
|
+
window_ratio: float between 0 and 1.0
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
tuple (x_mod, y_mod, z_mod): modulo numbers for each dimension
|
|
27
|
+
"""
|
|
28
|
+
if window_ratio <= 0.125 + 0.0625: # around 0.125
|
|
29
|
+
return (2, 2, 2)
|
|
30
|
+
elif window_ratio <= 0.25 + 0.125: # around 0.25
|
|
31
|
+
combinations = [(2, 2, 1), (2, 1, 2), (1, 2, 2)]
|
|
32
|
+
return combinations[hash(str(window_ratio)) % len(combinations)]
|
|
33
|
+
elif window_ratio <= 0.5 + 0.125: # around 0.5
|
|
34
|
+
combinations = [(2, 1, 1), (1, 2, 1), (1, 1, 2)]
|
|
35
|
+
return combinations[hash(str(window_ratio)) % len(combinations)]
|
|
36
|
+
elif window_ratio <= 0.75 + 0.125: # around 0.75
|
|
37
|
+
combinations = [(2, 1, 1), (1, 2, 1), (1, 1, 2)]
|
|
38
|
+
return combinations[hash(str(window_ratio)) % len(combinations)]
|
|
39
|
+
else: # above 0.875
|
|
40
|
+
return (1, 1, 1)
|
|
41
|
+
|
|
42
|
+
def set_building_material_by_id(voxelcity_grid, building_id_grid_ori, ids, mark, window_ratio=0.125, glass_id=-16):
|
|
43
|
+
"""
|
|
44
|
+
Marks cells in voxelcity_grid based on building IDs and window ratio.
|
|
45
|
+
Never sets glass_id to cells with maximum z index.
|
|
46
|
+
|
|
47
|
+
Parameters:
|
|
48
|
+
voxelcity_grid: 3D numpy array
|
|
49
|
+
building_id_grid_ori: 2D numpy array containing building IDs
|
|
50
|
+
ids: list/array of building IDs to check
|
|
51
|
+
mark: value to set for marked cells
|
|
52
|
+
window_ratio: float between 0 and 1.0, determines window density:
|
|
53
|
+
~0.125: sparse windows (2,2,2)
|
|
54
|
+
~0.25: medium-sparse windows (2,2,1), (2,1,2), or (1,2,2)
|
|
55
|
+
~0.5: medium windows (2,1,1), (1,2,1), or (1,1,2)
|
|
56
|
+
~0.75: dense windows (2,1,1), (1,2,1), or (1,1,2)
|
|
57
|
+
>0.875: maximum density (1,1,1)
|
|
58
|
+
glass_id: value to set for glass cells (default: -16)
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Modified voxelcity_grid
|
|
62
|
+
"""
|
|
63
|
+
building_id_grid = np.flipud(building_id_grid_ori.copy())
|
|
64
|
+
|
|
65
|
+
# Get modulo numbers based on window_ratio
|
|
66
|
+
x_mod, y_mod, z_mod = get_modulo_numbers(window_ratio)
|
|
67
|
+
|
|
68
|
+
# Get positions where building IDs match
|
|
69
|
+
building_positions = np.where(np.isin(building_id_grid, ids))
|
|
70
|
+
|
|
71
|
+
# Loop through each position that matches building IDs
|
|
72
|
+
for i in range(len(building_positions[0])):
|
|
73
|
+
x, y = building_positions[0][i], building_positions[1][i]
|
|
74
|
+
z_mask = voxelcity_grid[x, y, :] == -3
|
|
75
|
+
voxelcity_grid[x, y, z_mask] = mark
|
|
76
|
+
|
|
77
|
+
# Check if x and y meet the modulo conditions
|
|
78
|
+
if x % x_mod == 0 and y % y_mod == 0:
|
|
79
|
+
z_mask = voxelcity_grid[x, y, :] == mark
|
|
80
|
+
if np.any(z_mask):
|
|
81
|
+
# Find the maximum z index where z_mask is True
|
|
82
|
+
z_indices = np.where(z_mask)[0]
|
|
83
|
+
max_z_index = np.max(z_indices)
|
|
84
|
+
|
|
85
|
+
# Create base mask excluding maximum z index
|
|
86
|
+
base_mask = z_mask.copy()
|
|
87
|
+
base_mask[max_z_index] = False
|
|
88
|
+
|
|
89
|
+
# Create pattern mask based on z modulo
|
|
90
|
+
pattern_mask = np.zeros_like(z_mask)
|
|
91
|
+
valid_z_indices = z_indices[z_indices != max_z_index] # Exclude max_z_index
|
|
92
|
+
if len(valid_z_indices) > 0:
|
|
93
|
+
pattern_mask[valid_z_indices[valid_z_indices % z_mod == 0]] = True
|
|
94
|
+
|
|
95
|
+
# For window_ratio around 0.75, add additional pattern
|
|
96
|
+
if 0.625 < window_ratio <= 0.875 and len(valid_z_indices) > 0:
|
|
97
|
+
additional_pattern = np.zeros_like(z_mask)
|
|
98
|
+
additional_pattern[valid_z_indices[valid_z_indices % (z_mod + 1) == 0]] = True
|
|
99
|
+
pattern_mask = np.logical_or(pattern_mask, additional_pattern)
|
|
100
|
+
|
|
101
|
+
# Final mask combines base_mask and pattern_mask
|
|
102
|
+
final_glass_mask = np.logical_and(base_mask, pattern_mask)
|
|
103
|
+
|
|
104
|
+
# Set glass_id for all positions in the final mask
|
|
105
|
+
voxelcity_grid[x, y, final_glass_mask] = glass_id
|
|
106
|
+
|
|
107
|
+
return voxelcity_grid
|
|
108
|
+
|
|
109
|
+
def set_building_material_by_gdf(voxelcity_grid_ori, building_id_grid, gdf_buildings, material_id_dict=None):
|
|
110
|
+
"""
|
|
111
|
+
Sets building materials based on a GeoDataFrame containing building information.
|
|
112
|
+
|
|
113
|
+
Parameters:
|
|
114
|
+
voxelcity_grid_ori: 3D numpy array of the original voxel grid
|
|
115
|
+
building_id_grid: 2D numpy array containing building IDs
|
|
116
|
+
gdf_buildings: GeoDataFrame containing building information with columns:
|
|
117
|
+
'building_id', 'surface_material', 'window_ratio'
|
|
118
|
+
material_id_dict: Dictionary mapping material names to their IDs (optional)
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Modified voxelcity_grid
|
|
122
|
+
"""
|
|
123
|
+
voxelcity_grid = voxelcity_grid_ori.copy()
|
|
124
|
+
if material_id_dict == None:
|
|
125
|
+
material_id_dict = get_material_dict()
|
|
126
|
+
|
|
127
|
+
for index, row in gdf_buildings.iterrows():
|
|
128
|
+
# Access properties
|
|
129
|
+
osmid = row['building_id']
|
|
130
|
+
surface_material = row['surface_material']
|
|
131
|
+
window_ratio = row['window_ratio']
|
|
132
|
+
if surface_material is None:
|
|
133
|
+
surface_material = 'unknown'
|
|
134
|
+
set_building_material_by_id(voxelcity_grid, building_id_grid, osmid,
|
|
135
|
+
material_id_dict[surface_material],
|
|
136
|
+
window_ratio=window_ratio,
|
|
137
|
+
glass_id=material_id_dict['glass'])
|
|
138
|
+
|
|
139
|
+
return voxelcity_grid
|