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.
- voxcity/__init__.py +10 -4
- 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 +9 -1
- voxcity/exporter/cityles.py +129 -34
- voxcity/exporter/envimet.py +51 -26
- voxcity/exporter/magicavoxel.py +42 -5
- voxcity/exporter/netcdf.py +27 -0
- voxcity/exporter/obj.py +103 -28
- voxcity/generator/__init__.py +47 -0
- voxcity/generator/api.py +721 -0
- voxcity/generator/grids.py +381 -0
- voxcity/generator/io.py +94 -0
- voxcity/generator/pipeline.py +282 -0
- voxcity/generator/update.py +429 -0
- voxcity/generator/voxelizer.py +392 -0
- voxcity/geoprocessor/__init__.py +75 -6
- voxcity/geoprocessor/conversion.py +153 -0
- voxcity/geoprocessor/draw.py +1488 -1169
- voxcity/geoprocessor/heights.py +199 -0
- voxcity/geoprocessor/io.py +101 -0
- voxcity/geoprocessor/merge_utils.py +91 -0
- voxcity/geoprocessor/mesh.py +26 -10
- voxcity/geoprocessor/network.py +35 -6
- voxcity/geoprocessor/overlap.py +84 -0
- voxcity/geoprocessor/raster/__init__.py +82 -0
- voxcity/geoprocessor/raster/buildings.py +435 -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 +159 -0
- voxcity/geoprocessor/raster/raster.py +110 -0
- voxcity/geoprocessor/selection.py +85 -0
- voxcity/geoprocessor/utils.py +824 -820
- 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 +66 -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/sky.py +668 -0
- voxcity/simulator/solar/temporal.py +792 -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/__init__.py +11 -0
- voxcity/utils/classes.py +194 -0
- voxcity/utils/lc.py +80 -39
- voxcity/utils/logging.py +61 -0
- voxcity/utils/orientation.py +51 -0
- voxcity/utils/shape.py +230 -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 +1145 -0
- {voxcity-0.6.26.dist-info → voxcity-1.0.2.dist-info}/METADATA +162 -48
- voxcity-1.0.2.dist-info/RECORD +81 -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-1.0.2.dist-info}/WHEEL +0 -0
- {voxcity-0.6.26.dist-info → voxcity-1.0.2.dist-info}/licenses/AUTHORS.rst +0 -0
- {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
|
+
|