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

@@ -2386,157 +2386,180 @@ def _rgb_tuple_to_plotly_color(rgb_tuple):
2386
2386
  return "rgb(128,128,128)"
2387
2387
 
2388
2388
 
2389
- def visualize_voxcity_plotly(
2389
+ def visualize_building_sim_results_plotly(
2390
2390
  voxel_array,
2391
2391
  meshsize,
2392
- classes=None,
2393
- voxel_color_map='default',
2394
- opacity=None,
2395
- max_dimension=128,
2396
- downsample=None,
2397
- title=None,
2398
- width=1000,
2399
- height=800,
2400
- show=True,
2401
- return_fig=False,
2392
+ building_sim_mesh,
2393
+ **kwargs
2402
2394
  ):
2403
2395
  """
2404
- Interactive 3D visualization of a voxcity voxel grid using Plotly.
2405
-
2406
- Parameters
2407
- ----------
2408
- voxel_array : np.ndarray
2409
- 3D array of voxel class IDs with shape (nx, ny, nz).
2410
- meshsize : float
2411
- Physical size of each voxel in meters.
2412
- classes : list[int], optional
2413
- Voxel class IDs to render. Defaults to prominent classes if None. Typical IDs:
2414
- - -3: Buildings
2415
- - -2: Vegetation
2416
- - -1: Underground
2417
- - 1..: Ground surface classes
2418
- voxel_color_map : str
2419
- Name of color scheme to fetch via get_voxel_color_map.
2420
- opacity : float or dict[int,float], optional
2421
- Single opacity for all classes (0-1) or per-class mapping.
2422
- max_dimension : int
2423
- If any axis exceeds this and downsample is None, auto downsample to keep
2424
- dimensions <= max_dimension.
2425
- downsample : int, optional
2426
- Explicit stride for all axes (e.g., 2 renders every other voxel).
2427
- title : str, optional
2396
+ Interactive Plotly visualization of voxels with an overlaid building simulation mesh.
2397
+
2398
+ This function reuses visualize_voxcity_plotly to render voxel cubes and adds a
2399
+ Plotly Mesh3d trace for a provided building simulation mesh (e.g., SVF, temperature).
2400
+
2401
+ Parameters (kwargs)
2402
+ -------------------
2403
+ classes : list[int] or None
2404
+ Classes to render for voxel cubes. Default: all non-zero classes present.
2405
+ voxel_color_map : str or dict
2406
+ Scheme name understood by get_voxel_color_map or explicit mapping {class_id: [R,G,B]}.
2407
+ downsample : int or None
2408
+ Stride for voxel cubes. 1 means no downsampling.
2409
+ cubes_opacity : float
2410
+ Opacity for voxel cubes (default 0.95 for buildings, 0.6 for others via function's per-class logic; here default 0.9).
2411
+ title : str
2428
2412
  Figure title.
2429
2413
  width, height : int
2430
- Figure size in pixels.
2431
- show : bool
2432
- Whether to display the figure (default True).
2433
- return_fig : bool
2434
- Whether to return the plotly Figure object.
2435
-
2436
- Notes
2437
- -----
2438
- - Uses one Isosurface trace per class with isomin/isomax around the class value.
2439
- - For large grids, consider downsampling for performance.
2414
+ Figure size.
2415
+ value_name : str
2416
+ Metadata field name on building_sim_mesh storing per-face values (default 'svf_values').
2417
+ colormap : str
2418
+ Matplotlib colormap name for the simulation values (default 'viridis').
2419
+ vmin, vmax : float or None
2420
+ Value range for color mapping. If None, computed from finite values.
2421
+ nan_color : str
2422
+ Color name for NaN values (default 'gray').
2423
+ building_opacity : float
2424
+ Opacity for the simulation mesh (default 1.0).
2425
+ shaded : bool
2426
+ If True, apply lighting-based shading to the simulation mesh. Default False (unlit colors).
2427
+ render_voxel_buildings : bool
2428
+ If True, also render voxel buildings (-3) from voxcity_grid. Default False (hide voxel buildings
2429
+ so only simulation mesh buildings are visible).
2430
+ show : bool, return_fig : bool
2431
+ Standard display controls.
2440
2432
  """
2441
- if voxel_array is None or getattr(voxel_array, 'ndim', 0) != 3:
2442
- raise ValueError("voxel_array must be a 3D numpy array (nx, ny, nz)")
2443
-
2444
- vox = voxel_array
2445
-
2446
- # Downsample for performance if requested or auto-needed
2447
- stride = 1
2448
- if downsample is not None and downsample > 1:
2449
- stride = int(downsample)
2450
- else:
2451
- nx, ny, nz = vox.shape
2452
- max_dim = max(nx, ny, nz)
2453
- if max_dim > max_dimension:
2454
- # ceil so that max_dim/stride <= max_dimension
2455
- stride = int(np.ceil(max_dim / max_dimension))
2456
-
2457
- if stride > 1:
2458
- vox = vox[::stride, ::stride, ::stride]
2459
-
2460
- nx, ny, nz = vox.shape
2461
- # Coordinate axes in meters
2462
- x = np.arange(nx, dtype=float) * meshsize * stride
2463
- y = np.arange(ny, dtype=float) * meshsize * stride
2464
- z = np.arange(nz, dtype=float) * meshsize * stride
2465
-
2466
- # Select default classes if not provided: render all non-zero classes present
2433
+ classes = kwargs.get('classes')
2434
+ voxel_color_map = kwargs.get('voxel_color_map', 'default')
2435
+ downsample = kwargs.get('downsample')
2436
+ title = kwargs.get('title', None)
2437
+ width = kwargs.get('width', 1000)
2438
+ height = kwargs.get('height', 800)
2439
+ cubes_opacity = kwargs.get('cubes_opacity', 0.9)
2440
+ show = kwargs.get('show', True)
2441
+ return_fig = kwargs.get('return_fig', False)
2442
+ render_voxel_buildings = kwargs.get('render_voxel_buildings', False)
2443
+
2444
+ # Determine classes for voxel cubes and exclude buildings (-3) by default
2467
2445
  if classes is None:
2468
- classes = np.unique(vox[vox != 0]).tolist()
2469
-
2470
- if not classes:
2471
- raise ValueError("No classes to visualize (voxel grid may be empty)")
2472
-
2473
- # Colors
2474
- vox_dict = get_voxel_color_map(voxel_color_map)
2475
- class_to_color = {}
2476
- for c in classes:
2477
- color_rgb = vox_dict.get(int(c), [128, 128, 128])
2478
- class_to_color[c] = _rgb_tuple_to_plotly_color(color_rgb)
2479
-
2480
- # Opacity per class
2481
- def get_opacity(c):
2482
- if isinstance(opacity, dict):
2483
- return float(opacity.get(c, 0.6))
2484
- if isinstance(opacity, (int, float)):
2485
- return float(opacity)
2486
- # defaults: buildings opaque, vegetation semi
2487
- return 0.95 if c == -3 else 0.6
2488
-
2489
- # Build figure
2490
- fig = go.Figure()
2491
-
2492
- # Precompute flat coordinates for isosurface
2493
- # Plotly Isosurface accepts 1D arrays for x, y, z defining positions
2494
- # combined with a 3D value grid.
2495
- X = np.broadcast_to(x[:, None, None], (nx, ny, nz)).ravel()
2496
- Y = np.broadcast_to(y[None, :, None], (nx, ny, nz)).ravel()
2497
- Z = np.broadcast_to(z[None, None, :], (nx, ny, nz)).ravel()
2498
-
2499
- for cls in classes:
2500
- # Skip if class not present
2501
- if not np.any(vox == cls):
2502
- continue
2503
-
2504
- # Colorscale with a single color
2505
- color = class_to_color.get(cls, "rgb(128,128,128)")
2506
- colorscale = [[0.0, color], [1.0, color]]
2507
-
2508
- # Use binary mask per class to ensure iso level lies strictly between 0 and 1
2509
- M = (vox == cls).astype(np.uint8).ravel()
2510
-
2511
- fig.add_trace(
2512
- go.Isosurface(
2513
- x=X,
2514
- y=Y,
2515
- z=Z,
2516
- value=M,
2517
- isomin=0.5,
2518
- isomax=0.5001,
2519
- surface_count=1,
2520
- caps=dict(x_show=False, y_show=False, z_show=False),
2521
- colorscale=colorscale,
2522
- showscale=False,
2523
- opacity=get_opacity(cls),
2524
- name=str(cls),
2446
+ classes_all = np.unique(voxel_array[voxel_array != 0]).tolist()
2447
+ else:
2448
+ classes_all = list(classes)
2449
+ classes_cubes = classes_all if render_voxel_buildings else [c for c in classes_all if int(c) != -3]
2450
+
2451
+ # Render voxel cubes background (or blank scene if nothing to render)
2452
+ if len(classes_cubes) > 0:
2453
+ fig = visualize_voxcity_plotly(
2454
+ voxel_array,
2455
+ meshsize,
2456
+ classes=classes_cubes,
2457
+ voxel_color_map=voxel_color_map,
2458
+ opacity=cubes_opacity,
2459
+ downsample=downsample,
2460
+ title=title or "Building Simulation (Plotly)",
2461
+ width=width,
2462
+ height=height,
2463
+ show=False,
2464
+ return_fig=True,
2465
+ )
2466
+ else:
2467
+ fig = go.Figure()
2468
+ fig.update_layout(
2469
+ title=title or "Building Simulation (Plotly)",
2470
+ width=width,
2471
+ height=height,
2472
+ scene=dict(
2473
+ xaxis_title="X (m)",
2474
+ yaxis_title="Y (m)",
2475
+ zaxis_title="Z (m)",
2476
+ aspectmode="data",
2477
+ camera=dict(eye=dict(x=1.6, y=1.6, z=1.0)),
2525
2478
  )
2526
2479
  )
2527
2480
 
2528
- # Layout
2529
- fig.update_layout(
2530
- title=title or "VoxCity 3D (Plotly)",
2531
- width=width,
2532
- height=height,
2533
- scene=dict(
2534
- xaxis_title="X (m)",
2535
- yaxis_title="Y (m)",
2536
- zaxis_title="Z (m)",
2537
- aspectmode="data",
2538
- ),
2539
- legend=dict(itemsizing='constant')
2481
+ # Nothing to overlay
2482
+ if building_sim_mesh is None or getattr(building_sim_mesh, 'vertices', None) is None:
2483
+ if show:
2484
+ fig.show()
2485
+ if return_fig:
2486
+ return fig
2487
+ return None
2488
+
2489
+ # Extract geometry
2490
+ V = np.asarray(building_sim_mesh.vertices)
2491
+ F = np.asarray(building_sim_mesh.faces)
2492
+ values = None
2493
+ value_name = kwargs.get('value_name', 'svf_values')
2494
+ if hasattr(building_sim_mesh, 'metadata') and isinstance(building_sim_mesh.metadata, dict):
2495
+ values = building_sim_mesh.metadata.get(value_name)
2496
+ if values is not None:
2497
+ values = np.asarray(values)
2498
+
2499
+ # Compute per-face scalar to avoid color interpolation across edges
2500
+ face_vals = None
2501
+ if values is not None and values.size == len(F):
2502
+ # Already per-face
2503
+ face_vals = values.astype(float)
2504
+ elif values is not None and values.size == len(V):
2505
+ # Average the three vertex values per face
2506
+ vals_v = values.astype(float)
2507
+ face_vals = np.nanmean(vals_v[F], axis=1)
2508
+
2509
+ # Map to colors
2510
+ cmap_name = kwargs.get('colormap', 'viridis')
2511
+ vmin = kwargs.get('vmin')
2512
+ vmax = kwargs.get('vmax')
2513
+ nan_color = kwargs.get('nan_color', 'gray')
2514
+ building_opacity = kwargs.get('building_opacity', 1.0)
2515
+
2516
+ facecolor = None
2517
+ if face_vals is not None:
2518
+ # Compute range
2519
+ finite = np.isfinite(face_vals)
2520
+ if vmin is None:
2521
+ vmin = float(np.nanmin(face_vals[finite])) if np.any(finite) else 0.0
2522
+ if vmax is None:
2523
+ vmax = float(np.nanmax(face_vals[finite])) if np.any(finite) else 1.0
2524
+ norm = mcolors.Normalize(vmin=vmin, vmax=vmax)
2525
+ cmap = cm.get_cmap(cmap_name)
2526
+ # Colors per face (constant on each triangle)
2527
+ colors_rgba = np.zeros((len(F), 4), dtype=float)
2528
+ colors_rgba[finite] = cmap(norm(face_vals[finite]))
2529
+ nan_rgba = np.array(mcolors.to_rgba(nan_color))
2530
+ colors_rgba[~finite] = nan_rgba
2531
+ facecolor = [
2532
+ f"rgb({int(255*c[0])},{int(255*c[1])},{int(255*c[2])})" for c in colors_rgba
2533
+ ]
2534
+
2535
+ # Lighting (disable shading by default for true color rendering)
2536
+ shaded = kwargs.get('shaded', False)
2537
+ if shaded:
2538
+ lighting = dict(ambient=0.35, diffuse=1.0, specular=0.4, roughness=0.5, fresnel=0.1)
2539
+ flat = False
2540
+ else:
2541
+ # Unlit: make colors independent of lighting
2542
+ lighting = dict(ambient=1.0, diffuse=0.0, specular=0.0, roughness=0.0, fresnel=0.0)
2543
+ flat = False
2544
+ cx = float((V[:,0].min() + V[:,0].max()) * 0.5)
2545
+ cy = float((V[:,1].min() + V[:,1].max()) * 0.5)
2546
+ cz = float((V[:,2].min() + V[:,2].max()) * 0.5)
2547
+ lx = cx + (V[:,0].max() - V[:,0].min() + meshsize) * 0.9
2548
+ ly = cy + (V[:,1].max() - V[:,1].min() + meshsize) * 0.6
2549
+ lz = cz + (V[:,2].max() - V[:,2].min() + meshsize) * 1.4
2550
+
2551
+ fig.add_trace(
2552
+ go.Mesh3d(
2553
+ x=V[:,0], y=V[:,1], z=V[:,2],
2554
+ i=F[:,0], j=F[:,1], k=F[:,2],
2555
+ facecolor=facecolor if facecolor is not None else None,
2556
+ color=None if facecolor is not None else 'rgb(200,200,200)',
2557
+ opacity=float(building_opacity),
2558
+ flatshading=flat,
2559
+ lighting=lighting,
2560
+ lightposition=dict(x=lx, y=ly, z=lz),
2561
+ name=value_name if facecolor is not None else 'building_mesh'
2562
+ )
2540
2563
  )
2541
2564
 
2542
2565
  if show:
@@ -2545,8 +2568,7 @@ def visualize_voxcity_plotly(
2545
2568
  return fig
2546
2569
  return None
2547
2570
 
2548
-
2549
- def visualize_voxcity_plotly_voxels(
2571
+ def visualize_voxcity_plotly(
2550
2572
  voxel_array,
2551
2573
  meshsize,
2552
2574
  classes=None,
@@ -2565,6 +2587,8 @@ def visualize_voxcity_plotly_voxels(
2565
2587
  using Plotly Mesh3d. One Mesh3d trace per class.
2566
2588
 
2567
2589
  Parameters are similar to visualize_voxcity_plotly, but rendering is via exact cubes.
2590
+ voxel_color_map may be either a scheme name (str) understood by get_voxel_color_map,
2591
+ or a dict mapping class_id -> [R, G, B] (0-255).
2568
2592
  """
2569
2593
  if voxel_array is None or getattr(voxel_array, 'ndim', 0) != 3:
2570
2594
  raise ValueError("voxel_array must be a 3D numpy array (nx, ny, nz)")
@@ -2572,10 +2596,11 @@ def visualize_voxcity_plotly_voxels(
2572
2596
  vox = voxel_array
2573
2597
 
2574
2598
  # Downsample for performance if requested or auto-needed
2575
- stride = 1
2576
- if downsample is not None and downsample > 1:
2577
- stride = int(downsample)
2599
+ # Respect explicit downsample even when it is 1 (no auto-downsample)
2600
+ if downsample is not None:
2601
+ stride = max(1, int(downsample))
2578
2602
  else:
2603
+ stride = 1
2579
2604
  nx, ny, nz = vox.shape
2580
2605
  max_dim = max(nx, ny, nz)
2581
2606
  if max_dim > max_dimension:
@@ -2600,7 +2625,11 @@ def visualize_voxcity_plotly_voxels(
2600
2625
  if not classes:
2601
2626
  raise ValueError("No classes to visualize (voxel grid may be empty)")
2602
2627
 
2603
- vox_dict = get_voxel_color_map(voxel_color_map)
2628
+ # Resolve color map: accept scheme name or explicit dict
2629
+ if isinstance(voxel_color_map, dict):
2630
+ vox_dict = voxel_color_map
2631
+ else:
2632
+ vox_dict = get_voxel_color_map(voxel_color_map)
2604
2633
 
2605
2634
  def exposed_face_masks(occ):
2606
2635
  # occ shape (nx, ny, nz)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voxcity
3
- Version: 0.6.21
3
+ Version: 0.6.23
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
@@ -29,10 +29,10 @@ voxcity/simulator/view.py,sha256=k3FoS6gsibR-eDrTHJivJSQfvN3Tg8R8eSTeMqd9ans,939
29
29
  voxcity/utils/__init__.py,sha256=Q-NYCqYnAAaF80KuNwpqIjbE7Ec3Gr4y_khMLIMhJrg,68
30
30
  voxcity/utils/lc.py,sha256=722Gz3lPbgAp0mmTZ-g-QKBbAnbxrcgaYwb1sa7q8Sk,16189
31
31
  voxcity/utils/material.py,sha256=H8K8Lq4wBL6dQtgj7esUW2U6wLCOTeOtelkTDJoRgMo,10007
32
- voxcity/utils/visualization.py,sha256=rPPxZeoMvzDblZe6A2vgRkX1PHXVdft6l36nLOuSRkU,114774
32
+ voxcity/utils/visualization.py,sha256=LGzDITOO-57abMA7Vx4wJLsdUP7el3GZZUbTEpx3TA0,117192
33
33
  voxcity/utils/weather.py,sha256=cb6ZooL42Hc4214OtFiJ78cCgWYM6VE-DU8S3e-urRg,48449
34
- voxcity-0.6.21.dist-info/licenses/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
35
- voxcity-0.6.21.dist-info/licenses/LICENSE,sha256=s_jE1Df1nTPL4A_5GCGic5Zwex0CVaPKcAmSilxJPPE,1089
36
- voxcity-0.6.21.dist-info/METADATA,sha256=mt2V_ZeRFL7UUBuEeUUUc_Br_fhvB9pPtbXVTaTvobY,26227
37
- voxcity-0.6.21.dist-info/WHEEL,sha256=M5asmiAlL6HEcOq52Yi5mmk9KmTVjY2RDPtO4p9DMrc,88
38
- voxcity-0.6.21.dist-info/RECORD,,
34
+ voxcity-0.6.23.dist-info/licenses/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
35
+ voxcity-0.6.23.dist-info/licenses/LICENSE,sha256=s_jE1Df1nTPL4A_5GCGic5Zwex0CVaPKcAmSilxJPPE,1089
36
+ voxcity-0.6.23.dist-info/METADATA,sha256=BNpPaAKeQYDz-pj4KA0keNf2MtDT0lyrJn9xkelLj80,26227
37
+ voxcity-0.6.23.dist-info/WHEEL,sha256=M5asmiAlL6HEcOq52Yi5mmk9KmTVjY2RDPtO4p9DMrc,88
38
+ voxcity-0.6.23.dist-info/RECORD,,