voxcity 0.3.20__py3-none-any.whl → 0.3.22__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/downloader/gee.py CHANGED
@@ -135,6 +135,10 @@ def get_dem_image(roi_buffered, source):
135
135
  collection_name = 'AU/GA/AUSTRALIA_5M_DEM'
136
136
  collection = ee.ImageCollection(collection_name)
137
137
  dem = collection.select('elevation').mosaic()
138
+ elif source == 'Netherlands 0.5m DTM':
139
+ collection_name = 'AHN/AHN4'
140
+ collection = ee.ImageCollection(collection_name)
141
+ dem = collection.select('dtm').mosaic()
138
142
  elif source == 'USGS 3DEP 1m':
139
143
  collection_name = 'USGS/3DEP/1m'
140
144
  dem = ee.ImageCollection(collection_name).mosaic()
@@ -395,13 +399,14 @@ def save_geotiff_open_buildings_temporal(aoi, geotiff_path):
395
399
  file_per_band=False
396
400
  )
397
401
 
398
- def save_geotiff_england_dsm_minus_dtm(roi, geotiff_path, meshsize):
399
- """Get the height difference between DSM and DTM from England 1m terrain data.
402
+ def save_geotiff_dsm_minus_dtm(roi, geotiff_path, meshsize, source):
403
+ """Get the height difference between DSM and DTM from terrain data.
400
404
 
401
405
  Args:
402
406
  roi: Earth Engine geometry defining area of interest
403
407
  geotiff_path: Output path for GeoTIFF file
404
408
  meshsize: Size of each grid cell in meters
409
+ source: Source of terrain data ('England' or 'Netherlands')
405
410
 
406
411
  Returns:
407
412
  ee.Image: Image representing DSM minus DTM (building/vegetation heights)
@@ -413,9 +418,16 @@ def save_geotiff_england_dsm_minus_dtm(roi, geotiff_path, meshsize):
413
418
  buffer_distance = 100
414
419
  roi_buffered = roi.buffer(buffer_distance)
415
420
 
416
- collection_name = 'UK/EA/ENGLAND_1M_TERRAIN/2022'
417
- dtm = ee.Image(collection_name).select('dtm')
418
- dsm = ee.Image(collection_name).select('dsm_first')
421
+ if source == 'England 1m DSM - DTM':
422
+ collection_name = 'UK/EA/ENGLAND_1M_TERRAIN/2022'
423
+ dtm = ee.Image(collection_name).select('dtm')
424
+ dsm = ee.Image(collection_name).select('dsm_first')
425
+ elif source == 'Netherlands 0.5m DSM - DTM':
426
+ collection = ee.ImageCollection('AHN/AHN4').filterBounds(roi_buffered)
427
+ dtm = collection.select('dtm').mosaic()
428
+ dsm = collection.select('dsm').mosaic()
429
+ else:
430
+ raise ValueError("Source must be either 'England' or 'Netherlands'")
419
431
 
420
432
  # Subtract DTM from DSM to get height difference
421
433
  height_diff = dsm.subtract(dtm)
@@ -100,4 +100,7 @@ def get_mbfp_gdf(output_dir, rectangle_vertices):
100
100
  # Load GeoJSON data from downloaded files and fix coordinate ordering
101
101
  gdf = load_gdf_from_multiple_gz(filenames)
102
102
 
103
+ # Replace id column with index numbers
104
+ gdf['id'] = gdf.index
105
+
103
106
  return gdf
voxcity/generator.py CHANGED
@@ -35,7 +35,7 @@ from .downloader.gee import (
35
35
  save_geotiff_esri_landcover,
36
36
  save_geotiff_dynamic_world_v1,
37
37
  save_geotiff_open_buildings_temporal,
38
- save_geotiff_england_dsm_minus_dtm
38
+ save_geotiff_dsm_minus_dtm
39
39
  )
40
40
  from .geoprocessor.grid import (
41
41
  group_and_label_cells,
@@ -201,12 +201,12 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, *
201
201
  geotiff_path_comp = os.path.join(output_dir, "building_height.tif")
202
202
  save_geotiff_open_buildings_temporal(roi, geotiff_path_comp)
203
203
  building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(gdf, meshsize, rectangle_vertices, geotiff_path_comp=geotiff_path_comp, complement_height=building_complement_height)
204
- elif building_complementary_source == "England 1m DSM - DTM":
204
+ elif building_complementary_source in ["England 1m DSM - DTM", "Netherlands 0.5m DSM - DTM"]:
205
205
  # Special case: use temporal height data as complement
206
206
  roi = get_roi(rectangle_vertices)
207
207
  os.makedirs(output_dir, exist_ok=True)
208
208
  geotiff_path_comp = os.path.join(output_dir, "building_height.tif")
209
- save_geotiff_england_dsm_minus_dtm(roi, geotiff_path_comp, meshsize)
209
+ save_geotiff_dsm_minus_dtm(roi, geotiff_path_comp, meshsize, building_complementary_source)
210
210
  building_height_grid, building_min_height_grid, building_id_grid, filtered_buildings = create_building_height_grid_from_gdf_polygon(gdf, meshsize, rectangle_vertices, geotiff_path_comp=geotiff_path_comp, complement_height=building_complement_height)
211
211
  else:
212
212
  # Get complementary data from other sources
@@ -541,11 +541,16 @@ def create_building_height_grid_from_gdf_polygon(
541
541
  plotting_box = box(extent[2], extent[0], extent[3], extent[1])
542
542
  filtered_gdf = gdf[gdf.geometry.intersects(plotting_box)].copy()
543
543
 
544
+ # Count buildings with height=0 or NaN
545
+ zero_height_count = len(filtered_gdf[filtered_gdf['height'] == 0])
546
+ nan_height_count = len(filtered_gdf[filtered_gdf['height'].isna()])
547
+ print(f"{zero_height_count+nan_height_count} of the total {len(filtered_gdf)} building footprint from the base data source did not have height data.")
548
+
544
549
  # Optionally merge heights from complementary sources
545
550
  if gdf_comp is not None:
546
551
  filtered_gdf_comp = gdf_comp[gdf_comp.geometry.intersects(plotting_box)].copy()
547
552
  if complement_building_footprints:
548
- filtered_gdf = complement_building_heights_gdf(filtered_gdf, filtered_gdf_comp)
553
+ filtered_gdf = complement_building_heights_from_gdf(filtered_gdf, filtered_gdf_comp)
549
554
  else:
550
555
  filtered_gdf = extract_building_heights_from_gdf(filtered_gdf, filtered_gdf_comp)
551
556
  elif geotiff_path_comp:
@@ -201,8 +201,9 @@ def extract_building_heights_from_gdf(gdf_0: gpd.GeoDataFrame, gdf_1: gpd.GeoDat
201
201
 
202
202
  # Print statistics about height updates
203
203
  if count_0 > 0:
204
- print(f"{count_0} of the total {len(gdf_primary)} building footprint from OSM did not have height data.")
205
- print(f"For {count_1} of these building footprints without height, values from Microsoft Building Footprints were assigned.")
204
+ # print(f"{count_0} of the total {len(gdf_primary)} building footprint from OSM did not have height data.")
205
+ print(f"For {count_1} of these building footprints without height, values from the complementary source were assigned.")
206
+ print(f"For {count_2} of these building footprints without height, no data exist in complementary data.")
206
207
 
207
208
  return gdf_primary
208
209
 
@@ -377,7 +378,6 @@ def complement_building_heights_from_gdf(gdf_0, gdf_1,
377
378
 
378
379
  Returns:
379
380
  gpd.GeoDataFrame: Updated GeoDataFrame (including new buildings).
380
- You can convert it back to a list of dict features if needed.
381
381
  """
382
382
  # Make a copy of input GeoDataFrames to avoid modifying originals
383
383
  gdf_primary = gdf_0.copy()
@@ -406,7 +406,6 @@ def complement_building_heights_from_gdf(gdf_0, gdf_1,
406
406
 
407
407
  # Compute intersection area
408
408
  intersect_gdf['intersect_area'] = intersect_gdf.area
409
- # Weighted area (height_ref * intersect_area)
410
409
  intersect_gdf['height_area'] = intersect_gdf['height_ref'] * intersect_gdf['intersect_area']
411
410
 
412
411
  # ----------------------------------------------------------------
@@ -417,7 +416,7 @@ def complement_building_heights_from_gdf(gdf_0, gdf_1,
417
416
  'height_area': 'sum',
418
417
  'intersect_area': 'sum'
419
418
  }
420
- grouped = intersect_gdf.groupby(gdf_primary[primary_id].name).agg(group_cols)
419
+ grouped = intersect_gdf.groupby(f'{primary_id}_1').agg(group_cols)
421
420
 
422
421
  # Weighted average
423
422
  grouped['weighted_height'] = grouped['height_area'] / grouped['intersect_area']
@@ -433,10 +432,10 @@ def complement_building_heights_from_gdf(gdf_0, gdf_1,
433
432
 
434
433
  # Where primary had zero or missing height, we assign the new weighted height
435
434
  zero_or_nan_mask = (gdf_primary['height_primary'] == 0) | (gdf_primary['height_primary'].isna())
436
- gdf_primary.loc[zero_or_nan_mask, 'height_primary'] = gdf_primary.loc[zero_or_nan_mask, 'weighted_height']
437
-
438
- # For any building that had no overlap, 'weighted_height' might be NaN.
439
- # Keep it as NaN or set to 0 if you prefer:
435
+
436
+ # Only update heights where we have valid weighted heights
437
+ valid_weighted_height_mask = zero_or_nan_mask & gdf_primary['weighted_height'].notna()
438
+ gdf_primary.loc[valid_weighted_height_mask, 'height_primary'] = gdf_primary.loc[valid_weighted_height_mask, 'weighted_height']
440
439
  gdf_primary['height_primary'] = gdf_primary['height_primary'].fillna(np.nan)
441
440
 
442
441
  # ----------------------------------------------------------------
@@ -448,10 +447,11 @@ def complement_building_heights_from_gdf(gdf_0, gdf_1,
448
447
 
449
448
  # For building-level intersection, do a left join of ref onto primary.
450
449
  # Then we'll identify which reference IDs are missing from the intersection result.
451
- sjoin_gdf = gpd.sjoin(gdf_ref, gdf_primary, how='left', op='intersects')
452
-
453
- # All reference buildings that did not intersect any primary building
454
- non_intersect_ids = sjoin_gdf.loc[sjoin_gdf[primary_id].isna(), ref_id].unique()
450
+ sjoin_gdf = gpd.sjoin(gdf_ref, gdf_primary, how='left', predicate='intersects')
451
+
452
+ # Find reference buildings that don't intersect with any primary building
453
+ non_intersect_mask = sjoin_gdf[f'{primary_id}_right'].isna()
454
+ non_intersect_ids = sjoin_gdf[non_intersect_mask][f'{ref_id}_left'].unique()
455
455
 
456
456
  # Extract them from the original reference GDF
457
457
  gdf_ref_non_intersect = gdf_ref[gdf_ref[ref_id].isin(non_intersect_ids)]
@@ -473,6 +473,29 @@ def complement_building_heights_from_gdf(gdf_0, gdf_1,
473
473
  # Concatenate
474
474
  final_gdf = pd.concat([gdf_primary, gdf_ref_non_intersect], ignore_index=True)
475
475
 
476
+ # Calculate statistics
477
+ count_total = len(gdf_primary)
478
+ count_0 = len(gdf_primary[zero_or_nan_mask])
479
+ count_1 = len(gdf_primary[valid_weighted_height_mask])
480
+ count_2 = count_0 - count_1
481
+ count_3 = len(gdf_ref_non_intersect)
482
+ count_4 = count_3
483
+ height_mask = gdf_ref_non_intersect['height'].notna() & (gdf_ref_non_intersect['height'] > 0)
484
+ count_5 = len(gdf_ref_non_intersect[height_mask])
485
+ count_6 = count_4 - count_5
486
+ final_height_mask = final_gdf['height'].notna() & (final_gdf['height'] > 0)
487
+ count_7 = len(final_gdf[final_height_mask])
488
+ count_8 = len(final_gdf)
489
+
490
+ # Print statistics if there were buildings without height data
491
+ if count_0 > 0:
492
+ print(f"{count_0} of the total {count_total} building footprints from base data source did not have height data.")
493
+ print(f"For {count_1} of these building footprints without height, values from complementary data were assigned.")
494
+ print(f"For the rest {count_2}, no data exists in complementary data.")
495
+ print(f"Footprints of {count_3} buildings were added from the complementary source.")
496
+ print(f"Of these {count_4} additional building footprints, {count_5} had height data while {count_6} had no height data.")
497
+ print(f"In total, {count_7} buildings had height data out of {count_8} total building footprints.")
498
+
476
499
  return final_gdf
477
500
 
478
501
 
@@ -616,8 +639,8 @@ def extract_building_heights_from_geotiff(geotiff_path, gdf):
616
639
  gdf.at[idx, 'height'] = float(np.mean(heights))
617
640
  else:
618
641
  count_2 += 1
619
- gdf.at[idx, 'height'] = 10
620
- print(f"No valid height data for building at index {idx}")
642
+ gdf.at[idx, 'height'] = np.nan
643
+ # print(f"No valid height data for building at index {idx}")
621
644
  except ValueError as e:
622
645
  print(f"Error processing building at index {idx}. Error: {str(e)}")
623
646
  gdf.at[idx, 'height'] = None
@@ -626,7 +649,7 @@ def extract_building_heights_from_geotiff(geotiff_path, gdf):
626
649
  if count_0 > 0:
627
650
  print(f"{count_0} of the total {len(gdf)} building footprint from OSM did not have height data.")
628
651
  print(f"For {count_1} of these building footprints without height, values from complementary data were assigned.")
629
- print(f"For {count_2} of these building footprints without height, no data exist in complementary data. Height values of 10m were set instead")
652
+ print(f"For {count_2} of these building footprints without height, no data exist in complementary data.")
630
653
 
631
654
  return gdf
632
655
 
@@ -1032,7 +1032,8 @@ def visualize_voxcity_multi_view(voxel_array, meshsize, **kwargs):
1032
1032
  base_filename = kwargs.get("output_file_name", None)
1033
1033
  sim_grid = kwargs.get("sim_grid", None)
1034
1034
  dem_grid_ori = kwargs.get("dem_grid", None)
1035
- dem_grid = dem_grid_ori - np.min(dem_grid_ori)
1035
+ if dem_grid_ori is not None:
1036
+ dem_grid = dem_grid_ori - np.min(dem_grid_ori)
1036
1037
  z_offset = kwargs.get("view_point_height", 1.5)
1037
1038
  cmap_name = kwargs.get("colormap", "viridis")
1038
1039
  vmin = kwargs.get("vmin", np.nanmin(sim_grid))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: voxcity
3
- Version: 0.3.20
3
+ Version: 0.3.22
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,9 +1,9 @@
1
1
  voxcity/__init__.py,sha256=el9v3gfybHOF_GUYPeSOqN0-vCrTW0eU1mcvi0sEfeU,252
2
- voxcity/generator.py,sha256=2wmfiCXJREOC0zxq6GB8ECxtGjk42UYzGvf5vGZuEOg,34359
2
+ voxcity/generator.py,sha256=dEhEGWF7Wgz8BwtX-eheAmn85VzhgJC9SckVQohVzzo,34406
3
3
  voxcity/downloader/__init__.py,sha256=OgGcGxOXF4tjcEL6DhOnt13DYPTvOigUelp5xIpTqM0,171
4
4
  voxcity/downloader/eubucco.py,sha256=XCkkdEPNuWdrnuxzL80Ext37WsgiCiZGueb-aQV5rvI,14476
5
- voxcity/downloader/gee.py,sha256=C5Y3UFAK4-AcBQwUd5FQNDwwxT_iqfElAHWiVJTkF9k,15471
6
- voxcity/downloader/mbfp.py,sha256=5fXq9S7qNVSLDdVtj67Da1pBAJP6kL4P8qLZTOmWqdw,3895
5
+ voxcity/downloader/gee.py,sha256=hEN5OvQAltORYnrlPbmYcDequ6lKLmwyTbNaCZ81Vj8,16089
6
+ voxcity/downloader/mbfp.py,sha256=pGJuXXLRuRedlORXfg8WlgAVwmKI30jxki9t-v5NejY,3972
7
7
  voxcity/downloader/oemj.py,sha256=YlCuWBQfi40gfmwQcGDeHiPOs4Pk_jLZq65d5R3IGMU,7886
8
8
  voxcity/downloader/omt.py,sha256=ByFvoQDnBOJF4qdVYNkDjn7cMvEhWwtD0mIV_T-zMEs,9017
9
9
  voxcity/downloader/osm.py,sha256=0VpYuPRiO3iru3nHM_or0nTDFb8ytUUDZieFX6zxB9Q,26338
@@ -15,10 +15,10 @@ voxcity/exporter/magicavoxel.py,sha256=Fsv7yGRXeKmp82xcG3rOb0t_HtoqltNq2tHl08xVl
15
15
  voxcity/exporter/obj.py,sha256=oW-kPoZj53nfmO9tXP3Wvizq6Kkjh-QQR8UBexRuMiI,21609
16
16
  voxcity/geoprocessor/__init_.py,sha256=JzPVhhttxBWvaZ0IGX2w7OWL5bCo_TIvpHefWeNXruA,133
17
17
  voxcity/geoprocessor/draw.py,sha256=8Em2NvazFpYfFJUqG9LofNXaxdghKLL_rNuztmPwn8Q,13911
18
- voxcity/geoprocessor/grid.py,sha256=aLb_iInDrhh5cacQOOtHPZ9IWFTWsQtF6hEgsA6TB2Y,43761
18
+ voxcity/geoprocessor/grid.py,sha256=DYphECd0v4o7_ZH5rd-_AWSvNaKoAsQ2fuko1sh-2AM,44113
19
19
  voxcity/geoprocessor/mesh.py,sha256=u4j5wombULMc26yhH8seltMZvmeKab1oequnZYrbiuE,9454
20
20
  voxcity/geoprocessor/network.py,sha256=opb_kpUCAxDd1qtrWPStqR5reYZtVe96XxazNSen7Lk,18851
21
- voxcity/geoprocessor/polygon.py,sha256=EeAlawsI0AQHctxG_1rS8y3sLFijwmWtdIFtLIXHUCg,32231
21
+ voxcity/geoprocessor/polygon.py,sha256=wMVn2Y7rfbzOlTBktq4pBNZ7-Dmh3jOqRBHk450Ywss,33669
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=FOcHoUm4miJNyeCcGs2oL93Vu38Affyywt29dJcmIT4,31974
@@ -27,11 +27,11 @@ voxcity/simulator/view.py,sha256=zNbfTLQ2Jo0V5-rFA3-xamRjOuw3H3MBrLKpQp8x3hY,367
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=wLx_YBajzDvQVOp42uba4rn7rxUdikJnIrG1jmIDltc,48588
30
+ voxcity/utils/visualization.py,sha256=WZkwxihy6XrSxMOPgAs0--MeoKK_lKIHRdPW9d-hQbQ,48626
31
31
  voxcity/utils/weather.py,sha256=P6s1y_EstBL1OGP_MR_6u3vr-t6Uawg8uDckJnoI7FI,21482
32
- voxcity-0.3.20.dist-info/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
33
- voxcity-0.3.20.dist-info/LICENSE,sha256=-hGliOFiwUrUSoZiB5WF90xXGqinKyqiDI2t6hrnam8,1087
34
- voxcity-0.3.20.dist-info/METADATA,sha256=_F6HJIiDGGlCFlH_aRKB5miZJ0tVymTBhoP826WbCZU,25186
35
- voxcity-0.3.20.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
36
- voxcity-0.3.20.dist-info/top_level.txt,sha256=00b2U-LKfDllt6RL1R33MXie5MvxzUFye0NGD96t_8I,8
37
- voxcity-0.3.20.dist-info/RECORD,,
32
+ voxcity-0.3.22.dist-info/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
33
+ voxcity-0.3.22.dist-info/LICENSE,sha256=-hGliOFiwUrUSoZiB5WF90xXGqinKyqiDI2t6hrnam8,1087
34
+ voxcity-0.3.22.dist-info/METADATA,sha256=X27qtDw9i9619sVIcpoKLZ11DOHa_uOOLj3oRdSRcgc,25186
35
+ voxcity-0.3.22.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
36
+ voxcity-0.3.22.dist-info/top_level.txt,sha256=00b2U-LKfDllt6RL1R33MXie5MvxzUFye0NGD96t_8I,8
37
+ voxcity-0.3.22.dist-info/RECORD,,