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

@@ -1542,18 +1542,35 @@ def create_dem_grid_from_gdf_polygon(terrain_gdf, mesh_size, polygon):
1542
1542
 
1543
1543
  # ------------------------------------------------------------------------
1544
1544
  # 4. Nearest-neighbor join from terrain_gdf to grid points
1545
+ # Use a projected CRS (UTM zone from polygon centroid) for robust distances
1545
1546
  # ------------------------------------------------------------------------
1546
1547
  if 'elevation' not in terrain_gdf.columns:
1547
1548
  raise ValueError("terrain_gdf must have an 'elevation' column.")
1548
1549
 
1549
- # Nearest spatial join (requires GeoPandas >= 0.10)
1550
- # This will assign each grid point the nearest terrain_gdf elevation.
1551
- grid_points_elev = gpd.sjoin_nearest(
1552
- grid_points,
1553
- terrain_gdf[['elevation', 'geometry']],
1554
- how="left",
1555
- distance_col="dist_to_terrain"
1556
- )
1550
+ try:
1551
+ centroid = poly.centroid
1552
+ lon_c, lat_c = float(centroid.x), float(centroid.y)
1553
+ zone = int((lon_c + 180.0) // 6) + 1
1554
+ epsg_proj = 32600 + zone if lat_c >= 0 else 32700 + zone
1555
+ terrain_proj = terrain_gdf.to_crs(epsg=epsg_proj)
1556
+ grid_points_proj = grid_points.to_crs(epsg=epsg_proj)
1557
+
1558
+ grid_points_elev = gpd.sjoin_nearest(
1559
+ grid_points_proj,
1560
+ terrain_proj[['elevation', 'geometry']],
1561
+ how="left",
1562
+ distance_col="dist_to_terrain"
1563
+ )
1564
+ # Keep original index mapping
1565
+ grid_points_elev.index = grid_points.index
1566
+ except Exception:
1567
+ # Fallback to geographic join if projection fails
1568
+ grid_points_elev = gpd.sjoin_nearest(
1569
+ grid_points,
1570
+ terrain_gdf[['elevation', 'geometry']],
1571
+ how="left",
1572
+ distance_col="dist_to_terrain"
1573
+ )
1557
1574
 
1558
1575
  # ------------------------------------------------------------------------
1559
1576
  # 5. Build the final 2D height array
@@ -2502,7 +2502,30 @@ def visualize_voxcity_plotly(
2502
2502
  if max_dim > max_dimension:
2503
2503
  stride = int(np.ceil(max_dim / max_dimension))
2504
2504
  if stride > 1:
2505
- vox = vox[::stride, ::stride, ::stride]
2505
+ # Surface-aware downsampling:
2506
+ # - Stride in X and Y for speed
2507
+ # - Along Z, choose the topmost non-zero within each stride window
2508
+ orig = voxel_array
2509
+ nx0, ny0, nz0 = orig.shape
2510
+ xs = orig[::stride, ::stride, :]
2511
+ nx_ds, ny_ds, _ = xs.shape
2512
+ nz_ds = int(np.ceil(nz0 / float(stride)))
2513
+ vox = np.zeros((nx_ds, ny_ds, nz_ds), dtype=orig.dtype)
2514
+ for k in range(nz_ds):
2515
+ z0w = k * stride
2516
+ z1w = min(z0w + stride, nz0)
2517
+ W = xs[:, :, z0w:z1w]
2518
+ if W.size == 0:
2519
+ continue
2520
+ nz_mask = (W != 0)
2521
+ has_any = nz_mask.any(axis=2)
2522
+ # find topmost index within window where any class exists
2523
+ rev_mask = nz_mask[:, :, ::-1]
2524
+ idx_rev = rev_mask.argmax(axis=2)
2525
+ real_idx = (W.shape[2] - 1) - idx_rev
2526
+ # gather labels at that topmost index
2527
+ gathered = np.take_along_axis(W, real_idx[..., None], axis=2).squeeze(-1)
2528
+ vox[:, :, k] = np.where(has_any, gathered, 0)
2506
2529
 
2507
2530
  nx, ny, nz = vox.shape
2508
2531
 
@@ -2534,19 +2557,54 @@ def visualize_voxcity_plotly(
2534
2557
  else:
2535
2558
  vox_dict = get_voxel_color_map(voxel_color_map)
2536
2559
 
2537
- def exposed_face_masks(occ):
2538
- # occ shape (nx, ny, nz)
2539
- p = np.pad(occ, ((0,1),(0,0),(0,0)), constant_values=False)
2560
+ def _bool_max_pool_3d(arr_bool, sx):
2561
+ """Max-pool a 3D boolean array with cubic window of size sx.
2562
+
2563
+ Pads with False so the output shape equals ceil(n/sx) per axis.
2564
+ """
2565
+ if isinstance(sx, (tuple, list, np.ndarray)):
2566
+ sx, sy, sz = int(sx[0]), int(sx[1]), int(sx[2])
2567
+ else:
2568
+ sy = sz = int(sx)
2569
+ sx = int(sx)
2570
+
2571
+ a = np.asarray(arr_bool, dtype=bool)
2572
+ nx, ny, nz = a.shape
2573
+ px = (sx - (nx % sx)) % sx
2574
+ py = (sy - (ny % sy)) % sy
2575
+ pz = (sz - (nz % sz)) % sz
2576
+ if px or py or pz:
2577
+ a = np.pad(a, ((0, px), (0, py), (0, pz)), constant_values=False)
2578
+ nxp, nyp, nzp = a.shape
2579
+ a = a.reshape(nxp // sx, sx, nyp // sy, sy, nzp // sz, sz)
2580
+ a = a.max(axis=1).max(axis=2).max(axis=4)
2581
+ return a
2582
+
2583
+ # Build an occluder mask that represents occupancy of any class, including those not rendered.
2584
+ # During downsampling, use max-pooling so omitted classes within a block still occlude neighbors.
2585
+ occluder = None
2586
+ if vox is not None:
2587
+ if stride > 1:
2588
+ try:
2589
+ occluder = _bool_max_pool_3d((voxel_array != 0), stride)
2590
+ except Exception:
2591
+ occluder = (vox != 0)
2592
+ else:
2593
+ occluder = (vox != 0)
2594
+
2595
+ def exposed_face_masks(occ, occ_any):
2596
+ # occ, occ_any shape (nx, ny, nz)
2597
+ p = np.pad(occ_any, ((0,1),(0,0),(0,0)), constant_values=False)
2540
2598
  posx = occ & (~p[1:,:,:])
2541
- p = np.pad(occ, ((1,0),(0,0),(0,0)), constant_values=False)
2599
+ p = np.pad(occ_any, ((1,0),(0,0),(0,0)), constant_values=False)
2542
2600
  negx = occ & (~p[:-1,:,:])
2543
- p = np.pad(occ, ((0,0),(0,1),(0,0)), constant_values=False)
2601
+ p = np.pad(occ_any, ((0,0),(0,1),(0,0)), constant_values=False)
2544
2602
  posy = occ & (~p[:,1:,:])
2545
- p = np.pad(occ, ((0,0),(1,0),(0,0)), constant_values=False)
2603
+ p = np.pad(occ_any, ((0,0),(1,0),(0,0)), constant_values=False)
2546
2604
  negy = occ & (~p[:,:-1,:])
2547
- p = np.pad(occ, ((0,0),(0,0),(0,1)), constant_values=False)
2605
+ p = np.pad(occ_any, ((0,0),(0,0),(0,1)), constant_values=False)
2548
2606
  posz = occ & (~p[:,:,1:])
2549
- p = np.pad(occ, ((0,0),(0,0),(1,0)), constant_values=False)
2607
+ p = np.pad(occ_any, ((0,0),(0,0),(1,0)), constant_values=False)
2550
2608
  negz = occ & (~p[:,:,:-1])
2551
2609
  return posx, negx, posy, negy, posz, negz
2552
2610
 
@@ -2639,7 +2697,7 @@ def visualize_voxcity_plotly(
2639
2697
  if not np.any(vox == cls):
2640
2698
  continue
2641
2699
  occ = (vox == cls)
2642
- posx, negx, posy, negy, posz, negz = exposed_face_masks(occ)
2700
+ posx, negx, posy, negy, posz, negz = exposed_face_masks(occ, occluder)
2643
2701
  color_rgb = vox_dict.get(int(cls), [128,128,128])
2644
2702
  add_faces(fig, posx, '+x', color_rgb)
2645
2703
  add_faces(fig, negx, '-x', color_rgb)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voxcity
3
- Version: 0.6.28
3
+ Version: 0.6.30
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
  License-File: AUTHORS.rst
@@ -69,13 +69,15 @@ Description-Content-Type: text/markdown
69
69
  <!-- [![License: CC BY-SA 4.0](https://licensebuttons.net/l/by-sa/4.0/80x15.png)](https://creativecommons.org/licenses/by-sa/4.0/) -->
70
70
 
71
71
  <p align="center">
72
- Tutorial preview: <a href="https://colab.research.google.com/drive/1Lofd3RawKMr6QuUsamGaF48u2MN0hfrP?usp=sharing">[Google Colab]</a> | Documentation: <a href="https://voxcity.readthedocs.io/en/latest">[Read the Docs]</a>
72
+ Tutorial preview: <a href="https://colab.research.google.com/drive/1Lofd3RawKMr6QuUsamGaF48u2MN0hfrP?usp=sharing">[Google Colab]</a> | Documentation: <a href="https://voxcity.readthedocs.io/en/latest">[Read the Docs]</a> | Video tutorial: <a href="https://youtu.be/qHusvKB07qk">[Watch on YouTube]</a>
73
73
  </p>
74
74
 
75
75
  <p align="center">
76
76
  <img src="https://raw.githubusercontent.com/kunifujiwara/VoxCity/main/images/logo.png" alt="Voxcity logo" width="550">
77
77
  </p>
78
78
 
79
+
80
+
79
81
  # VoxCity
80
82
 
81
83
  **voxcity** is a Python package that provides a seamless solution for grid-based 3D city model generation and urban simulation for cities worldwide. VoxCity's generator module automatically downloads building heights, tree canopy heights, land cover, and terrain elevation within a specified target area, and voxelizes buildings, trees, land cover, and terrain to generate an integrated voxel city model. The simulator module enables users to conduct environmental simulations, including solar radiation and view index analyses. Users can export the generated models using several file formats compatible with external software, such as ENVI-met (INX), Blender, and Rhino (OBJ). Try it out using the [Google Colab Demo](https://colab.research.google.com/drive/1Lofd3RawKMr6QuUsamGaF48u2MN0hfrP?usp=sharing) or your local environment. For detailed documentation, API reference, and tutorials, visit our [Read the Docs](https://voxcity.readthedocs.io/en/latest) page.
@@ -90,6 +92,21 @@ Description-Content-Type: text/markdown
90
92
  <img src="https://raw.githubusercontent.com/kunifujiwara/VoxCity/main/images/concept.png" alt="Conceptual Diagram of voxcity" width="800">
91
93
  </p>
92
94
 
95
+ ## Tutorial
96
+
97
+ - **Google Colab (interactive notebook)**: <a href="https://colab.research.google.com/drive/1Lofd3RawKMr6QuUsamGaF48u2MN0hfrP?usp=sharing">Open tutorial in Colab</a>
98
+ - **YouTube video (walkthrough)**: <a href="https://youtu.be/qHusvKB07qk">Watch on YouTube</a>
99
+
100
+ <p align="center">
101
+ <a href="https://youtu.be/qHusvKB07qk" title="Click to watch the VoxCity tutorial on YouTube">
102
+ <img src="images/youtube_thumbnail_play.png" alt="VoxCity Tutorial — Click to watch on YouTube" width="480">
103
+ </a>
104
+ </p>
105
+
106
+ <p align="center">
107
+ <em>Tutorial video by <a href="https://ual.sg/author/liang-xiucheng/">Xiucheng Liang</a></em>
108
+ </p>
109
+
93
110
 
94
111
  ## Key Features
95
112
 
@@ -524,6 +541,8 @@ Fujiwara K, Tsurumi R, Kiyono T, Fan Z, Liang X, Lei B, Yap W, Ito K, Biljecki F
524
541
 
525
542
  ## Credit
526
543
 
544
+ - Tutorial video by <a href="https://ual.sg/author/liang-xiucheng/">Xiucheng Liang</a>
545
+
527
546
  This package was created with [Cookiecutter](https://github.com/audreyr/cookiecutter) and the [`audreyr/cookiecutter-pypackage`](https://github.com/audreyr/cookiecutter-pypackage) project template.
528
547
 
529
548
  --------------------------------------------------------------------------------
@@ -5,7 +5,7 @@ voxcity/downloader/eubucco.py,sha256=ln1YNaaOgJfxNfCtVbYaMm775-bUvpAA_LDv60_i22w
5
5
  voxcity/downloader/gba.py,sha256=b-VmlVS8IzCR0OYfWgtlMpuZrB5_0M4EpG8BEBj6YEY,7184
6
6
  voxcity/downloader/gee.py,sha256=nvJvYqcSZyyontRtG2cFeb__ZJfeY4rRN1NBPORxLwQ,23557
7
7
  voxcity/downloader/mbfp.py,sha256=UXDVjsO0fnb0fSal9yqrSFEIBThnRmnutnp08kZTmCA,6595
8
- voxcity/downloader/oemj.py,sha256=iDacTpiqn7RAXuqyEtHP29m0Cycwta5sMy9-GdvX3Fg,12293
8
+ voxcity/downloader/oemj.py,sha256=SeMId9MvI-DnGyREpqu5-6D-xwRdMJdYIGcAPFD95rw,16432
9
9
  voxcity/downloader/osm.py,sha256=7Wo6lSodci7gALMKLQ_0ricmn0ZrfUK90vKYQ-ayU2A,46285
10
10
  voxcity/downloader/overture.py,sha256=hVxu-3Fmuu2E1tEzcDcNyU1cR-aE-6h6jkcxkuqN1-s,13343
11
11
  voxcity/downloader/utils.py,sha256=tz6wt4B9BhEOyvoF5OYXlr8rUd5cBEDedWL3j__oT70,3099
@@ -14,11 +14,11 @@ voxcity/exporter/cityles.py,sha256=Kfl2PAn4WquqCdjOlyPrHysxPLaudh8QfsoC6WAXlvs,1
14
14
  voxcity/exporter/envimet.py,sha256=Sh7s1JdQ6SgT_L2Xd_c4gtEGWK2hTS87bccaoIqik-s,31105
15
15
  voxcity/exporter/magicavoxel.py,sha256=SfGEgTZRlossKx3Xrv9d3iKSX-HmfQJEL9lZHgWMDX4,12782
16
16
  voxcity/exporter/netcdf.py,sha256=48rJ3wDsFhi9ANbElhMjXLxWMJoJzBt1gFbN0ekPp-A,7404
17
- voxcity/exporter/obj.py,sha256=aiaG8hMuggNnyCwsFZVhisnHm3BbUHi8SX4m9CyCYgc,59565
18
- voxcity/generator.py,sha256=uaiXDejqVX9CSSg25QK_f1pDztcOTYjDc_4w0XGTBFY,69550
17
+ voxcity/exporter/obj.py,sha256=iNszT4gurZZQurdl7hGWDyIhuZt50w1gZyMOt5XY2Kk,59888
18
+ voxcity/generator.py,sha256=zJZSLcldMvfbRk2UGXzY25DJmJi1L4KZP0glPuMRFgg,69477
19
19
  voxcity/geoprocessor/__init__.py,sha256=WYxcAQrjGucIvGHF0JTC6rONZzL3kCms1S2vpzS6KaU,127
20
20
  voxcity/geoprocessor/draw.py,sha256=AZMWq23wxxDJygNloCbVzWAAr1JO7nC94umf9LSxJ5o,49248
21
- voxcity/geoprocessor/grid.py,sha256=NmlQwl1nJpS7MduVtJeJCG-xBfVIwKTOip7pMm3RhsY,76722
21
+ voxcity/geoprocessor/grid.py,sha256=s64tCCY-pPiZjAWRWRJkBpb2FCkruOjq_nm1vD2unT8,77407
22
22
  voxcity/geoprocessor/mesh.py,sha256=A7uaCMWfm82KEoYPfQYpxv6xMtQKaU2PBVDfKTpngqg,32027
23
23
  voxcity/geoprocessor/network.py,sha256=YynqR0nq_NUra_cQ3Z_56KxfRia1b6-hIzGCj3QT-wE,25137
24
24
  voxcity/geoprocessor/polygon.py,sha256=DfzXf6R-qoWXEZv1z1aHCVfr-DCuCFw6lieQT5cNHPA,61188
@@ -30,10 +30,10 @@ voxcity/simulator/view.py,sha256=k3FoS6gsibR-eDrTHJivJSQfvN3Tg8R8eSTeMqd9ans,939
30
30
  voxcity/utils/__init__.py,sha256=Q-NYCqYnAAaF80KuNwpqIjbE7Ec3Gr4y_khMLIMhJrg,68
31
31
  voxcity/utils/lc.py,sha256=722Gz3lPbgAp0mmTZ-g-QKBbAnbxrcgaYwb1sa7q8Sk,16189
32
32
  voxcity/utils/material.py,sha256=H8K8Lq4wBL6dQtgj7esUW2U6wLCOTeOtelkTDJoRgMo,10007
33
- voxcity/utils/visualization.py,sha256=m9Tgyv47UrHvCx90E8s14Pry4Ys6YkvSI5ntfz87zn8,121406
33
+ voxcity/utils/visualization.py,sha256=aI4ipzYCTuIIzi6PU-Chog2cHxNFn4bveBZ3ljnZD9g,123876
34
34
  voxcity/utils/weather.py,sha256=cb6ZooL42Hc4214OtFiJ78cCgWYM6VE-DU8S3e-urRg,48449
35
- voxcity-0.6.28.dist-info/licenses/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
36
- voxcity-0.6.28.dist-info/licenses/LICENSE,sha256=s_jE1Df1nTPL4A_5GCGic5Zwex0CVaPKcAmSilxJPPE,1089
37
- voxcity-0.6.28.dist-info/METADATA,sha256=I_17a_1ggPLiowMqpolU0Y5CF4spJwTlR_H6f4XfaFk,26227
38
- voxcity-0.6.28.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
39
- voxcity-0.6.28.dist-info/RECORD,,
35
+ voxcity-0.6.30.dist-info/licenses/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
36
+ voxcity-0.6.30.dist-info/licenses/LICENSE,sha256=s_jE1Df1nTPL4A_5GCGic5Zwex0CVaPKcAmSilxJPPE,1089
37
+ voxcity-0.6.30.dist-info/METADATA,sha256=6ns5bThD5uMWC05L8u2s4ZEJ_m5XTMEGQsrsmckA-B8,27039
38
+ voxcity-0.6.30.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
39
+ voxcity-0.6.30.dist-info/RECORD,,