voxcity 0.6.5__py3-none-any.whl → 0.6.7__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.

@@ -190,7 +190,7 @@ def _build_index_to_cityles_map(land_cover_source):
190
190
 
191
191
 
192
192
  def export_topog(building_height_grid, building_id_grid, output_path,
193
- building_material='default'):
193
+ building_material='default', cityles_landuse_grid=None):
194
194
  """
195
195
  Export topog.txt file for CityLES
196
196
 
@@ -226,8 +226,17 @@ def export_topog(building_height_grid, building_id_grid, output_path,
226
226
  i_1based = i + 1
227
227
  j_1based = j + 1
228
228
  height = float(building_height_grid[j, i])
229
+ # Decide material code per cell
230
+ if cityles_landuse_grid is not None:
231
+ cell_lu = int(cityles_landuse_grid[j, i])
232
+ material_code_cell = cell_lu + 100
233
+ else:
234
+ if height > 0:
235
+ material_code_cell = material_code
236
+ else:
237
+ material_code_cell = 102
229
238
  # Format: i j height material_code depth1 depth2 changed_material
230
- f.write(f"{i_1based} {j_1based} {height:.1f} {material_code} 0.0 0.0 102\n")
239
+ f.write(f"{i_1based} {j_1based} {height:.1f} {material_code_cell} 0.0 0.0 102\n")
231
240
 
232
241
 
233
242
  def export_landuse(land_cover_grid, output_path, land_cover_source=None):
@@ -254,6 +263,8 @@ def export_landuse(land_cover_grid, output_path, land_cover_source=None):
254
263
 
255
264
  # Create mapping statistics
256
265
  mapping_stats = {}
266
+ # Prepare grid to return
267
+ cityles_landuse_grid = np.zeros((ny, nx), dtype=int)
257
268
 
258
269
  with open(filename, 'w') as f:
259
270
  # Write in row-major order (j varies first, then i)
@@ -263,6 +274,8 @@ def export_landuse(land_cover_grid, output_path, land_cover_source=None):
263
274
  cityles_code = index_to_code.get(idx, 4)
264
275
  f.write(f"{cityles_code}\n")
265
276
 
277
+ cityles_landuse_grid[j, i] = cityles_code
278
+
266
279
  # Track mapping statistics
267
280
  if idx not in mapping_stats:
268
281
  mapping_stats[idx] = {'cityles_code': cityles_code, 'count': 0}
@@ -277,6 +290,8 @@ def export_landuse(land_cover_grid, output_path, land_cover_source=None):
277
290
  class_name = class_names[idx] if 0 <= idx < len(class_names) else 'Unknown'
278
291
  print(f" {idx}: {class_name} -> CityLES {stats['cityles_code']}: "
279
292
  f"{stats['count']} cells ({percentage:.1f}%)")
293
+
294
+ return cityles_landuse_grid
280
295
 
281
296
 
282
297
  def export_dem(dem_grid, output_path):
@@ -434,11 +449,17 @@ def export_cityles(building_height_grid, building_id_grid, canopy_height_grid,
434
449
  print(f"Land cover source: {land_cover_source}")
435
450
 
436
451
  # Export individual files
437
- print("\nExporting topog.txt...")
438
- export_topog(building_height_grid, building_id_grid, output_path, building_material)
439
-
440
452
  print("\nExporting landuse.txt...")
441
- export_landuse(land_cover_grid, output_path, land_cover_source)
453
+ cityles_landuse_grid = export_landuse(land_cover_grid, output_path, land_cover_source)
454
+
455
+ print("\nExporting topog.txt...")
456
+ export_topog(
457
+ building_height_grid,
458
+ building_id_grid,
459
+ output_path,
460
+ building_material,
461
+ cityles_landuse_grid=cityles_landuse_grid,
462
+ )
442
463
 
443
464
  print("\nExporting dem.txt...")
444
465
  export_dem(dem_grid, output_path)
@@ -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):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: voxcity
3
- Version: 0.6.5
3
+ Version: 0.6.7
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
@@ -9,14 +9,14 @@ voxcity/downloader/osm.py,sha256=9nOVcVE50N76F5uquJbNIFr8Xajff4ac2Uj2oSGcFrc,425
9
9
  voxcity/downloader/overture.py,sha256=4YG2DMwUSSyZKUw_o8cGhMmAkPJon82aPqOFBvrre-Y,11987
10
10
  voxcity/downloader/utils.py,sha256=tz6wt4B9BhEOyvoF5OYXlr8rUd5cBEDedWL3j__oT70,3099
11
11
  voxcity/exporter/__init__.py,sha256=dvyWJ184Eik9tFc0VviGbzTQzZi7O0JNyrqi_n39pVI,94
12
- voxcity/exporter/cityles.py,sha256=Xe6VAt_vtfaMsfizXvc-WhzzemwweJz9iESJRvfVCpA,16665
12
+ voxcity/exporter/cityles.py,sha256=OhsGODC8bFq0MTGc0nBnqRyqnqg1JXL7UaL66a-CKsQ,17418
13
13
  voxcity/exporter/envimet.py,sha256=Sh7s1JdQ6SgT_L2Xd_c4gtEGWK2hTS87bccaoIqik-s,31105
14
14
  voxcity/exporter/magicavoxel.py,sha256=SfGEgTZRlossKx3Xrv9d3iKSX-HmfQJEL9lZHgWMDX4,12782
15
15
  voxcity/exporter/obj.py,sha256=h1_aInpemcsu96fSTwjKMqX2VZAFYbZbElWd4M1ogyI,27973
16
16
  voxcity/generator.py,sha256=J61i6-bvgOlNQWgxlkSvOZ7CLAjRgh_XRYwslWkKxVM,55756
17
17
  voxcity/geoprocessor/__init__.py,sha256=WYxcAQrjGucIvGHF0JTC6rONZzL3kCms1S2vpzS6KaU,127
18
18
  voxcity/geoprocessor/draw.py,sha256=avXQwbGQWG3ZPPI8mwy0YN0K_aG4NMBdXI0vDg7yad0,35837
19
- voxcity/geoprocessor/grid.py,sha256=wrlOsX8cD0W5xCnOS5IOHy8DNqDGTM1I2270eVs787c,70602
19
+ voxcity/geoprocessor/grid.py,sha256=D4sqoIGK2P1U8uuVQZ-447SD0Yrv6qS_zlmDtKoLDe8,71257
20
20
  voxcity/geoprocessor/mesh.py,sha256=A7uaCMWfm82KEoYPfQYpxv6xMtQKaU2PBVDfKTpngqg,32027
21
21
  voxcity/geoprocessor/network.py,sha256=YynqR0nq_NUra_cQ3Z_56KxfRia1b6-hIzGCj3QT-wE,25137
22
22
  voxcity/geoprocessor/polygon.py,sha256=DfzXf6R-qoWXEZv1z1aHCVfr-DCuCFw6lieQT5cNHPA,61188
@@ -30,8 +30,8 @@ voxcity/utils/lc.py,sha256=722Gz3lPbgAp0mmTZ-g-QKBbAnbxrcgaYwb1sa7q8Sk,16189
30
30
  voxcity/utils/material.py,sha256=H8K8Lq4wBL6dQtgj7esUW2U6wLCOTeOtelkTDJoRgMo,10007
31
31
  voxcity/utils/visualization.py,sha256=ZR9N-XKfydeSStO53IM2hGXyZJoeBiAyIMWw9Cb2MPM,116449
32
32
  voxcity/utils/weather.py,sha256=2Jtg-rIVJcsTtiKE-KuDnhIqS1-MSS16_zFRzj6zmu4,36435
33
- voxcity-0.6.5.dist-info/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
34
- voxcity-0.6.5.dist-info/LICENSE,sha256=s_jE1Df1nTPL4A_5GCGic5Zwex0CVaPKcAmSilxJPPE,1089
35
- voxcity-0.6.5.dist-info/METADATA,sha256=ebnpwEnZYPa0aSabyohTodRj8IlgoENztRWL-KG4DbU,26091
36
- voxcity-0.6.5.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
37
- voxcity-0.6.5.dist-info/RECORD,,
33
+ voxcity-0.6.7.dist-info/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
34
+ voxcity-0.6.7.dist-info/LICENSE,sha256=s_jE1Df1nTPL4A_5GCGic5Zwex0CVaPKcAmSilxJPPE,1089
35
+ voxcity-0.6.7.dist-info/METADATA,sha256=lICJOYiGbMdF2Lee90GLEP-KBz3S71jHDaHucZrYs7g,26091
36
+ voxcity-0.6.7.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
37
+ voxcity-0.6.7.dist-info/RECORD,,