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.
Files changed (75) hide show
  1. voxcity/__init__.py +14 -8
  2. voxcity/downloader/__init__.py +2 -1
  3. voxcity/downloader/gba.py +210 -0
  4. voxcity/downloader/gee.py +5 -1
  5. voxcity/downloader/mbfp.py +1 -1
  6. voxcity/downloader/oemj.py +80 -8
  7. voxcity/downloader/utils.py +73 -73
  8. voxcity/errors.py +30 -0
  9. voxcity/exporter/__init__.py +13 -5
  10. voxcity/exporter/cityles.py +633 -538
  11. voxcity/exporter/envimet.py +728 -708
  12. voxcity/exporter/magicavoxel.py +334 -297
  13. voxcity/exporter/netcdf.py +238 -211
  14. voxcity/exporter/obj.py +1481 -1406
  15. voxcity/generator/__init__.py +44 -0
  16. voxcity/generator/api.py +675 -0
  17. voxcity/generator/grids.py +379 -0
  18. voxcity/generator/io.py +94 -0
  19. voxcity/generator/pipeline.py +282 -0
  20. voxcity/generator/voxelizer.py +380 -0
  21. voxcity/geoprocessor/__init__.py +75 -6
  22. voxcity/geoprocessor/conversion.py +153 -0
  23. voxcity/geoprocessor/draw.py +62 -12
  24. voxcity/geoprocessor/heights.py +199 -0
  25. voxcity/geoprocessor/io.py +101 -0
  26. voxcity/geoprocessor/merge_utils.py +91 -0
  27. voxcity/geoprocessor/mesh.py +806 -790
  28. voxcity/geoprocessor/network.py +708 -679
  29. voxcity/geoprocessor/overlap.py +84 -0
  30. voxcity/geoprocessor/raster/__init__.py +82 -0
  31. voxcity/geoprocessor/raster/buildings.py +428 -0
  32. voxcity/geoprocessor/raster/canopy.py +258 -0
  33. voxcity/geoprocessor/raster/core.py +150 -0
  34. voxcity/geoprocessor/raster/export.py +93 -0
  35. voxcity/geoprocessor/raster/landcover.py +156 -0
  36. voxcity/geoprocessor/raster/raster.py +110 -0
  37. voxcity/geoprocessor/selection.py +85 -0
  38. voxcity/geoprocessor/utils.py +18 -14
  39. voxcity/models.py +113 -0
  40. voxcity/simulator/common/__init__.py +22 -0
  41. voxcity/simulator/common/geometry.py +98 -0
  42. voxcity/simulator/common/raytracing.py +450 -0
  43. voxcity/simulator/solar/__init__.py +43 -0
  44. voxcity/simulator/solar/integration.py +336 -0
  45. voxcity/simulator/solar/kernels.py +62 -0
  46. voxcity/simulator/solar/radiation.py +648 -0
  47. voxcity/simulator/solar/temporal.py +434 -0
  48. voxcity/simulator/view.py +36 -2286
  49. voxcity/simulator/visibility/__init__.py +29 -0
  50. voxcity/simulator/visibility/landmark.py +392 -0
  51. voxcity/simulator/visibility/view.py +508 -0
  52. voxcity/utils/logging.py +61 -0
  53. voxcity/utils/orientation.py +51 -0
  54. voxcity/utils/weather/__init__.py +26 -0
  55. voxcity/utils/weather/epw.py +146 -0
  56. voxcity/utils/weather/files.py +36 -0
  57. voxcity/utils/weather/onebuilding.py +486 -0
  58. voxcity/visualizer/__init__.py +24 -0
  59. voxcity/visualizer/builder.py +43 -0
  60. voxcity/visualizer/grids.py +141 -0
  61. voxcity/visualizer/maps.py +187 -0
  62. voxcity/visualizer/palette.py +228 -0
  63. voxcity/visualizer/renderer.py +928 -0
  64. {voxcity-0.6.26.dist-info → voxcity-0.7.0.dist-info}/METADATA +107 -34
  65. voxcity-0.7.0.dist-info/RECORD +77 -0
  66. voxcity/generator.py +0 -1302
  67. voxcity/geoprocessor/grid.py +0 -1739
  68. voxcity/geoprocessor/polygon.py +0 -1344
  69. voxcity/simulator/solar.py +0 -2339
  70. voxcity/utils/visualization.py +0 -2849
  71. voxcity/utils/weather.py +0 -1038
  72. voxcity-0.6.26.dist-info/RECORD +0 -38
  73. {voxcity-0.6.26.dist-info → voxcity-0.7.0.dist-info}/WHEEL +0 -0
  74. {voxcity-0.6.26.dist-info → voxcity-0.7.0.dist-info}/licenses/AUTHORS.rst +0 -0
  75. {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
+