voxcity 0.6.26__py3-none-any.whl → 1.0.2__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 (81) hide show
  1. voxcity/__init__.py +10 -4
  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 +9 -1
  10. voxcity/exporter/cityles.py +129 -34
  11. voxcity/exporter/envimet.py +51 -26
  12. voxcity/exporter/magicavoxel.py +42 -5
  13. voxcity/exporter/netcdf.py +27 -0
  14. voxcity/exporter/obj.py +103 -28
  15. voxcity/generator/__init__.py +47 -0
  16. voxcity/generator/api.py +721 -0
  17. voxcity/generator/grids.py +381 -0
  18. voxcity/generator/io.py +94 -0
  19. voxcity/generator/pipeline.py +282 -0
  20. voxcity/generator/update.py +429 -0
  21. voxcity/generator/voxelizer.py +392 -0
  22. voxcity/geoprocessor/__init__.py +75 -6
  23. voxcity/geoprocessor/conversion.py +153 -0
  24. voxcity/geoprocessor/draw.py +1488 -1169
  25. voxcity/geoprocessor/heights.py +199 -0
  26. voxcity/geoprocessor/io.py +101 -0
  27. voxcity/geoprocessor/merge_utils.py +91 -0
  28. voxcity/geoprocessor/mesh.py +26 -10
  29. voxcity/geoprocessor/network.py +35 -6
  30. voxcity/geoprocessor/overlap.py +84 -0
  31. voxcity/geoprocessor/raster/__init__.py +82 -0
  32. voxcity/geoprocessor/raster/buildings.py +435 -0
  33. voxcity/geoprocessor/raster/canopy.py +258 -0
  34. voxcity/geoprocessor/raster/core.py +150 -0
  35. voxcity/geoprocessor/raster/export.py +93 -0
  36. voxcity/geoprocessor/raster/landcover.py +159 -0
  37. voxcity/geoprocessor/raster/raster.py +110 -0
  38. voxcity/geoprocessor/selection.py +85 -0
  39. voxcity/geoprocessor/utils.py +824 -820
  40. voxcity/models.py +113 -0
  41. voxcity/simulator/common/__init__.py +22 -0
  42. voxcity/simulator/common/geometry.py +98 -0
  43. voxcity/simulator/common/raytracing.py +450 -0
  44. voxcity/simulator/solar/__init__.py +66 -0
  45. voxcity/simulator/solar/integration.py +336 -0
  46. voxcity/simulator/solar/kernels.py +62 -0
  47. voxcity/simulator/solar/radiation.py +648 -0
  48. voxcity/simulator/solar/sky.py +668 -0
  49. voxcity/simulator/solar/temporal.py +792 -0
  50. voxcity/simulator/view.py +36 -2286
  51. voxcity/simulator/visibility/__init__.py +29 -0
  52. voxcity/simulator/visibility/landmark.py +392 -0
  53. voxcity/simulator/visibility/view.py +508 -0
  54. voxcity/utils/__init__.py +11 -0
  55. voxcity/utils/classes.py +194 -0
  56. voxcity/utils/lc.py +80 -39
  57. voxcity/utils/logging.py +61 -0
  58. voxcity/utils/orientation.py +51 -0
  59. voxcity/utils/shape.py +230 -0
  60. voxcity/utils/weather/__init__.py +26 -0
  61. voxcity/utils/weather/epw.py +146 -0
  62. voxcity/utils/weather/files.py +36 -0
  63. voxcity/utils/weather/onebuilding.py +486 -0
  64. voxcity/visualizer/__init__.py +24 -0
  65. voxcity/visualizer/builder.py +43 -0
  66. voxcity/visualizer/grids.py +141 -0
  67. voxcity/visualizer/maps.py +187 -0
  68. voxcity/visualizer/palette.py +228 -0
  69. voxcity/visualizer/renderer.py +1145 -0
  70. {voxcity-0.6.26.dist-info → voxcity-1.0.2.dist-info}/METADATA +162 -48
  71. voxcity-1.0.2.dist-info/RECORD +81 -0
  72. voxcity/generator.py +0 -1302
  73. voxcity/geoprocessor/grid.py +0 -1739
  74. voxcity/geoprocessor/polygon.py +0 -1344
  75. voxcity/simulator/solar.py +0 -2339
  76. voxcity/utils/visualization.py +0 -2849
  77. voxcity/utils/weather.py +0 -1038
  78. voxcity-0.6.26.dist-info/RECORD +0 -38
  79. {voxcity-0.6.26.dist-info → voxcity-1.0.2.dist-info}/WHEEL +0 -0
  80. {voxcity-0.6.26.dist-info → voxcity-1.0.2.dist-info}/licenses/AUTHORS.rst +0 -0
  81. {voxcity-0.6.26.dist-info → voxcity-1.0.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,110 @@
1
+ import numpy as np
2
+ from typing import List, Tuple
3
+ from shapely.geometry import Polygon
4
+ from affine import Affine
5
+ from pyproj import Geod, Transformer, CRS
6
+ import rasterio
7
+ from scipy.interpolate import griddata
8
+
9
+
10
+ def create_height_grid_from_geotiff_polygon(
11
+ tiff_path: str,
12
+ mesh_size: float,
13
+ polygon: List[Tuple[float, float]]
14
+ ) -> np.ndarray:
15
+ """
16
+ Create a height grid from a GeoTIFF file within a polygon boundary.
17
+ """
18
+ with rasterio.open(tiff_path) as src:
19
+ img = src.read(1)
20
+ left, bottom, right, top = src.bounds
21
+
22
+ poly = Polygon(polygon)
23
+ left_wgs84, bottom_wgs84, right_wgs84, top_wgs84 = poly.bounds
24
+
25
+ geod = Geod(ellps="WGS84")
26
+ _, _, width = geod.inv(left_wgs84, bottom_wgs84, right_wgs84, bottom_wgs84)
27
+ _, _, height = geod.inv(left_wgs84, bottom_wgs84, left_wgs84, top_wgs84)
28
+
29
+ num_cells_x = int(width / mesh_size + 0.5)
30
+ num_cells_y = int(height / mesh_size + 0.5)
31
+
32
+ adjusted_mesh_size_x = (right - left) / num_cells_x
33
+ adjusted_mesh_size_y = (top - bottom) / num_cells_y
34
+
35
+ new_affine = Affine(adjusted_mesh_size_x, 0, left, 0, -adjusted_mesh_size_y, top)
36
+
37
+ cols, rows = np.meshgrid(np.arange(num_cells_x), np.arange(num_cells_y))
38
+ xs, ys = new_affine * (cols, rows)
39
+ xs_flat, ys_flat = xs.flatten(), ys.flatten()
40
+
41
+ row, col = src.index(xs_flat, ys_flat)
42
+ row, col = np.array(row), np.array(col)
43
+
44
+ valid = (row >= 0) & (row < src.height) & (col >= 0) & (col < src.width)
45
+ row, col = row[valid], col[valid]
46
+
47
+ grid = np.full((num_cells_y, num_cells_x), np.nan)
48
+ flat_indices = np.ravel_multi_index((row, col), img.shape)
49
+ np.put(grid, np.ravel_multi_index((rows.flatten()[valid], cols.flatten()[valid]), grid.shape), img.flat[flat_indices])
50
+
51
+ return np.flipud(grid)
52
+
53
+
54
+ def create_dem_grid_from_geotiff_polygon(tiff_path, mesh_size, rectangle_vertices, dem_interpolation=False):
55
+ """
56
+ Create a Digital Elevation Model (DEM) grid from a GeoTIFF within a polygon boundary.
57
+ """
58
+ from shapely.geometry import Polygon as ShapelyPolygon
59
+ from ..utils import convert_format_lat_lon
60
+
61
+ converted_coords = convert_format_lat_lon(rectangle_vertices)
62
+ roi_shapely = ShapelyPolygon(converted_coords)
63
+
64
+ with rasterio.open(tiff_path) as src:
65
+ dem = src.read(1)
66
+ dem = np.where(dem < -1000, 0, dem)
67
+ transform = src.transform
68
+ src_crs = src.crs
69
+
70
+ if src_crs.to_epsg() != 3857:
71
+ transformer_to_3857 = Transformer.from_crs(src_crs, CRS.from_epsg(3857), always_xy=True)
72
+ else:
73
+ transformer_to_3857 = lambda x, y: (x, y)
74
+
75
+ roi_bounds = roi_shapely.bounds
76
+ roi_left, roi_bottom = transformer_to_3857.transform(roi_bounds[0], roi_bounds[1])
77
+ roi_right, roi_top = transformer_to_3857.transform(roi_bounds[2], roi_bounds[3])
78
+
79
+ wgs84 = CRS.from_epsg(4326)
80
+ transformer_to_wgs84 = Transformer.from_crs(CRS.from_epsg(3857), wgs84, always_xy=True)
81
+ roi_left_wgs84, roi_bottom_wgs84 = transformer_to_wgs84.transform(roi_left, roi_bottom)
82
+ roi_right_wgs84, roi_top_wgs84 = transformer_to_wgs84.transform(roi_right, roi_top)
83
+
84
+ geod = Geod(ellps="WGS84")
85
+ _, _, roi_width_m = geod.inv(roi_left_wgs84, roi_bottom_wgs84, roi_right_wgs84, roi_bottom_wgs84)
86
+ _, _, roi_height_m = geod.inv(roi_left_wgs84, roi_bottom_wgs84, roi_left_wgs84, roi_top_wgs84)
87
+
88
+ num_cells_x = int(roi_width_m / mesh_size + 0.5)
89
+ num_cells_y = int(roi_height_m / mesh_size + 0.5)
90
+
91
+ x = np.linspace(roi_left, roi_right, num_cells_x, endpoint=False)
92
+ y = np.linspace(roi_top, roi_bottom, num_cells_y, endpoint=False)
93
+ xx, yy = np.meshgrid(x, y)
94
+
95
+ rows, cols = np.meshgrid(range(dem.shape[0]), range(dem.shape[1]), indexing='ij')
96
+ orig_x, orig_y = rasterio.transform.xy(transform, rows.ravel(), cols.ravel())
97
+ orig_x, orig_y = transformer_to_3857.transform(orig_x, orig_y)
98
+
99
+ points = np.column_stack((orig_x, orig_y))
100
+ values = dem.ravel()
101
+ if dem_interpolation:
102
+ grid = griddata(points, values, (xx, yy), method='cubic')
103
+ else:
104
+ grid = griddata(points, values, (xx, yy), method='nearest')
105
+
106
+ return np.flipud(grid)
107
+
108
+
109
+
110
+
@@ -0,0 +1,85 @@
1
+ """
2
+ Selection and filtering helpers for building footprints.
3
+ """
4
+
5
+ from typing import List, Dict, Tuple
6
+
7
+ from shapely.geometry import Polygon, Point, shape
8
+ from shapely.errors import ShapelyError
9
+
10
+ from .utils import validate_polygon_coordinates
11
+ from ..utils.logging import get_logger
12
+
13
+
14
+ def filter_buildings(geojson_data, plotting_box):
15
+ """
16
+ Filter building features that intersect with a given bounding box.
17
+ """
18
+ logger = get_logger(__name__)
19
+ filtered_features = []
20
+
21
+ for feature in geojson_data:
22
+ if not validate_polygon_coordinates(feature['geometry']):
23
+ logger.warning("Skipping feature with invalid geometry: %s", feature.get('geometry'))
24
+ continue
25
+
26
+ try:
27
+ geom = shape(feature['geometry'])
28
+ if not geom.is_valid:
29
+ logger.warning("Skipping invalid geometry: %s", geom)
30
+ continue
31
+
32
+ if plotting_box.intersects(geom):
33
+ filtered_features.append(feature)
34
+
35
+ except ShapelyError as e:
36
+ logger.warning("Skipping feature due to geometry error: %s", e)
37
+
38
+ return filtered_features
39
+
40
+
41
+ def find_building_containing_point(building_gdf, target_point):
42
+ """
43
+ Find building IDs that contain a given point in their footprint.
44
+ """
45
+ point = Point(target_point[0], target_point[1])
46
+
47
+ id_list = []
48
+ for _, row in building_gdf.iterrows():
49
+ if not isinstance(row.geometry, Polygon):
50
+ continue
51
+ if row.geometry.contains(point):
52
+ id_list.append(row.get('id', None))
53
+
54
+ return id_list
55
+
56
+
57
+ def get_buildings_in_drawn_polygon(building_gdf, drawn_polygons, operation='within'):
58
+ """
59
+ Find buildings that intersect with or are contained within user-drawn polygons.
60
+ """
61
+ if not drawn_polygons:
62
+ return []
63
+
64
+ included_building_ids = set()
65
+
66
+ for polygon_data in drawn_polygons:
67
+ vertices = polygon_data['vertices']
68
+ drawn_polygon_shapely = Polygon(vertices)
69
+
70
+ for _, row in building_gdf.iterrows():
71
+ if not isinstance(row.geometry, Polygon):
72
+ continue
73
+
74
+ if operation == 'intersect':
75
+ if row.geometry.intersects(drawn_polygon_shapely):
76
+ included_building_ids.add(row.get('id', None))
77
+ elif operation == 'within':
78
+ if row.geometry.within(drawn_polygon_shapely):
79
+ included_building_ids.add(row.get('id', None))
80
+ else:
81
+ raise ValueError("operation must be 'intersect' or 'within'")
82
+
83
+ return list(included_building_ids)
84
+
85
+