voxcity 0.6.6__tar.gz → 0.6.8__tar.gz
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-0.6.6 → voxcity-0.6.8}/PKG-INFO +1 -1
- {voxcity-0.6.6 → voxcity-0.6.8}/pyproject.toml +1 -1
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/exporter/cityles.py +17 -7
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/geoprocessor/grid.py +45 -33
- {voxcity-0.6.6 → voxcity-0.6.8}/AUTHORS.rst +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/LICENSE +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/README.md +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/__init__.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/downloader/__init__.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/downloader/citygml.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/downloader/eubucco.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/downloader/gee.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/downloader/mbfp.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/downloader/oemj.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/downloader/osm.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/downloader/overture.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/downloader/utils.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/exporter/__init__.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/exporter/envimet.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/exporter/magicavoxel.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/exporter/obj.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/generator.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/geoprocessor/__init__.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/geoprocessor/draw.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/geoprocessor/mesh.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/geoprocessor/network.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/geoprocessor/polygon.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/geoprocessor/utils.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/simulator/__init__.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/simulator/solar.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/simulator/utils.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/simulator/view.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/utils/__init__.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/utils/lc.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/utils/material.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/utils/visualization.py +0 -0
- {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/utils/weather.py +0 -0
|
@@ -315,12 +315,14 @@ def export_dem(dem_grid, output_path):
|
|
|
315
315
|
# CityLES uses 1-based indexing
|
|
316
316
|
i_1based = i + 1
|
|
317
317
|
j_1based = j + 1
|
|
318
|
-
elevation = dem_grid[j, i]
|
|
319
|
-
|
|
318
|
+
elevation = float(dem_grid[j, i])
|
|
319
|
+
# Clamp negative elevations to 0.0 meters
|
|
320
|
+
if elevation < 0.0:
|
|
321
|
+
elevation = 0.0
|
|
320
322
|
f.write(f"{i_1based} {j_1based} {elevation:.1f}\n")
|
|
321
323
|
|
|
322
324
|
|
|
323
|
-
def export_vmap(canopy_height_grid, output_path, tree_base_ratio=0.3, tree_type='default'):
|
|
325
|
+
def export_vmap(canopy_height_grid, output_path, tree_base_ratio=0.3, tree_type='default', building_height_grid=None):
|
|
324
326
|
"""
|
|
325
327
|
Export vmap.txt file for CityLES
|
|
326
328
|
|
|
@@ -340,8 +342,14 @@ def export_vmap(canopy_height_grid, output_path, tree_base_ratio=0.3, tree_type=
|
|
|
340
342
|
ny, nx = canopy_height_grid.shape
|
|
341
343
|
tree_code = TREE_TYPE_MAPPING.get(tree_type, TREE_TYPE_MAPPING['default'])
|
|
342
344
|
|
|
345
|
+
# If building heights are provided, remove trees where buildings exist
|
|
346
|
+
if building_height_grid is not None:
|
|
347
|
+
effective_canopy = np.where(building_height_grid > 0, 0.0, canopy_height_grid)
|
|
348
|
+
else:
|
|
349
|
+
effective_canopy = canopy_height_grid
|
|
350
|
+
|
|
343
351
|
# Count only cells with canopy height > 0
|
|
344
|
-
vegetation_mask =
|
|
352
|
+
vegetation_mask = effective_canopy > 0
|
|
345
353
|
n_trees = int(np.count_nonzero(vegetation_mask))
|
|
346
354
|
|
|
347
355
|
with open(filename, 'w') as f:
|
|
@@ -354,7 +362,7 @@ def export_vmap(canopy_height_grid, output_path, tree_base_ratio=0.3, tree_type=
|
|
|
354
362
|
# CityLES uses 1-based indexing
|
|
355
363
|
i_1based = i + 1
|
|
356
364
|
j_1based = j + 1
|
|
357
|
-
total_height = float(
|
|
365
|
+
total_height = float(effective_canopy[j, i])
|
|
358
366
|
lower_height = total_height * tree_base_ratio
|
|
359
367
|
upper_height = total_height
|
|
360
368
|
# Format: i j lower_height upper_height tree_type
|
|
@@ -465,7 +473,7 @@ def export_cityles(building_height_grid, building_id_grid, canopy_height_grid,
|
|
|
465
473
|
export_dem(dem_grid, output_path)
|
|
466
474
|
|
|
467
475
|
print("\nExporting vmap.txt...")
|
|
468
|
-
export_vmap(canopy_height_grid, output_path, tree_base_ratio, tree_type)
|
|
476
|
+
export_vmap(canopy_height_grid, output_path, tree_base_ratio, tree_type, building_height_grid=building_height_grid)
|
|
469
477
|
|
|
470
478
|
print("\nExporting lonlat.txt...")
|
|
471
479
|
export_lonlat(rectangle_vertices, building_height_grid.shape, output_path)
|
|
@@ -483,7 +491,9 @@ def export_cityles(building_height_grid, building_id_grid, canopy_height_grid,
|
|
|
483
491
|
f.write(f"Tree type: {tree_type}\n")
|
|
484
492
|
f.write(f"Bounds: {rectangle_vertices}\n")
|
|
485
493
|
f.write(f"Buildings: {np.sum(building_height_grid > 0)}\n")
|
|
486
|
-
|
|
494
|
+
# Trees count after removing overlaps with buildings
|
|
495
|
+
trees_count = int(np.sum(np.where(building_height_grid > 0, 0.0, canopy_height_grid) > 0))
|
|
496
|
+
f.write(f"Trees: {trees_count}\n")
|
|
487
497
|
|
|
488
498
|
# Add land use value ranges
|
|
489
499
|
f.write(f"\nLand cover value range: {land_cover_grid.min()} - {land_cover_grid.max()}\n")
|
|
@@ -263,14 +263,19 @@ def calculate_grid_size(side_1, side_2, u_vec, v_vec, meshsize):
|
|
|
263
263
|
>>> mesh = 10 # Desired 10-unit mesh
|
|
264
264
|
>>> grid_size, adj_mesh = calculate_grid_size(side1, side2, u, v, mesh)
|
|
265
265
|
"""
|
|
266
|
-
# Calculate
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
266
|
+
# Calculate total side lengths in meters using the relationship between side vectors and unit vectors
|
|
267
|
+
# u_vec and v_vec represent degrees per meter along each side direction
|
|
268
|
+
dist_side_1_m = np.linalg.norm(side_1) / (np.linalg.norm(u_vec) + 1e-12)
|
|
269
|
+
dist_side_2_m = np.linalg.norm(side_2) / (np.linalg.norm(v_vec) + 1e-12)
|
|
270
|
+
|
|
271
|
+
# Calculate number of cells (nx along u, ny along v), rounding to nearest integer and ensuring at least 1
|
|
272
|
+
grid_size_0 = max(1, int(dist_side_1_m / meshsize + 0.5))
|
|
273
|
+
grid_size_1 = max(1, int(dist_side_2_m / meshsize + 0.5))
|
|
274
|
+
|
|
275
|
+
# Adjust mesh sizes (in meters) to exactly fit the sides with the calculated number of cells
|
|
276
|
+
adjusted_mesh_size_0 = dist_side_1_m / grid_size_0
|
|
277
|
+
adjusted_mesh_size_1 = dist_side_2_m / grid_size_1
|
|
278
|
+
|
|
274
279
|
return (grid_size_0, grid_size_1), (adjusted_mesh_size_0, adjusted_mesh_size_1)
|
|
275
280
|
|
|
276
281
|
def create_coordinate_mesh(origin, grid_size, adjusted_meshsize, u_vec, v_vec):
|
|
@@ -908,12 +913,19 @@ def _process_with_rasterio(filtered_gdf, grid_size, adjusted_meshsize, origin, u
|
|
|
908
913
|
Process buildings using fast rasterio-based approach.
|
|
909
914
|
Faster but less precise for overlapping footprints.
|
|
910
915
|
"""
|
|
911
|
-
# Set up transform for rasterio
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
916
|
+
# Set up transform for rasterio using rotated basis defined by u_vec and v_vec
|
|
917
|
+
# Step vectors in coordinate units (degrees) per cell
|
|
918
|
+
u_step = adjusted_meshsize[0] * u_vec
|
|
919
|
+
v_step = adjusted_meshsize[1] * v_vec
|
|
920
|
+
|
|
921
|
+
# Define the top-left corner so that row=0 is the northern edge
|
|
922
|
+
top_left = origin + grid_size[1] * v_step
|
|
923
|
+
|
|
924
|
+
# Affine transform mapping (col, row) -> (x, y)
|
|
925
|
+
# x = a*col + b*row + c ; y = d*col + e*row + f
|
|
926
|
+
# col increases along u_step; row increases southward, hence -v_step
|
|
927
|
+
transform = Affine(u_step[0], -v_step[0], top_left[0],
|
|
928
|
+
u_step[1], -v_step[1], top_left[1])
|
|
917
929
|
|
|
918
930
|
# Process buildings data
|
|
919
931
|
filtered_gdf = filtered_gdf.copy()
|
|
@@ -934,9 +946,9 @@ def _process_with_rasterio(filtered_gdf, grid_size, adjusted_meshsize, origin, u
|
|
|
934
946
|
regular_buildings = filtered_gdf[~filtered_gdf['is_inner']].copy()
|
|
935
947
|
regular_buildings = regular_buildings.sort_values('height', ascending=True, na_position='first')
|
|
936
948
|
|
|
937
|
-
#
|
|
938
|
-
|
|
939
|
-
|
|
949
|
+
# Temporary raster grids in rasterio's (rows=ny, cols=nx) order
|
|
950
|
+
height_raster = np.zeros((grid_size[1], grid_size[0]), dtype=np.float64)
|
|
951
|
+
id_raster = np.zeros((grid_size[1], grid_size[0]), dtype=np.float64)
|
|
940
952
|
|
|
941
953
|
# Vectorized rasterization
|
|
942
954
|
if len(regular_buildings) > 0:
|
|
@@ -949,9 +961,9 @@ def _process_with_rasterio(filtered_gdf, grid_size, adjusted_meshsize, origin, u
|
|
|
949
961
|
if pd.notna(height) and height > 0]
|
|
950
962
|
|
|
951
963
|
if height_shapes:
|
|
952
|
-
|
|
964
|
+
height_raster = features.rasterize(
|
|
953
965
|
height_shapes,
|
|
954
|
-
out_shape=grid_size,
|
|
966
|
+
out_shape=(grid_size[1], grid_size[0]),
|
|
955
967
|
transform=transform,
|
|
956
968
|
fill=0,
|
|
957
969
|
dtype=np.float64
|
|
@@ -962,9 +974,9 @@ def _process_with_rasterio(filtered_gdf, grid_size, adjusted_meshsize, origin, u
|
|
|
962
974
|
zip(valid_buildings.geometry, valid_buildings['id'])]
|
|
963
975
|
|
|
964
976
|
if id_shapes:
|
|
965
|
-
|
|
977
|
+
id_raster = features.rasterize(
|
|
966
978
|
id_shapes,
|
|
967
|
-
out_shape=grid_size,
|
|
979
|
+
out_shape=(grid_size[1], grid_size[0]),
|
|
968
980
|
transform=transform,
|
|
969
981
|
fill=0,
|
|
970
982
|
dtype=np.float64
|
|
@@ -977,17 +989,17 @@ def _process_with_rasterio(filtered_gdf, grid_size, adjusted_meshsize, origin, u
|
|
|
977
989
|
if inner_shapes:
|
|
978
990
|
inner_mask = features.rasterize(
|
|
979
991
|
inner_shapes,
|
|
980
|
-
out_shape=grid_size,
|
|
992
|
+
out_shape=(grid_size[1], grid_size[0]),
|
|
981
993
|
transform=transform,
|
|
982
994
|
fill=0,
|
|
983
995
|
dtype=np.uint8
|
|
984
996
|
)
|
|
985
|
-
|
|
986
|
-
|
|
997
|
+
height_raster[inner_mask > 0] = 0
|
|
998
|
+
id_raster[inner_mask > 0] = 0
|
|
987
999
|
|
|
988
1000
|
# Simplified min_height grid
|
|
989
1001
|
building_min_height_grid = np.empty(grid_size, dtype=object)
|
|
990
|
-
|
|
1002
|
+
min_heights_raster = np.zeros((grid_size[1], grid_size[0]), dtype=np.float64)
|
|
991
1003
|
|
|
992
1004
|
if len(regular_buildings) > 0:
|
|
993
1005
|
valid_buildings = regular_buildings[regular_buildings.geometry.is_valid].copy()
|
|
@@ -997,27 +1009,27 @@ def _process_with_rasterio(filtered_gdf, grid_size, adjusted_meshsize, origin, u
|
|
|
997
1009
|
if pd.notna(min_h)]
|
|
998
1010
|
|
|
999
1011
|
if min_height_shapes:
|
|
1000
|
-
|
|
1012
|
+
min_heights_raster = features.rasterize(
|
|
1001
1013
|
min_height_shapes,
|
|
1002
|
-
out_shape=grid_size,
|
|
1014
|
+
out_shape=(grid_size[1], grid_size[0]),
|
|
1003
1015
|
transform=transform,
|
|
1004
1016
|
fill=0,
|
|
1005
1017
|
dtype=np.float64
|
|
1006
1018
|
)
|
|
1007
1019
|
|
|
1008
1020
|
# Convert to list format (simplified)
|
|
1021
|
+
# Convert raster (ny, nx) to internal orientation (nx, ny) with north-up
|
|
1022
|
+
building_height_grid = np.flipud(height_raster).T
|
|
1023
|
+
building_id_grid = np.flipud(id_raster).T
|
|
1024
|
+
min_heights = np.flipud(min_heights_raster).T
|
|
1025
|
+
|
|
1009
1026
|
for i in range(grid_size[0]):
|
|
1010
1027
|
for j in range(grid_size[1]):
|
|
1011
1028
|
if building_height_grid[i, j] > 0:
|
|
1012
1029
|
building_min_height_grid[i, j] = [[min_heights[i, j], building_height_grid[i, j]]]
|
|
1013
1030
|
else:
|
|
1014
1031
|
building_min_height_grid[i, j] = []
|
|
1015
|
-
|
|
1016
|
-
# Fix north-south orientation by flipping grids
|
|
1017
|
-
building_height_grid = np.flipud(building_height_grid)
|
|
1018
|
-
building_id_grid = np.flipud(building_id_grid)
|
|
1019
|
-
building_min_height_grid = np.flipud(building_min_height_grid)
|
|
1020
|
-
|
|
1032
|
+
|
|
1021
1033
|
return building_height_grid, building_min_height_grid, building_id_grid, filtered_gdf
|
|
1022
1034
|
|
|
1023
1035
|
def create_building_height_grid_from_open_building_temporal_polygon(meshsize, rectangle_vertices, output_dir):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|