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.

Files changed (37) hide show
  1. {voxcity-0.6.6 → voxcity-0.6.8}/PKG-INFO +1 -1
  2. {voxcity-0.6.6 → voxcity-0.6.8}/pyproject.toml +1 -1
  3. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/exporter/cityles.py +17 -7
  4. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/geoprocessor/grid.py +45 -33
  5. {voxcity-0.6.6 → voxcity-0.6.8}/AUTHORS.rst +0 -0
  6. {voxcity-0.6.6 → voxcity-0.6.8}/LICENSE +0 -0
  7. {voxcity-0.6.6 → voxcity-0.6.8}/README.md +0 -0
  8. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/__init__.py +0 -0
  9. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/downloader/__init__.py +0 -0
  10. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/downloader/citygml.py +0 -0
  11. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/downloader/eubucco.py +0 -0
  12. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/downloader/gee.py +0 -0
  13. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/downloader/mbfp.py +0 -0
  14. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/downloader/oemj.py +0 -0
  15. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/downloader/osm.py +0 -0
  16. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/downloader/overture.py +0 -0
  17. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/downloader/utils.py +0 -0
  18. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/exporter/__init__.py +0 -0
  19. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/exporter/envimet.py +0 -0
  20. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/exporter/magicavoxel.py +0 -0
  21. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/exporter/obj.py +0 -0
  22. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/generator.py +0 -0
  23. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/geoprocessor/__init__.py +0 -0
  24. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/geoprocessor/draw.py +0 -0
  25. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/geoprocessor/mesh.py +0 -0
  26. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/geoprocessor/network.py +0 -0
  27. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/geoprocessor/polygon.py +0 -0
  28. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/geoprocessor/utils.py +0 -0
  29. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/simulator/__init__.py +0 -0
  30. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/simulator/solar.py +0 -0
  31. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/simulator/utils.py +0 -0
  32. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/simulator/view.py +0 -0
  33. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/utils/__init__.py +0 -0
  34. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/utils/lc.py +0 -0
  35. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/utils/material.py +0 -0
  36. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/utils/visualization.py +0 -0
  37. {voxcity-0.6.6 → voxcity-0.6.8}/src/voxcity/utils/weather.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: voxcity
3
- Version: 0.6.6
3
+ Version: 0.6.8
4
4
  Summary: voxcity is an easy and one-stop tool to output 3d city models for microclimate simulation by integrating multiple geospatial open-data
5
5
  License: MIT
6
6
  Author: Kunihiko Fujiwara
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "voxcity"
3
- version = "0.6.6"
3
+ version = "0.6.8"
4
4
  description = "voxcity is an easy and one-stop tool to output 3d city models for microclimate simulation by integrating multiple geospatial open-data"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -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 = canopy_height_grid > 0
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(canopy_height_grid[j, i])
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
- f.write(f"Trees: {np.sum(canopy_height_grid > 0)}\n")
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 number of cells needed in each direction, rounding to nearest integer
267
- grid_size_0 = int(np.linalg.norm(side_1) / np.linalg.norm(meshsize * u_vec) + 0.5)
268
- grid_size_1 = int(np.linalg.norm(side_2) / np.linalg.norm(meshsize * v_vec) + 0.5)
269
-
270
- # Adjust mesh sizes to exactly fit the desired area with the calculated number of cells
271
- adjusted_mesh_size_0 = meshsize * np.linalg.norm(meshsize * u_vec) * grid_size_0 / np.linalg.norm(side_1)
272
- adjusted_mesh_size_1 = meshsize * np.linalg.norm(meshsize * v_vec) * grid_size_1 / np.linalg.norm(side_2)
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
- min_x, min_y = origin[0], origin[1]
913
- max_corner = origin + grid_size[0] * adjusted_meshsize[0] * u_vec + grid_size[1] * adjusted_meshsize[1] * v_vec
914
- max_x, max_y = max_corner[0], max_corner[1]
915
-
916
- transform = from_bounds(min_x, min_y, max_x, max_y, grid_size[0], grid_size[1])
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
- # Initialize grids
938
- building_height_grid = np.zeros(grid_size, dtype=np.float64)
939
- building_id_grid = np.zeros(grid_size, dtype=np.float64)
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
- building_height_grid = features.rasterize(
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
- building_id_grid = features.rasterize(
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
- building_height_grid[inner_mask > 0] = 0
986
- building_id_grid[inner_mask > 0] = 0
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
- min_heights = np.zeros(grid_size, dtype=np.float64)
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
- min_heights = features.rasterize(
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