voxcity 0.6.26__py3-none-any.whl → 0.7.0__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.
- voxcity/__init__.py +14 -8
- voxcity/downloader/__init__.py +2 -1
- voxcity/downloader/gba.py +210 -0
- voxcity/downloader/gee.py +5 -1
- voxcity/downloader/mbfp.py +1 -1
- voxcity/downloader/oemj.py +80 -8
- voxcity/downloader/utils.py +73 -73
- voxcity/errors.py +30 -0
- voxcity/exporter/__init__.py +13 -5
- voxcity/exporter/cityles.py +633 -538
- voxcity/exporter/envimet.py +728 -708
- voxcity/exporter/magicavoxel.py +334 -297
- voxcity/exporter/netcdf.py +238 -211
- voxcity/exporter/obj.py +1481 -1406
- voxcity/generator/__init__.py +44 -0
- voxcity/generator/api.py +675 -0
- voxcity/generator/grids.py +379 -0
- voxcity/generator/io.py +94 -0
- voxcity/generator/pipeline.py +282 -0
- voxcity/generator/voxelizer.py +380 -0
- voxcity/geoprocessor/__init__.py +75 -6
- voxcity/geoprocessor/conversion.py +153 -0
- voxcity/geoprocessor/draw.py +62 -12
- voxcity/geoprocessor/heights.py +199 -0
- voxcity/geoprocessor/io.py +101 -0
- voxcity/geoprocessor/merge_utils.py +91 -0
- voxcity/geoprocessor/mesh.py +806 -790
- voxcity/geoprocessor/network.py +708 -679
- voxcity/geoprocessor/overlap.py +84 -0
- voxcity/geoprocessor/raster/__init__.py +82 -0
- voxcity/geoprocessor/raster/buildings.py +428 -0
- voxcity/geoprocessor/raster/canopy.py +258 -0
- voxcity/geoprocessor/raster/core.py +150 -0
- voxcity/geoprocessor/raster/export.py +93 -0
- voxcity/geoprocessor/raster/landcover.py +156 -0
- voxcity/geoprocessor/raster/raster.py +110 -0
- voxcity/geoprocessor/selection.py +85 -0
- voxcity/geoprocessor/utils.py +18 -14
- voxcity/models.py +113 -0
- voxcity/simulator/common/__init__.py +22 -0
- voxcity/simulator/common/geometry.py +98 -0
- voxcity/simulator/common/raytracing.py +450 -0
- voxcity/simulator/solar/__init__.py +43 -0
- voxcity/simulator/solar/integration.py +336 -0
- voxcity/simulator/solar/kernels.py +62 -0
- voxcity/simulator/solar/radiation.py +648 -0
- voxcity/simulator/solar/temporal.py +434 -0
- voxcity/simulator/view.py +36 -2286
- voxcity/simulator/visibility/__init__.py +29 -0
- voxcity/simulator/visibility/landmark.py +392 -0
- voxcity/simulator/visibility/view.py +508 -0
- voxcity/utils/logging.py +61 -0
- voxcity/utils/orientation.py +51 -0
- voxcity/utils/weather/__init__.py +26 -0
- voxcity/utils/weather/epw.py +146 -0
- voxcity/utils/weather/files.py +36 -0
- voxcity/utils/weather/onebuilding.py +486 -0
- voxcity/visualizer/__init__.py +24 -0
- voxcity/visualizer/builder.py +43 -0
- voxcity/visualizer/grids.py +141 -0
- voxcity/visualizer/maps.py +187 -0
- voxcity/visualizer/palette.py +228 -0
- voxcity/visualizer/renderer.py +928 -0
- {voxcity-0.6.26.dist-info → voxcity-0.7.0.dist-info}/METADATA +107 -34
- voxcity-0.7.0.dist-info/RECORD +77 -0
- voxcity/generator.py +0 -1302
- voxcity/geoprocessor/grid.py +0 -1739
- voxcity/geoprocessor/polygon.py +0 -1344
- voxcity/simulator/solar.py +0 -2339
- voxcity/utils/visualization.py +0 -2849
- voxcity/utils/weather.py +0 -1038
- voxcity-0.6.26.dist-info/RECORD +0 -38
- {voxcity-0.6.26.dist-info → voxcity-0.7.0.dist-info}/WHEEL +0 -0
- {voxcity-0.6.26.dist-info → voxcity-0.7.0.dist-info}/licenses/AUTHORS.rst +0 -0
- {voxcity-0.6.26.dist-info → voxcity-0.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""
|
|
2
|
+
I/O helpers for reading/writing vector data (GPKG, gzipped GeoJSON lines) and
|
|
3
|
+
saving FeatureCollections.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import copy
|
|
7
|
+
import gzip
|
|
8
|
+
import json
|
|
9
|
+
from typing import List
|
|
10
|
+
|
|
11
|
+
import geopandas as gpd
|
|
12
|
+
|
|
13
|
+
from .conversion import filter_and_convert_gdf_to_geojson
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_geojson_from_gpkg(gpkg_path, rectangle_vertices):
|
|
17
|
+
"""
|
|
18
|
+
Read a GeoPackage file and convert it to GeoJSON features within a bounding rectangle.
|
|
19
|
+
"""
|
|
20
|
+
print(f"Opening GPKG file: {gpkg_path}")
|
|
21
|
+
gdf = gpd.read_file(gpkg_path)
|
|
22
|
+
geojson = filter_and_convert_gdf_to_geojson(gdf, rectangle_vertices)
|
|
23
|
+
return geojson
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_gdf_from_gpkg(gpkg_path, rectangle_vertices):
|
|
27
|
+
"""
|
|
28
|
+
Read a GeoPackage file and convert it to a GeoDataFrame with consistent CRS.
|
|
29
|
+
|
|
30
|
+
Note: rectangle_vertices is currently unused but kept for signature compatibility.
|
|
31
|
+
"""
|
|
32
|
+
print(f"Opening GPKG file: {gpkg_path}")
|
|
33
|
+
gdf = gpd.read_file(gpkg_path)
|
|
34
|
+
|
|
35
|
+
if gdf.crs is None:
|
|
36
|
+
gdf.set_crs(epsg=4326, inplace=True)
|
|
37
|
+
elif gdf.crs != "EPSG:4326":
|
|
38
|
+
gdf = gdf.to_crs(epsg=4326)
|
|
39
|
+
|
|
40
|
+
gdf['id'] = gdf.index
|
|
41
|
+
return gdf
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def load_gdf_from_multiple_gz(file_paths):
|
|
45
|
+
"""
|
|
46
|
+
Load GeoJSON features from multiple gzipped files into a single GeoDataFrame.
|
|
47
|
+
Each line in each file must be a single GeoJSON Feature.
|
|
48
|
+
"""
|
|
49
|
+
geojson_objects = []
|
|
50
|
+
|
|
51
|
+
for gz_file_path in file_paths:
|
|
52
|
+
with gzip.open(gz_file_path, 'rt', encoding='utf-8') as file:
|
|
53
|
+
for line in file:
|
|
54
|
+
try:
|
|
55
|
+
data = json.loads(line)
|
|
56
|
+
if 'properties' in data and 'height' in data['properties']:
|
|
57
|
+
if data['properties']['height'] is None:
|
|
58
|
+
data['properties']['height'] = 0
|
|
59
|
+
else:
|
|
60
|
+
if 'properties' not in data:
|
|
61
|
+
data['properties'] = {}
|
|
62
|
+
data['properties']['height'] = 0
|
|
63
|
+
geojson_objects.append(data)
|
|
64
|
+
except json.JSONDecodeError as e:
|
|
65
|
+
print(f"Skipping line in {gz_file_path} due to JSONDecodeError: {e}")
|
|
66
|
+
|
|
67
|
+
gdf = gpd.GeoDataFrame.from_features(geojson_objects)
|
|
68
|
+
gdf.set_crs(epsg=4326, inplace=True)
|
|
69
|
+
return gdf
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def swap_coordinates(features):
|
|
73
|
+
"""
|
|
74
|
+
Swap coordinate ordering in GeoJSON features from (lat, lon) to (lon, lat).
|
|
75
|
+
Modifies the input features in-place.
|
|
76
|
+
"""
|
|
77
|
+
for feature in features:
|
|
78
|
+
if feature['geometry']['type'] == 'Polygon':
|
|
79
|
+
new_coords = [[[lon, lat] for lat, lon in polygon] for polygon in feature['geometry']['coordinates']]
|
|
80
|
+
feature['geometry']['coordinates'] = new_coords
|
|
81
|
+
elif feature['geometry']['type'] == 'MultiPolygon':
|
|
82
|
+
new_coords = [[[[lon, lat] for lat, lon in polygon] for polygon in multipolygon] for multipolygon in feature['geometry']['coordinates']]
|
|
83
|
+
feature['geometry']['coordinates'] = new_coords
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def save_geojson(features, save_path):
|
|
87
|
+
"""
|
|
88
|
+
Save GeoJSON features to a file with coordinate swapping and pretty printing.
|
|
89
|
+
"""
|
|
90
|
+
geojson_features = copy.deepcopy(features)
|
|
91
|
+
swap_coordinates(geojson_features)
|
|
92
|
+
|
|
93
|
+
geojson = {
|
|
94
|
+
"type": "FeatureCollection",
|
|
95
|
+
"features": geojson_features
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
with open(save_path, 'w') as f:
|
|
99
|
+
json.dump(geojson, f, indent=2)
|
|
100
|
+
|
|
101
|
+
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utilities to merge GeoDataFrames while resolving ID conflicts.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _merge_gdfs_with_missing_columns(gdf_1, gdf_2):
|
|
9
|
+
"""
|
|
10
|
+
Helper to merge two GeoDataFrames while handling missing columns by filling with None.
|
|
11
|
+
"""
|
|
12
|
+
columns_1 = set(gdf_1.columns)
|
|
13
|
+
columns_2 = set(gdf_2.columns)
|
|
14
|
+
|
|
15
|
+
only_in_1 = columns_1 - columns_2
|
|
16
|
+
only_in_2 = columns_2 - columns_1
|
|
17
|
+
|
|
18
|
+
for col in only_in_2:
|
|
19
|
+
gdf_1[col] = None
|
|
20
|
+
for col in only_in_1:
|
|
21
|
+
gdf_2[col] = None
|
|
22
|
+
|
|
23
|
+
all_columns = sorted(list(columns_1.union(columns_2)))
|
|
24
|
+
gdf_1 = gdf_1[all_columns]
|
|
25
|
+
gdf_2 = gdf_2[all_columns]
|
|
26
|
+
|
|
27
|
+
merged_gdf = pd.concat([gdf_1, gdf_2], ignore_index=True)
|
|
28
|
+
return merged_gdf
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def merge_gdfs_with_id_conflict_resolution(gdf_1, gdf_2, id_columns=['id', 'building_id']):
|
|
32
|
+
"""
|
|
33
|
+
Merge two GeoDataFrames while resolving ID conflicts by modifying IDs in the second GeoDataFrame.
|
|
34
|
+
"""
|
|
35
|
+
gdf_primary = gdf_1.copy()
|
|
36
|
+
gdf_secondary = gdf_2.copy()
|
|
37
|
+
|
|
38
|
+
missing_columns = []
|
|
39
|
+
for col in id_columns:
|
|
40
|
+
if col not in gdf_primary.columns:
|
|
41
|
+
missing_columns.append(f"'{col}' missing from gdf_1")
|
|
42
|
+
if col not in gdf_secondary.columns:
|
|
43
|
+
missing_columns.append(f"'{col}' missing from gdf_2")
|
|
44
|
+
|
|
45
|
+
if missing_columns:
|
|
46
|
+
print(f"Warning: Missing ID columns: {', '.join(missing_columns)}")
|
|
47
|
+
id_columns = [col for col in id_columns if col in gdf_primary.columns and col in gdf_secondary.columns]
|
|
48
|
+
|
|
49
|
+
if not id_columns:
|
|
50
|
+
print("Warning: No valid ID columns found. Merging without ID conflict resolution.")
|
|
51
|
+
merged_gdf = _merge_gdfs_with_missing_columns(gdf_primary, gdf_secondary)
|
|
52
|
+
return merged_gdf
|
|
53
|
+
|
|
54
|
+
max_ids = {}
|
|
55
|
+
for col in id_columns:
|
|
56
|
+
if gdf_primary[col].dtype in ['int64', 'int32', 'float64', 'float32']:
|
|
57
|
+
max_ids[col] = gdf_primary[col].max()
|
|
58
|
+
else:
|
|
59
|
+
max_ids[col] = len(gdf_primary)
|
|
60
|
+
|
|
61
|
+
next_ids = {col: max_ids[col] + 1 for col in id_columns}
|
|
62
|
+
modified_buildings = 0
|
|
63
|
+
|
|
64
|
+
for idx, row in gdf_secondary.iterrows():
|
|
65
|
+
needs_new_ids = False
|
|
66
|
+
for col in id_columns:
|
|
67
|
+
current_id = row[col]
|
|
68
|
+
if current_id in gdf_primary[col].values:
|
|
69
|
+
needs_new_ids = True
|
|
70
|
+
break
|
|
71
|
+
if needs_new_ids:
|
|
72
|
+
modified_buildings += 1
|
|
73
|
+
for col in id_columns:
|
|
74
|
+
new_id = next_ids[col]
|
|
75
|
+
gdf_secondary.at[idx, col] = new_id
|
|
76
|
+
next_ids[col] += 1
|
|
77
|
+
|
|
78
|
+
merged_gdf = _merge_gdfs_with_missing_columns(gdf_primary, gdf_secondary)
|
|
79
|
+
|
|
80
|
+
total_buildings = len(merged_gdf)
|
|
81
|
+
primary_buildings = len(gdf_primary)
|
|
82
|
+
secondary_buildings = len(gdf_secondary)
|
|
83
|
+
|
|
84
|
+
print(f"Merged {primary_buildings} buildings from primary dataset with {secondary_buildings} buildings from secondary dataset.")
|
|
85
|
+
print(f"Total buildings in merged dataset: {total_buildings}")
|
|
86
|
+
if modified_buildings > 0:
|
|
87
|
+
print(f"Modified IDs for {modified_buildings} buildings in secondary dataset to resolve conflicts.")
|
|
88
|
+
|
|
89
|
+
return merged_gdf
|
|
90
|
+
|
|
91
|
+
|