voxcity 0.3.21__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 +17 -5
- voxcity/downloader/mbfp.py +3 -0
- voxcity/generator.py +3 -3
- voxcity/geoprocessor/grid.py +6 -1
- voxcity/geoprocessor/polygon.py +39 -16
- {voxcity-0.3.21.dist-info → voxcity-0.3.22.dist-info}/METADATA +1 -1
- {voxcity-0.3.21.dist-info → voxcity-0.3.22.dist-info}/RECORD +11 -11
- {voxcity-0.3.21.dist-info → voxcity-0.3.22.dist-info}/AUTHORS.rst +0 -0
- {voxcity-0.3.21.dist-info → voxcity-0.3.22.dist-info}/LICENSE +0 -0
- {voxcity-0.3.21.dist-info → voxcity-0.3.22.dist-info}/WHEEL +0 -0
- {voxcity-0.3.21.dist-info → voxcity-0.3.22.dist-info}/top_level.txt +0 -0
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
|
|
399
|
-
"""Get the height difference between DSM and DTM from
|
|
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
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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)
|
voxcity/downloader/mbfp.py
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
voxcity/geoprocessor/grid.py
CHANGED
|
@@ -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 =
|
|
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:
|
voxcity/geoprocessor/polygon.py
CHANGED
|
@@ -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
|
|
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(
|
|
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
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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',
|
|
452
|
-
|
|
453
|
-
#
|
|
454
|
-
|
|
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'] =
|
|
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.
|
|
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
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: voxcity
|
|
3
|
-
Version: 0.3.
|
|
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=
|
|
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=
|
|
6
|
-
voxcity/downloader/mbfp.py,sha256=
|
|
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=
|
|
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=
|
|
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
|
|
@@ -29,9 +29,9 @@ voxcity/utils/lc.py,sha256=RwPd-VY3POV3gTrBhM7TubgGb9MCd3nVah_G8iUEF7k,11562
|
|
|
29
29
|
voxcity/utils/material.py,sha256=Vt3IID5Ft54HNJcEC4zi31BCPqi_687X3CSp7rXaRVY,5907
|
|
30
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.
|
|
33
|
-
voxcity-0.3.
|
|
34
|
-
voxcity-0.3.
|
|
35
|
-
voxcity-0.3.
|
|
36
|
-
voxcity-0.3.
|
|
37
|
-
voxcity-0.3.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|