voxcity 0.4.3__py3-none-any.whl → 0.4.5__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.
- voxcity/generator.py +8 -4
- voxcity/geoprocessor/mesh.py +14 -3
- voxcity/geoprocessor/polygon.py +24 -14
- voxcity/simulator/view.py +2 -1
- voxcity/utils/visualization.py +5 -5
- {voxcity-0.4.3.dist-info → voxcity-0.4.5.dist-info}/METADATA +1 -1
- {voxcity-0.4.3.dist-info → voxcity-0.4.5.dist-info}/RECORD +11 -11
- {voxcity-0.4.3.dist-info → voxcity-0.4.5.dist-info}/WHEEL +1 -1
- {voxcity-0.4.3.dist-info → voxcity-0.4.5.dist-info}/AUTHORS.rst +0 -0
- {voxcity-0.4.3.dist-info → voxcity-0.4.5.dist-info}/LICENSE +0 -0
- {voxcity-0.4.3.dist-info → voxcity-0.4.5.dist-info}/top_level.txt +0 -0
voxcity/generator.py
CHANGED
|
@@ -82,7 +82,7 @@ def get_land_cover_grid(rectangle_vertices, meshsize, source, output_dir, **kwar
|
|
|
82
82
|
print(f"Data source: {source}")
|
|
83
83
|
|
|
84
84
|
# Initialize Earth Engine for accessing satellite data
|
|
85
|
-
if source
|
|
85
|
+
if source != "OpenStreetMap":
|
|
86
86
|
initialize_earth_engine()
|
|
87
87
|
|
|
88
88
|
# Create output directory if it doesn't exist
|
|
@@ -158,7 +158,7 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, *
|
|
|
158
158
|
"""
|
|
159
159
|
|
|
160
160
|
# Initialize Earth Engine for accessing satellite data
|
|
161
|
-
if source
|
|
161
|
+
if source not in ["OpenStreetMap", "Overture", "Local file"]:
|
|
162
162
|
initialize_earth_engine()
|
|
163
163
|
|
|
164
164
|
print("Creating Building Height grid\n ")
|
|
@@ -236,7 +236,9 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, *
|
|
|
236
236
|
# Visualize grid if requested
|
|
237
237
|
grid_vis = kwargs.get("gridvis", True)
|
|
238
238
|
if grid_vis:
|
|
239
|
-
|
|
239
|
+
building_height_grid_nan = building_height_grid.copy()
|
|
240
|
+
building_height_grid_nan[building_height_grid_nan == 0] = np.nan
|
|
241
|
+
visualize_numerical_grid(np.flipud(building_height_grid_nan), meshsize, "building height (m)", cmap='viridis', label='Value')
|
|
240
242
|
|
|
241
243
|
return building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings
|
|
242
244
|
|
|
@@ -282,7 +284,9 @@ def get_canopy_height_grid(rectangle_vertices, meshsize, source, output_dir, **k
|
|
|
282
284
|
# Visualize grid if requested
|
|
283
285
|
grid_vis = kwargs.get("gridvis", True)
|
|
284
286
|
if grid_vis:
|
|
285
|
-
|
|
287
|
+
canopy_height_grid_nan = canopy_height_grid.copy()
|
|
288
|
+
canopy_height_grid_nan[canopy_height_grid_nan == 0] = np.nan
|
|
289
|
+
visualize_numerical_grid(np.flipud(canopy_height_grid_nan), meshsize, "Tree canopy height", cmap='Greens', label='Tree canopy height (m)')
|
|
286
290
|
|
|
287
291
|
return canopy_height_grid
|
|
288
292
|
|
voxcity/geoprocessor/mesh.py
CHANGED
|
@@ -4,7 +4,7 @@ import matplotlib.colors as mcolors
|
|
|
4
4
|
import matplotlib.cm as cm
|
|
5
5
|
import matplotlib.pyplot as plt
|
|
6
6
|
|
|
7
|
-
def create_voxel_mesh(voxel_array, class_id, meshsize=1.0, building_id_grid=None):
|
|
7
|
+
def create_voxel_mesh(voxel_array, class_id, meshsize=1.0, building_id_grid=None, mesh_type=None):
|
|
8
8
|
"""
|
|
9
9
|
Create a mesh from voxels preserving sharp edges, scaled by meshsize.
|
|
10
10
|
|
|
@@ -18,6 +18,11 @@ def create_voxel_mesh(voxel_array, class_id, meshsize=1.0, building_id_grid=None
|
|
|
18
18
|
The real-world size of each voxel in meters, for x, y, and z.
|
|
19
19
|
building_id_grid : np.ndarray (2D), optional
|
|
20
20
|
2D grid of building IDs, shape (X, Y). Used when class_id=-3 (buildings).
|
|
21
|
+
mesh_type : str, optional
|
|
22
|
+
Type of mesh to create:
|
|
23
|
+
- None (default): create meshes for boundaries between different classes
|
|
24
|
+
- 'building_solar': only create meshes for boundaries between buildings (-3)
|
|
25
|
+
and void (0) or trees (-2)
|
|
21
26
|
|
|
22
27
|
Returns
|
|
23
28
|
-------
|
|
@@ -81,7 +86,7 @@ def create_voxel_mesh(voxel_array, class_id, meshsize=1.0, building_id_grid=None
|
|
|
81
86
|
(x, y-1, z) # Bottom
|
|
82
87
|
]
|
|
83
88
|
|
|
84
|
-
# Only create faces where there's a transition
|
|
89
|
+
# Only create faces where there's a transition based on mesh_type
|
|
85
90
|
for face_idx, adj_coord in enumerate(adjacent_coords):
|
|
86
91
|
try:
|
|
87
92
|
# If adj_coord is outside array bounds, it's a boundary => face is visible
|
|
@@ -89,7 +94,13 @@ def create_voxel_mesh(voxel_array, class_id, meshsize=1.0, building_id_grid=None
|
|
|
89
94
|
is_boundary = True
|
|
90
95
|
else:
|
|
91
96
|
adj_value = voxel_array[adj_coord]
|
|
92
|
-
|
|
97
|
+
|
|
98
|
+
if mesh_type == 'open_air' and class_id == -3:
|
|
99
|
+
# For building_solar, only create faces at boundaries with void (0) or trees (-2)
|
|
100
|
+
is_boundary = (adj_value == 0 or adj_value == -2)
|
|
101
|
+
else:
|
|
102
|
+
# Default behavior - create faces at any class change
|
|
103
|
+
is_boundary = (adj_value == 0 or adj_value != class_id)
|
|
93
104
|
except IndexError:
|
|
94
105
|
# Out of range => boundary
|
|
95
106
|
is_boundary = True
|
voxcity/geoprocessor/polygon.py
CHANGED
|
@@ -156,7 +156,7 @@ def extract_building_heights_from_gdf(gdf_0: gpd.GeoDataFrame, gdf_1: gpd.GeoDat
|
|
|
156
156
|
|
|
157
157
|
# Process each building in primary dataset that needs height data
|
|
158
158
|
for idx_primary, row in gdf_primary.iterrows():
|
|
159
|
-
if row['height']
|
|
159
|
+
if row['height'] <= 0 or pd.isna(row['height']):
|
|
160
160
|
count_0 += 1
|
|
161
161
|
geom = row.geometry
|
|
162
162
|
|
|
@@ -619,7 +619,7 @@ def extract_building_heights_from_geotiff(geotiff_path, gdf):
|
|
|
619
619
|
transformer = Transformer.from_crs(CRS.from_epsg(4326), src.crs, always_xy=True)
|
|
620
620
|
|
|
621
621
|
# Filter buildings that need height processing
|
|
622
|
-
mask_condition = (gdf.geometry.geom_type == 'Polygon') & (gdf.get('height', 0) <= 0)
|
|
622
|
+
mask_condition = (gdf.geometry.geom_type == 'Polygon') & ((gdf.get('height', 0) <= 0) | gdf.get('height').isna())
|
|
623
623
|
buildings_to_process = gdf[mask_condition]
|
|
624
624
|
count_0 = len(buildings_to_process)
|
|
625
625
|
|
|
@@ -815,14 +815,24 @@ def process_building_footprints_by_overlap(filtered_gdf, overlap_threshold=0.5):
|
|
|
815
815
|
if 'id' not in gdf.columns:
|
|
816
816
|
gdf['id'] = gdf.index
|
|
817
817
|
|
|
818
|
-
#
|
|
819
|
-
gdf
|
|
820
|
-
|
|
821
|
-
|
|
818
|
+
# Check if CRS is set before transforming
|
|
819
|
+
if gdf.crs is None:
|
|
820
|
+
# Work with original geometries if no CRS is set
|
|
821
|
+
gdf_projected = gdf.copy()
|
|
822
|
+
else:
|
|
823
|
+
# Store original CRS to convert back later
|
|
824
|
+
original_crs = gdf.crs
|
|
825
|
+
# Project to Web Mercator for accurate area calculation
|
|
826
|
+
gdf_projected = gdf.to_crs("EPSG:3857")
|
|
827
|
+
|
|
828
|
+
# Calculate areas on the geometries
|
|
829
|
+
gdf_projected['area'] = gdf_projected.geometry.area
|
|
830
|
+
gdf_projected = gdf_projected.sort_values(by='area', ascending=False)
|
|
831
|
+
gdf_projected = gdf_projected.reset_index(drop=True)
|
|
822
832
|
|
|
823
833
|
# Create spatial index for efficient querying
|
|
824
834
|
spatial_idx = index.Index()
|
|
825
|
-
for i, geom in enumerate(
|
|
835
|
+
for i, geom in enumerate(gdf_projected.geometry):
|
|
826
836
|
if geom.is_valid:
|
|
827
837
|
spatial_idx.insert(i, geom.bounds)
|
|
828
838
|
else:
|
|
@@ -835,10 +845,10 @@ def process_building_footprints_by_overlap(filtered_gdf, overlap_threshold=0.5):
|
|
|
835
845
|
id_mapping = {}
|
|
836
846
|
|
|
837
847
|
# Process each building (skip the largest one)
|
|
838
|
-
for i in range(1, len(
|
|
839
|
-
current_poly =
|
|
840
|
-
current_area =
|
|
841
|
-
current_id =
|
|
848
|
+
for i in range(1, len(gdf_projected)):
|
|
849
|
+
current_poly = gdf_projected.iloc[i].geometry
|
|
850
|
+
current_area = gdf_projected.iloc[i].area
|
|
851
|
+
current_id = gdf_projected.iloc[i]['id']
|
|
842
852
|
|
|
843
853
|
# Skip if already mapped
|
|
844
854
|
if current_id in id_mapping:
|
|
@@ -854,8 +864,8 @@ def process_building_footprints_by_overlap(filtered_gdf, overlap_threshold=0.5):
|
|
|
854
864
|
potential_overlaps = [j for j in spatial_idx.intersection(current_poly.bounds) if j < i]
|
|
855
865
|
|
|
856
866
|
for j in potential_overlaps:
|
|
857
|
-
larger_poly =
|
|
858
|
-
larger_id =
|
|
867
|
+
larger_poly = gdf_projected.iloc[j].geometry
|
|
868
|
+
larger_id = gdf_projected.iloc[j]['id']
|
|
859
869
|
|
|
860
870
|
# Skip if already processed
|
|
861
871
|
if larger_id in id_mapping:
|
|
@@ -876,7 +886,7 @@ def process_building_footprints_by_overlap(filtered_gdf, overlap_threshold=0.5):
|
|
|
876
886
|
# Replace ID if overlap exceeds threshold
|
|
877
887
|
if overlap_ratio > overlap_threshold:
|
|
878
888
|
id_mapping[current_id] = larger_id
|
|
879
|
-
|
|
889
|
+
gdf_projected.at[i, 'id'] = larger_id
|
|
880
890
|
break # Stop at first significant overlap
|
|
881
891
|
except (GEOSException, ValueError) as e:
|
|
882
892
|
# Handle geometry errors gracefully
|
voxcity/simulator/view.py
CHANGED
|
@@ -1708,7 +1708,8 @@ def get_surface_view_factor(voxel_data, meshsize, **kwargs):
|
|
|
1708
1708
|
voxel_data,
|
|
1709
1709
|
building_class_id,
|
|
1710
1710
|
meshsize,
|
|
1711
|
-
building_id_grid=building_id_grid
|
|
1711
|
+
building_id_grid=building_id_grid,
|
|
1712
|
+
mesh_type='open_air'
|
|
1712
1713
|
)
|
|
1713
1714
|
if building_mesh is None or len(building_mesh.faces) == 0:
|
|
1714
1715
|
print("No surfaces found in voxel data for the specified class.")
|
voxcity/utils/visualization.py
CHANGED
|
@@ -1270,7 +1270,7 @@ def visualize_numerical_grid_on_basemap(grid, rectangle_vertices, meshsize, valu
|
|
|
1270
1270
|
plt.tight_layout()
|
|
1271
1271
|
plt.show()
|
|
1272
1272
|
|
|
1273
|
-
def
|
|
1273
|
+
def visualize_numerical_gdf_on_basemap(gdf, value_name="value", cmap='viridis', vmin=None, vmax=None,
|
|
1274
1274
|
alpha=0.6, figsize=(12, 8), basemap='CartoDB light',
|
|
1275
1275
|
show_edge=False, edge_color='black', edge_width=0.5):
|
|
1276
1276
|
"""Visualizes a GeoDataFrame with numerical values on a basemap.
|
|
@@ -1298,7 +1298,7 @@ def visualize_numerical_grid_gdf_on_basemap(gdf, value_name="value", cmap='virid
|
|
|
1298
1298
|
fig, ax = plt.subplots(figsize=figsize)
|
|
1299
1299
|
|
|
1300
1300
|
# Plot the GeoDataFrame
|
|
1301
|
-
gdf_web.plot(column=
|
|
1301
|
+
gdf_web.plot(column=value_name,
|
|
1302
1302
|
ax=ax,
|
|
1303
1303
|
alpha=alpha,
|
|
1304
1304
|
cmap=cmap,
|
|
@@ -1325,7 +1325,7 @@ def visualize_numerical_grid_gdf_on_basemap(gdf, value_name="value", cmap='virid
|
|
|
1325
1325
|
plt.tight_layout()
|
|
1326
1326
|
plt.show()
|
|
1327
1327
|
|
|
1328
|
-
def
|
|
1328
|
+
def visualize_point_gdf_on_basemap(point_gdf, value_name='value', **kwargs):
|
|
1329
1329
|
"""Visualizes a point GeoDataFrame on a basemap with colors based on values.
|
|
1330
1330
|
|
|
1331
1331
|
Args:
|
|
@@ -1450,7 +1450,7 @@ def create_multi_view_scene(meshes, output_directory="output", projection_type="
|
|
|
1450
1450
|
|
|
1451
1451
|
# Add orthographic views
|
|
1452
1452
|
ortho_views = {
|
|
1453
|
-
'xy_top': [center + np.array([0, 0, distance]), center, (
|
|
1453
|
+
'xy_top': [center + np.array([0, 0, distance]), center, (-1, 0, 0)],
|
|
1454
1454
|
'yz_right': [center + np.array([distance, 0, 0]), center, (0, 0, 1)],
|
|
1455
1455
|
'xz_front': [center + np.array([0, distance, 0]), center, (0, 0, 1)],
|
|
1456
1456
|
'yz_left': [center + np.array([-distance, 0, 0]), center, (0, 0, 1)],
|
|
@@ -1787,7 +1787,7 @@ def visualize_voxcity_with_sim_meshes(voxel_array, meshsize, custom_meshes=None,
|
|
|
1787
1787
|
# Set vmin/vmax if not provided
|
|
1788
1788
|
local_vmin = vmin if vmin is not None else np.nanmin(values[~np.isnan(values)])
|
|
1789
1789
|
local_vmax = vmax if vmax is not None else np.nanmax(values[~np.isnan(values)])
|
|
1790
|
-
|
|
1790
|
+
|
|
1791
1791
|
# Create colors
|
|
1792
1792
|
cmap = cm.get_cmap(cmap_name)
|
|
1793
1793
|
norm = mcolors.Normalize(vmin=local_vmin, vmax=local_vmax)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: voxcity
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.5
|
|
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
|
Author-email: Kunihiko Fujiwara <kunihiko@nus.edu.sg>
|
|
6
6
|
Maintainer-email: Kunihiko Fujiwara <kunihiko@nus.edu.sg>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
voxcity/__init__.py,sha256=el9v3gfybHOF_GUYPeSOqN0-vCrTW0eU1mcvi0sEfeU,252
|
|
2
|
-
voxcity/generator.py,sha256=
|
|
2
|
+
voxcity/generator.py,sha256=0RKWcWKfwFs2xcepyEDLMQdHqGhAGlpPwhNe6UJTRuI,35348
|
|
3
3
|
voxcity/downloader/__init__.py,sha256=OgGcGxOXF4tjcEL6DhOnt13DYPTvOigUelp5xIpTqM0,171
|
|
4
4
|
voxcity/downloader/eubucco.py,sha256=XCkkdEPNuWdrnuxzL80Ext37WsgiCiZGueb-aQV5rvI,14476
|
|
5
5
|
voxcity/downloader/gee.py,sha256=hEN5OvQAltORYnrlPbmYcDequ6lKLmwyTbNaCZ81Vj8,16089
|
|
@@ -16,22 +16,22 @@ voxcity/exporter/obj.py,sha256=0RBFPMKGRH6uNmCLIwAoYFko1bOZKtTSwg7QnoPMud0,21593
|
|
|
16
16
|
voxcity/geoprocessor/__init_.py,sha256=JzPVhhttxBWvaZ0IGX2w7OWL5bCo_TIvpHefWeNXruA,133
|
|
17
17
|
voxcity/geoprocessor/draw.py,sha256=8Em2NvazFpYfFJUqG9LofNXaxdghKLL_rNuztmPwn8Q,13911
|
|
18
18
|
voxcity/geoprocessor/grid.py,sha256=t-KAdYOk1iUeBuOMcoAb5e4hzJjMIfVItTdFP6FJs1A,44340
|
|
19
|
-
voxcity/geoprocessor/mesh.py,sha256=
|
|
19
|
+
voxcity/geoprocessor/mesh.py,sha256=r3cRPLgpbhjwgESBemHWWJ5pEWl2KdkRhID6mdLhios,11171
|
|
20
20
|
voxcity/geoprocessor/network.py,sha256=opb_kpUCAxDd1qtrWPStqR5reYZtVe96XxazNSen7Lk,18851
|
|
21
|
-
voxcity/geoprocessor/polygon.py,sha256=
|
|
21
|
+
voxcity/geoprocessor/polygon.py,sha256=8Vb2AbkpKYhq1kk2hQMc-gitmUo9pFIe910v4p1vP2g,37772
|
|
22
22
|
voxcity/geoprocessor/utils.py,sha256=1BRHp-DDeOA8HG8jplY7Eo75G3oXkVGL6DGONL4BA8A,19815
|
|
23
23
|
voxcity/simulator/__init_.py,sha256=APdkcdaovj0v_RPOaA4SBvFUKT2RM7Hxuuz3Sux4gCo,65
|
|
24
24
|
voxcity/simulator/solar.py,sha256=amJ5GJtzdG9NE7oxrLj4EMRxGvDY9s2BQdDJ2paFfn0,56709
|
|
25
25
|
voxcity/simulator/utils.py,sha256=sEYBB2-hLJxTiXQps1_-Fi7t1HN3-1OPOvBCWtgIisA,130
|
|
26
|
-
voxcity/simulator/view.py,sha256
|
|
26
|
+
voxcity/simulator/view.py,sha256=YufbLuDXrLg1d1dedM6pVyiJ7uHsqY8F2sLLnIoJvB4,74910
|
|
27
27
|
voxcity/utils/__init_.py,sha256=nLYrj2huBbDBNMqfchCwexGP8Tlt9O_XluVDG7MoFkw,98
|
|
28
28
|
voxcity/utils/lc.py,sha256=RwPd-VY3POV3gTrBhM7TubgGb9MCd3nVah_G8iUEF7k,11562
|
|
29
29
|
voxcity/utils/material.py,sha256=Vt3IID5Ft54HNJcEC4zi31BCPqi_687X3CSp7rXaRVY,5907
|
|
30
|
-
voxcity/utils/visualization.py,sha256=
|
|
30
|
+
voxcity/utils/visualization.py,sha256=SF8W7sqvBl3sZbB5noWCY9ic2D34Gq01VZYJ9NDNZ4Y,85237
|
|
31
31
|
voxcity/utils/weather.py,sha256=CFPtoqRTajwMRswswDChwQ3BW1cGsnA3orgWHgz7Ehg,26304
|
|
32
|
-
voxcity-0.4.
|
|
33
|
-
voxcity-0.4.
|
|
34
|
-
voxcity-0.4.
|
|
35
|
-
voxcity-0.4.
|
|
36
|
-
voxcity-0.4.
|
|
37
|
-
voxcity-0.4.
|
|
32
|
+
voxcity-0.4.5.dist-info/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
|
|
33
|
+
voxcity-0.4.5.dist-info/LICENSE,sha256=-hGliOFiwUrUSoZiB5WF90xXGqinKyqiDI2t6hrnam8,1087
|
|
34
|
+
voxcity-0.4.5.dist-info/METADATA,sha256=YDINX8e1EqSKFcCMTlPdFYj-wrWH7zIazT1fE89MkrQ,25527
|
|
35
|
+
voxcity-0.4.5.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
|
36
|
+
voxcity-0.4.5.dist-info/top_level.txt,sha256=00b2U-LKfDllt6RL1R33MXie5MvxzUFye0NGD96t_8I,8
|
|
37
|
+
voxcity-0.4.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|