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

@@ -1096,10 +1096,7 @@ def draw_additional_trees(tree_gdf=None, initial_center=None, zoom=17, rectangle
1096
1096
 
1097
1097
  tree_layers[next_tree_id] = circle
1098
1098
 
1099
- with status_output:
1100
- print(f"Tree {next_tree_id} added at (lon, lat)=({lon:.6f}, {lat:.6f})")
1101
- print(f"Top: {new_row['top_height']} m, Bottom: {new_row['bottom_height']} m, Crown: {new_row['crown_diameter']} m")
1102
- print(f"Total trees: {len(updated_trees)}")
1099
+ # Suppress status prints on add
1103
1100
  else:
1104
1101
  # Remove mode: find the nearest tree within its crown radius + 5m
1105
1102
  candidate_id = None
@@ -1127,12 +1124,10 @@ def draw_additional_trees(tree_gdf=None, initial_center=None, zoom=17, rectangle
1127
1124
  # Remove from gdf
1128
1125
  updated_trees.drop(index=candidate_idx, inplace=True)
1129
1126
  updated_trees.reset_index(drop=True, inplace=True)
1130
- with status_output:
1131
- print(f"Removed tree {candidate_id} (distance {candidate_dist:.2f} m)")
1132
- print(f"Total trees: {len(updated_trees)}")
1127
+ # Suppress status prints on remove
1133
1128
  else:
1134
- with status_output:
1135
- print("No tree near the clicked location to remove")
1129
+ # Suppress status prints when nothing to remove
1130
+ pass
1136
1131
  elif kwargs.get('type') == 'mousemove':
1137
1132
  lat, lon = kwargs.get('coordinates', (None, None))
1138
1133
  if lat is None or lon is None:
@@ -1565,4 +1565,169 @@ def create_dem_grid_from_gdf_polygon(terrain_gdf, mesh_size, polygon):
1565
1565
  # By default, row=0 is the "north/top" row, row=-1 is "south/bottom" row.
1566
1566
  # If you prefer the bottom row as index=0, you'd do: np.flipud(dem_grid)
1567
1567
 
1568
- return np.flipud(dem_grid)
1568
+ return np.flipud(dem_grid)
1569
+
1570
+ def create_canopy_grids_from_tree_gdf(tree_gdf, meshsize, rectangle_vertices):
1571
+ """
1572
+ Create canopy top and bottom height grids from a tree GeoDataFrame.
1573
+
1574
+ Assumptions:
1575
+ - Each tree is a point with attributes: 'top_height', 'bottom_height', 'crown_diameter'.
1576
+ - The crown is modeled as a solid of revolution with an ellipsoidal vertical profile.
1577
+ For a tree with top H_t, bottom H_b and crown radius R = crown_diameter/2,
1578
+ at a horizontal distance r (r <= R) from the tree center:
1579
+ z_top(r) = z0 + a * sqrt(1 - (r/R)^2)
1580
+ z_bot(r) = z0 - a * sqrt(1 - (r/R)^2)
1581
+ where a = (H_t - H_b)/2 and z0 = (H_t + H_b)/2.
1582
+
1583
+ The function outputs two grids (shape: (nx, ny) consistent with other grid functions):
1584
+ - canopy_height_grid: maximum canopy top height per cell across trees
1585
+ - canopy_bottom_height_grid: maximum canopy bottom height per cell across trees
1586
+
1587
+ Args:
1588
+ tree_gdf (geopandas.GeoDataFrame): Tree points with required columns.
1589
+ meshsize (float): Grid spacing in meters.
1590
+ rectangle_vertices (list[tuple]): 4 vertices [(lon,lat), ...] defining the grid rectangle.
1591
+
1592
+ Returns:
1593
+ tuple[np.ndarray, np.ndarray]: (canopy_height_grid, canopy_bottom_height_grid)
1594
+ """
1595
+
1596
+ # Validate and prepare input GeoDataFrame
1597
+ if tree_gdf is None or len(tree_gdf) == 0:
1598
+ return np.array([]), np.array([])
1599
+
1600
+ required_cols = ['top_height', 'bottom_height', 'crown_diameter', 'geometry']
1601
+ for col in required_cols:
1602
+ if col not in tree_gdf.columns:
1603
+ raise ValueError(f"tree_gdf must contain '{col}' column.")
1604
+
1605
+ # Ensure CRS is WGS84
1606
+ if tree_gdf.crs is None:
1607
+ warnings.warn("tree_gdf has no CRS. Assuming EPSG:4326.")
1608
+ tree_gdf = tree_gdf.set_crs(epsg=4326)
1609
+ elif tree_gdf.crs.to_epsg() != 4326:
1610
+ tree_gdf = tree_gdf.to_crs(epsg=4326)
1611
+
1612
+ # Grid setup consistent with building/land cover grid functions
1613
+ geod = initialize_geod()
1614
+ vertex_0, vertex_1, vertex_3 = rectangle_vertices[0], rectangle_vertices[1], rectangle_vertices[3]
1615
+
1616
+ dist_side_1 = calculate_distance(geod, vertex_0[0], vertex_0[1], vertex_1[0], vertex_1[1])
1617
+ dist_side_2 = calculate_distance(geod, vertex_0[0], vertex_0[1], vertex_3[0], vertex_3[1])
1618
+
1619
+ side_1 = np.array(vertex_1) - np.array(vertex_0)
1620
+ side_2 = np.array(vertex_3) - np.array(vertex_0)
1621
+ u_vec = normalize_to_one_meter(side_1, dist_side_1)
1622
+ v_vec = normalize_to_one_meter(side_2, dist_side_2)
1623
+
1624
+ origin = np.array(rectangle_vertices[0])
1625
+ grid_size, adjusted_meshsize = calculate_grid_size(side_1, side_2, u_vec, v_vec, meshsize)
1626
+
1627
+ nx, ny = grid_size[0], grid_size[1]
1628
+
1629
+ # Precompute metric cell-center coordinates in grid's (u,v) metric space (meters from origin)
1630
+ i_centers_m = (np.arange(nx) + 0.5) * adjusted_meshsize[0]
1631
+ j_centers_m = (np.arange(ny) + 0.5) * adjusted_meshsize[1]
1632
+
1633
+ # Initialize output grids
1634
+ canopy_top = np.zeros((nx, ny), dtype=float)
1635
+ canopy_bottom = np.zeros((nx, ny), dtype=float)
1636
+
1637
+ # Matrix to convert lon/lat offsets to metric (u,v) using u_vec, v_vec
1638
+ # delta_lonlat ≈ [u_vec v_vec] @ [alpha; beta], where alpha/beta are meters along u/v
1639
+ transform_mat = np.column_stack((u_vec, v_vec)) # shape (2,2)
1640
+ try:
1641
+ transform_inv = np.linalg.inv(transform_mat)
1642
+ except np.linalg.LinAlgError:
1643
+ # Fallback if u_vec/v_vec are degenerate (shouldn't happen for proper rectangles)
1644
+ transform_inv = np.linalg.pinv(transform_mat)
1645
+
1646
+ # Iterate trees and accumulate ellipsoidal canopy surfaces
1647
+ for _, row in tree_gdf.iterrows():
1648
+ geom = row['geometry']
1649
+ if geom is None or not hasattr(geom, 'x'):
1650
+ continue
1651
+
1652
+ top_h = float(row.get('top_height', 0.0) or 0.0)
1653
+ bot_h = float(row.get('bottom_height', 0.0) or 0.0)
1654
+ dia = float(row.get('crown_diameter', 0.0) or 0.0)
1655
+
1656
+ # Sanity checks and clamps
1657
+ if dia <= 0 or top_h <= 0:
1658
+ continue
1659
+ if bot_h < 0:
1660
+ bot_h = 0.0
1661
+ if bot_h > top_h:
1662
+ top_h, bot_h = bot_h, top_h
1663
+
1664
+ R = dia / 2.0 # radius (meters)
1665
+ a = max((top_h - bot_h) / 2.0, 0.0)
1666
+ z0 = (top_h + bot_h) / 2.0
1667
+ if a == 0:
1668
+ # Flat disk between bot_h and top_h collapses; treat as constant top/bottom
1669
+ a = 0.0
1670
+
1671
+ # Tree center in lon/lat
1672
+ tree_lon = float(geom.x)
1673
+ tree_lat = float(geom.y)
1674
+
1675
+ # Map tree center to (u,v) metric coordinates relative to origin
1676
+ delta = np.array([tree_lon, tree_lat]) - origin
1677
+ alpha_beta = transform_inv @ delta # meters along u (alpha) and v (beta)
1678
+ alpha_m = alpha_beta[0]
1679
+ beta_m = alpha_beta[1]
1680
+
1681
+ # Determine affected index ranges (bounding box in grid indices)
1682
+ # Convert radius in meters to index offsets along u and v
1683
+ du_cells = int(R / adjusted_meshsize[0] + 2)
1684
+ dv_cells = int(R / adjusted_meshsize[1] + 2)
1685
+
1686
+ i_center_idx = int(alpha_m / adjusted_meshsize[0])
1687
+ j_center_idx = int(beta_m / adjusted_meshsize[1])
1688
+
1689
+ i_min = max(0, i_center_idx - du_cells)
1690
+ i_max = min(nx - 1, i_center_idx + du_cells)
1691
+ j_min = max(0, j_center_idx - dv_cells)
1692
+ j_max = min(ny - 1, j_center_idx + dv_cells)
1693
+
1694
+ if i_min > i_max or j_min > j_max:
1695
+ continue
1696
+
1697
+ # Slice cell center coords for local window
1698
+ ic = i_centers_m[i_min:i_max + 1][:, None] # shape (Ii,1)
1699
+ jc = j_centers_m[j_min:j_max + 1][None, :] # shape (1,Jj)
1700
+
1701
+ # Compute radial distance in meters in grid metric space
1702
+ di = ic - alpha_m
1703
+ dj = jc - beta_m
1704
+ r = np.sqrt(di * di + dj * dj)
1705
+
1706
+ # Mask for points within crown radius
1707
+ within = r <= R
1708
+ if not np.any(within):
1709
+ continue
1710
+
1711
+ # Ellipsoidal vertical profile
1712
+ # Avoid numerical issues for r slightly > R due to precision
1713
+ ratio = np.clip(r / max(R, 1e-9), 0.0, 1.0)
1714
+ factor = np.sqrt(1.0 - ratio * ratio)
1715
+ local_top = z0 + a * factor
1716
+ local_bot = z0 - a * factor
1717
+
1718
+ # Apply mask; cells outside remain zero contribution
1719
+ local_top_masked = np.where(within, local_top, 0.0)
1720
+ local_bot_masked = np.where(within, local_bot, 0.0)
1721
+
1722
+ # Merge with maxima to represent union of crowns
1723
+ canopy_top[i_min:i_max + 1, j_min:j_max + 1] = np.maximum(
1724
+ canopy_top[i_min:i_max + 1, j_min:j_max + 1], local_top_masked
1725
+ )
1726
+ canopy_bottom[i_min:i_max + 1, j_min:j_max + 1] = np.maximum(
1727
+ canopy_bottom[i_min:i_max + 1, j_min:j_max + 1], local_bot_masked
1728
+ )
1729
+
1730
+ # Ensure bottom <= top everywhere
1731
+ canopy_bottom = np.minimum(canopy_bottom, canopy_top)
1732
+
1733
+ return canopy_top, canopy_bottom
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: voxcity
3
- Version: 0.6.14
3
+ Version: 0.6.16
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=3vyH5jdPNe1vDXBYObLB0mtzYw7Ax8sEoF2KVn6UQO4,455
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=_I0yJceBR54_BzUzckZNMp_RdknG9gKe0DAT5Aq3M4g,18019
12
+ voxcity/exporter/cityles.py,sha256=Kfl2PAn4WquqCdjOlyPrHysxPLaudh8QfsoC6WAXlvs,18325
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
- voxcity/generator.py,sha256=ok0LqVk9k0xVqXD9Ybar-CWmqi-vooJmrAFi9WjsaKE,58327
16
+ voxcity/generator.py,sha256=l_oqUPwhZbQNPvEVznJ1ALC5Je3q0yFYRFa3BuF49hA,60873
17
17
  voxcity/geoprocessor/__init__.py,sha256=WYxcAQrjGucIvGHF0JTC6rONZzL3kCms1S2vpzS6KaU,127
18
- voxcity/geoprocessor/draw.py,sha256=_CCQk7lMfwnl2PnRNede6GPEDvZDlf3KCmUC96Nnv3Q,49703
19
- voxcity/geoprocessor/grid.py,sha256=D4sqoIGK2P1U8uuVQZ-447SD0Yrv6qS_zlmDtKoLDe8,71257
18
+ voxcity/geoprocessor/draw.py,sha256=AZMWq23wxxDJygNloCbVzWAAr1JO7nC94umf9LSxJ5o,49248
19
+ voxcity/geoprocessor/grid.py,sha256=eJjT86P0lOFBdPdz420r63bcH2HCA3uQEBZcPBrRt14,78184
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=TQKMmATpAqSDTPMvMi1a-n2CvvzE2KcYVsss7iiShS4,116877
32
32
  voxcity/utils/weather.py,sha256=2Jtg-rIVJcsTtiKE-KuDnhIqS1-MSS16_zFRzj6zmu4,36435
33
- voxcity-0.6.14.dist-info/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
34
- voxcity-0.6.14.dist-info/LICENSE,sha256=s_jE1Df1nTPL4A_5GCGic5Zwex0CVaPKcAmSilxJPPE,1089
35
- voxcity-0.6.14.dist-info/METADATA,sha256=rF1FDSHJSXfZ0UX65Lh0hin_N1RRxhwrgXqIaEWQLWI,26092
36
- voxcity-0.6.14.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
37
- voxcity-0.6.14.dist-info/RECORD,,
33
+ voxcity-0.6.16.dist-info/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
34
+ voxcity-0.6.16.dist-info/LICENSE,sha256=s_jE1Df1nTPL4A_5GCGic5Zwex0CVaPKcAmSilxJPPE,1089
35
+ voxcity-0.6.16.dist-info/METADATA,sha256=ef5yw6coxYhtZtCeYwZAh_1f9J0Em7f01ZRnty2PN7c,26092
36
+ voxcity-0.6.16.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
37
+ voxcity-0.6.16.dist-info/RECORD,,