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 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 is not "OpenStreetMap":
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 is not "OpenStreetMap":
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
- visualize_numerical_grid(np.flipud(building_height_grid), meshsize, "building height (m)", cmap='viridis', label='Value')
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
- visualize_numerical_grid(np.flipud(canopy_height_grid), meshsize, "Tree canopy height", cmap='Greens', label='Tree canopy height (m)')
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
 
@@ -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 between this class and "not this class"
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
- is_boundary = (adj_value == 0 or adj_value != class_id)
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
@@ -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'] == 0:
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
- # Calculate areas and sort by area (descending)
819
- gdf['area'] = gdf.geometry.area
820
- gdf = gdf.sort_values(by='area', ascending=False)
821
- gdf = gdf.reset_index(drop=True)
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(gdf.geometry):
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(gdf)):
839
- current_poly = gdf.iloc[i].geometry
840
- current_area = gdf.iloc[i].area
841
- current_id = gdf.iloc[i]['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 = gdf.iloc[j].geometry
858
- larger_id = gdf.iloc[j]['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
- gdf.at[i, 'id'] = larger_id
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.")
@@ -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 visualize_numerical_grid_gdf_on_basemap(gdf, value_name="value", cmap='viridis', vmin=None, vmax=None,
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='value',
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 visualize_point_grid_on_basemap(point_gdf, value_name='value', **kwargs):
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, (0, 1, 0)],
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
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=rsS5Z77Esgomho9Bd3HRQeYlncvyEG1CEHUXAB_SHxw,35048
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=z6e-BigN_K1CGpS9mopkf121pbCnBFJMu32XYfqpLI4,10506
19
+ voxcity/geoprocessor/mesh.py,sha256=r3cRPLgpbhjwgESBemHWWJ5pEWl2KdkRhID6mdLhios,11171
20
20
  voxcity/geoprocessor/network.py,sha256=opb_kpUCAxDd1qtrWPStqR5reYZtVe96XxazNSen7Lk,18851
21
- voxcity/geoprocessor/polygon.py,sha256=wXU3K-lghAlAOe_C-m1YD4_75DvCWzdszuWieenP5mA,37208
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=-CM-pPbfthg_meyMdcZs9Ar3CHaZhw2fRf_dM6ioN_Q,74875
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=8r7ffnvBCAmvEYnJQ4KKh18aaah3G9ARoV9EDXUyo8I,85255
30
+ voxcity/utils/visualization.py,sha256=SF8W7sqvBl3sZbB5noWCY9ic2D34Gq01VZYJ9NDNZ4Y,85237
31
31
  voxcity/utils/weather.py,sha256=CFPtoqRTajwMRswswDChwQ3BW1cGsnA3orgWHgz7Ehg,26304
32
- voxcity-0.4.3.dist-info/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
33
- voxcity-0.4.3.dist-info/LICENSE,sha256=-hGliOFiwUrUSoZiB5WF90xXGqinKyqiDI2t6hrnam8,1087
34
- voxcity-0.4.3.dist-info/METADATA,sha256=kCm6TzscCstqLacexWTUM6I1u9qm_wT-AeYdlCe8P9o,25527
35
- voxcity-0.4.3.dist-info/WHEEL,sha256=EaM1zKIUYa7rQnxGiOCGhzJABRwy4WO57rWMR3_tj4I,91
36
- voxcity-0.4.3.dist-info/top_level.txt,sha256=00b2U-LKfDllt6RL1R33MXie5MvxzUFye0NGD96t_8I,8
37
- voxcity-0.4.3.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.9.1)
2
+ Generator: setuptools (76.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5