voxcity 0.3.4__tar.gz → 0.3.6__tar.gz
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-0.3.4 → voxcity-0.3.6}/PKG-INFO +36 -3
- {voxcity-0.3.4 → voxcity-0.3.6}/README.md +34 -2
- {voxcity-0.3.4 → voxcity-0.3.6}/pyproject.toml +2 -1
- voxcity-0.3.6/src/voxcity/geo/__init_.py +4 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/geo/grid.py +59 -2
- voxcity-0.3.6/src/voxcity/geo/network.py +194 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/sim/solar.py +5 -5
- voxcity-0.3.6/src/voxcity/utils/__init_.py +4 -0
- voxcity-0.3.6/src/voxcity/utils/material.py +139 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/utils/visualization.py +128 -354
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/utils/weather.py +2 -2
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/voxcity.py +56 -8
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity.egg-info/PKG-INFO +36 -3
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity.egg-info/SOURCES.txt +2 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity.egg-info/requires.txt +1 -0
- voxcity-0.3.4/src/voxcity/geo/__init_.py +0 -3
- voxcity-0.3.4/src/voxcity/utils/__init_.py +0 -3
- {voxcity-0.3.4 → voxcity-0.3.6}/AUTHORS.rst +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/CONTRIBUTING.rst +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/HISTORY.rst +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/LICENSE +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/MANIFEST.in +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/docs/Makefile +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/docs/archive/README.rst +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/docs/authors.rst +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/docs/conf.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/docs/index.rst +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/docs/make.bat +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/setup.cfg +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/__init__.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/download/__init__.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/download/eubucco.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/download/gee.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/download/mbfp.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/download/oemj.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/download/omt.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/download/osm.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/download/overture.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/download/utils.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/file/__init_.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/file/envimet.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/file/geojson.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/file/magicavoxel.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/file/obj.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/geo/draw.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/geo/utils.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/sim/__init_.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/sim/utils.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/sim/view.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity/utils/lc.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity.egg-info/dependency_links.txt +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/src/voxcity.egg-info/top_level.txt +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/tests/__init__.py +0 -0
- {voxcity-0.3.4 → voxcity-0.3.6}/tests/voxelcity.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: voxcity
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
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>
|
|
@@ -48,6 +48,7 @@ Requires-Dist: overturemaps
|
|
|
48
48
|
Requires-Dist: protobuf==3.20.3
|
|
49
49
|
Requires-Dist: timezonefinder
|
|
50
50
|
Requires-Dist: astral
|
|
51
|
+
Requires-Dist: osmnx
|
|
51
52
|
Provides-Extra: dev
|
|
52
53
|
Requires-Dist: coverage; extra == "dev"
|
|
53
54
|
Requires-Dist: mypy; extra == "dev"
|
|
@@ -329,7 +330,7 @@ solar_kwargs = {
|
|
|
329
330
|
}
|
|
330
331
|
|
|
331
332
|
# Compute global solar irradiance map (direct + diffuse radiation)
|
|
332
|
-
|
|
333
|
+
solar_grid = get_global_solar_irradiance_using_epw(
|
|
333
334
|
voxcity_grid, # 3D voxel grid representing the urban environment
|
|
334
335
|
meshsize, # Size of each voxel in meters
|
|
335
336
|
calc_type='instantaneous', # Calculate instantaneous irradiance at specified time
|
|
@@ -344,7 +345,7 @@ solar_kwargs["end_time"] = "01-31 23:00:00" # End time for cumulative calculatio
|
|
|
344
345
|
solar_kwargs["output_file_name"] = 'cummulative_solar_irradiance', # Base filename for outputs (without extension)
|
|
345
346
|
|
|
346
347
|
# Calculate cumulative solar irradiance over the specified time period
|
|
347
|
-
|
|
348
|
+
cum_solar_grid = get_global_solar_irradiance_using_epw(
|
|
348
349
|
voxcity_grid, # 3D voxel grid representing the urban environment
|
|
349
350
|
meshsize, # Size of each voxel in meters
|
|
350
351
|
calc_type='cumulative', # Calculate cumulative irradiance over time period instead of instantaneous
|
|
@@ -417,6 +418,38 @@ landmark_vis_map = get_landmark_visibility_map(voxcity_grid, building_id_grid, b
|
|
|
417
418
|
<em>Example Result Saved as OBJ and Rendered in Rhino</em>
|
|
418
419
|
</p>
|
|
419
420
|
|
|
421
|
+
#### Network Analysis:
|
|
422
|
+
|
|
423
|
+
```python
|
|
424
|
+
from voxcity.geo.network import get_network_values
|
|
425
|
+
|
|
426
|
+
network_kwargs = {
|
|
427
|
+
"network_type": "walk", # Type of network to download from OSM (walk, drive, all, etc.)
|
|
428
|
+
"colormap": "magma", # Matplotlib colormap for visualization
|
|
429
|
+
"vis_graph": True, # Whether to display the network visualization
|
|
430
|
+
"vmin": 0.0, # Minimum value for color scaling
|
|
431
|
+
"vmax": 600000, # Maximum value for color scaling
|
|
432
|
+
"edge_width": 2, # Width of network edges in visualization
|
|
433
|
+
"alpha": 0.8, # Transparency of network edges
|
|
434
|
+
"zoom": 16 # Zoom level for basemap
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
G, edge_gdf = get_network_values(
|
|
438
|
+
cum_solar_grid, # Grid of cumulative solar irradiance values
|
|
439
|
+
rectangle_vertices, # Coordinates defining simulation domain boundary
|
|
440
|
+
meshsize, # Size of each grid cell in meters
|
|
441
|
+
value_name='Cumulative Global Solar Irradiance (W/m²·hour)', # Label for values in visualization
|
|
442
|
+
**network_kwargs # Additional visualization and network parameters
|
|
443
|
+
)
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
<p align="center">
|
|
447
|
+
<img src="https://raw.githubusercontent.com/kunifujiwara/VoxCity/main/images/network.png" alt="Example of Graph Output" width="500">
|
|
448
|
+
</p>
|
|
449
|
+
<p align="center">
|
|
450
|
+
<em>Example Result Saved as OBJ and Rendered in Rhino</em>
|
|
451
|
+
</p>
|
|
452
|
+
|
|
420
453
|
## References of Data Sources
|
|
421
454
|
|
|
422
455
|
### Building
|
|
@@ -273,7 +273,7 @@ solar_kwargs = {
|
|
|
273
273
|
}
|
|
274
274
|
|
|
275
275
|
# Compute global solar irradiance map (direct + diffuse radiation)
|
|
276
|
-
|
|
276
|
+
solar_grid = get_global_solar_irradiance_using_epw(
|
|
277
277
|
voxcity_grid, # 3D voxel grid representing the urban environment
|
|
278
278
|
meshsize, # Size of each voxel in meters
|
|
279
279
|
calc_type='instantaneous', # Calculate instantaneous irradiance at specified time
|
|
@@ -288,7 +288,7 @@ solar_kwargs["end_time"] = "01-31 23:00:00" # End time for cumulative calculatio
|
|
|
288
288
|
solar_kwargs["output_file_name"] = 'cummulative_solar_irradiance', # Base filename for outputs (without extension)
|
|
289
289
|
|
|
290
290
|
# Calculate cumulative solar irradiance over the specified time period
|
|
291
|
-
|
|
291
|
+
cum_solar_grid = get_global_solar_irradiance_using_epw(
|
|
292
292
|
voxcity_grid, # 3D voxel grid representing the urban environment
|
|
293
293
|
meshsize, # Size of each voxel in meters
|
|
294
294
|
calc_type='cumulative', # Calculate cumulative irradiance over time period instead of instantaneous
|
|
@@ -361,6 +361,38 @@ landmark_vis_map = get_landmark_visibility_map(voxcity_grid, building_id_grid, b
|
|
|
361
361
|
<em>Example Result Saved as OBJ and Rendered in Rhino</em>
|
|
362
362
|
</p>
|
|
363
363
|
|
|
364
|
+
#### Network Analysis:
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
from voxcity.geo.network import get_network_values
|
|
368
|
+
|
|
369
|
+
network_kwargs = {
|
|
370
|
+
"network_type": "walk", # Type of network to download from OSM (walk, drive, all, etc.)
|
|
371
|
+
"colormap": "magma", # Matplotlib colormap for visualization
|
|
372
|
+
"vis_graph": True, # Whether to display the network visualization
|
|
373
|
+
"vmin": 0.0, # Minimum value for color scaling
|
|
374
|
+
"vmax": 600000, # Maximum value for color scaling
|
|
375
|
+
"edge_width": 2, # Width of network edges in visualization
|
|
376
|
+
"alpha": 0.8, # Transparency of network edges
|
|
377
|
+
"zoom": 16 # Zoom level for basemap
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
G, edge_gdf = get_network_values(
|
|
381
|
+
cum_solar_grid, # Grid of cumulative solar irradiance values
|
|
382
|
+
rectangle_vertices, # Coordinates defining simulation domain boundary
|
|
383
|
+
meshsize, # Size of each grid cell in meters
|
|
384
|
+
value_name='Cumulative Global Solar Irradiance (W/m²·hour)', # Label for values in visualization
|
|
385
|
+
**network_kwargs # Additional visualization and network parameters
|
|
386
|
+
)
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
<p align="center">
|
|
390
|
+
<img src="https://raw.githubusercontent.com/kunifujiwara/VoxCity/main/images/network.png" alt="Example of Graph Output" width="500">
|
|
391
|
+
</p>
|
|
392
|
+
<p align="center">
|
|
393
|
+
<em>Example Result Saved as OBJ and Rendered in Rhino</em>
|
|
394
|
+
</p>
|
|
395
|
+
|
|
364
396
|
## References of Data Sources
|
|
365
397
|
|
|
366
398
|
### Building
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "voxcity"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.6"
|
|
4
4
|
requires-python = ">=3.10,<3.13"
|
|
5
5
|
classifiers = [
|
|
6
6
|
"Programming Language :: Python :: 3.10",
|
|
@@ -50,6 +50,7 @@ dependencies = [
|
|
|
50
50
|
"protobuf==3.20.3",
|
|
51
51
|
"timezonefinder",
|
|
52
52
|
"astral",
|
|
53
|
+
"osmnx",
|
|
53
54
|
]
|
|
54
55
|
|
|
55
56
|
[project.optional-dependencies]
|
|
@@ -4,7 +4,7 @@ This module provides functions for creating and manipulating grids of building h
|
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import os
|
|
7
|
-
from shapely.geometry import Polygon
|
|
7
|
+
from shapely.geometry import Polygon, box
|
|
8
8
|
from scipy.ndimage import label, generate_binary_structure
|
|
9
9
|
from pyproj import Geod, Transformer, CRS
|
|
10
10
|
import rasterio
|
|
@@ -12,6 +12,7 @@ from affine import Affine
|
|
|
12
12
|
from shapely.geometry import box
|
|
13
13
|
from scipy.interpolate import griddata
|
|
14
14
|
from shapely.errors import GEOSException
|
|
15
|
+
import geopandas as gpd
|
|
15
16
|
|
|
16
17
|
from .utils import (
|
|
17
18
|
initialize_geod,
|
|
@@ -748,4 +749,60 @@ def create_dem_grid_from_geotiff_polygon(tiff_path, mesh_size, rectangle_vertice
|
|
|
748
749
|
# Use nearest neighbor interpolation for raw data
|
|
749
750
|
grid = griddata(points, values, (xx, yy), method='nearest')
|
|
750
751
|
|
|
751
|
-
return np.flipud(grid)
|
|
752
|
+
return np.flipud(grid)
|
|
753
|
+
|
|
754
|
+
def grid_to_geodataframe(grid_ori, rectangle_vertices, meshsize):
|
|
755
|
+
"""Converts a 2D grid to a GeoDataFrame with cell polygons and values.
|
|
756
|
+
|
|
757
|
+
Args:
|
|
758
|
+
grid: 2D numpy array containing grid values
|
|
759
|
+
rectangle_vertices: List of [lon, lat] coordinates defining area corners
|
|
760
|
+
meshsize: Size of each grid cell in meters
|
|
761
|
+
|
|
762
|
+
Returns:
|
|
763
|
+
GeoDataFrame with columns:
|
|
764
|
+
- geometry: Polygon geometry of each grid cell
|
|
765
|
+
- value: Value from the grid
|
|
766
|
+
"""
|
|
767
|
+
grid = np.flipud(grid_ori.copy())
|
|
768
|
+
|
|
769
|
+
# Extract bounds from rectangle vertices
|
|
770
|
+
min_lon = min(v[0] for v in rectangle_vertices)
|
|
771
|
+
max_lon = max(v[0] for v in rectangle_vertices)
|
|
772
|
+
min_lat = min(v[1] for v in rectangle_vertices)
|
|
773
|
+
max_lat = max(v[1] for v in rectangle_vertices)
|
|
774
|
+
|
|
775
|
+
rows, cols = grid.shape
|
|
776
|
+
|
|
777
|
+
# Calculate cell sizes in degrees (approximate)
|
|
778
|
+
# 111,111 meters = 1 degree at equator
|
|
779
|
+
cell_size_lon = meshsize / (111111 * np.cos(np.mean([min_lat, max_lat]) * np.pi / 180))
|
|
780
|
+
cell_size_lat = meshsize / 111111
|
|
781
|
+
|
|
782
|
+
# Create lists to store data
|
|
783
|
+
polygons = []
|
|
784
|
+
values = []
|
|
785
|
+
|
|
786
|
+
# Create grid cells
|
|
787
|
+
for i in range(rows):
|
|
788
|
+
for j in range(cols):
|
|
789
|
+
# Calculate cell bounds
|
|
790
|
+
cell_min_lon = min_lon + j * cell_size_lon
|
|
791
|
+
cell_max_lon = min_lon + (j + 1) * cell_size_lon
|
|
792
|
+
# Flip vertical axis since grid is stored with origin at top-left
|
|
793
|
+
cell_min_lat = max_lat - (i + 1) * cell_size_lat
|
|
794
|
+
cell_max_lat = max_lat - i * cell_size_lat
|
|
795
|
+
|
|
796
|
+
# Create polygon for cell
|
|
797
|
+
cell_poly = box(cell_min_lon, cell_min_lat, cell_max_lon, cell_max_lat)
|
|
798
|
+
|
|
799
|
+
polygons.append(cell_poly)
|
|
800
|
+
values.append(grid[i, j])
|
|
801
|
+
|
|
802
|
+
# Create GeoDataFrame
|
|
803
|
+
gdf = gpd.GeoDataFrame({
|
|
804
|
+
'geometry': polygons,
|
|
805
|
+
'value': values
|
|
806
|
+
}, crs=CRS.from_epsg(4326))
|
|
807
|
+
|
|
808
|
+
return gdf
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import contextily as ctx
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import geopandas as gpd
|
|
6
|
+
from shapely.geometry import LineString
|
|
7
|
+
import networkx as nx
|
|
8
|
+
import osmnx as ox
|
|
9
|
+
|
|
10
|
+
from .grid import grid_to_geodataframe
|
|
11
|
+
|
|
12
|
+
def calculate_edge_values(G, gdf, value_col='value'):
|
|
13
|
+
"""
|
|
14
|
+
Calculate average values for graph edges based on intersection with polygons.
|
|
15
|
+
|
|
16
|
+
Parameters:
|
|
17
|
+
-----------
|
|
18
|
+
G : NetworkX Graph
|
|
19
|
+
Input graph with edges to analyze
|
|
20
|
+
gdf : GeoDataFrame
|
|
21
|
+
Grid containing polygons with values
|
|
22
|
+
value_col : str, default 'value'
|
|
23
|
+
Name of the column containing values in the grid
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
--------
|
|
27
|
+
dict
|
|
28
|
+
Dictionary with edge identifiers (u,v,k) as keys and average values as values
|
|
29
|
+
"""
|
|
30
|
+
edge_values = {}
|
|
31
|
+
for u, v, k, data in G.edges(data=True, keys=True):
|
|
32
|
+
if 'geometry' in data:
|
|
33
|
+
edge_line = data['geometry']
|
|
34
|
+
else:
|
|
35
|
+
start_node = G.nodes[u]
|
|
36
|
+
end_node = G.nodes[v]
|
|
37
|
+
edge_line = LineString([(start_node['x'], start_node['y']),
|
|
38
|
+
(end_node['x'], end_node['y'])])
|
|
39
|
+
|
|
40
|
+
intersecting_polys = gdf[gdf.geometry.intersects(edge_line)]
|
|
41
|
+
|
|
42
|
+
if len(intersecting_polys) > 0:
|
|
43
|
+
total_length = 0
|
|
44
|
+
weighted_sum = 0
|
|
45
|
+
|
|
46
|
+
for idx, poly in intersecting_polys.iterrows():
|
|
47
|
+
if pd.isna(poly[value_col]):
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
intersection = edge_line.intersection(poly.geometry)
|
|
51
|
+
if not intersection.is_empty:
|
|
52
|
+
length = intersection.length
|
|
53
|
+
total_length += length
|
|
54
|
+
weighted_sum += length * poly[value_col]
|
|
55
|
+
|
|
56
|
+
if total_length > 0:
|
|
57
|
+
avg_value = weighted_sum / total_length
|
|
58
|
+
edge_values[(u, v, k)] = avg_value
|
|
59
|
+
else:
|
|
60
|
+
edge_values[(u, v, k)] = np.nan
|
|
61
|
+
else:
|
|
62
|
+
edge_values[(u, v, k)] = np.nan
|
|
63
|
+
|
|
64
|
+
return edge_values
|
|
65
|
+
|
|
66
|
+
def get_network_values(grid, rectangle_vertices, meshsize, value_name='value', **kwargs):
|
|
67
|
+
"""
|
|
68
|
+
Analyze and visualize network values based on grid intersections.
|
|
69
|
+
|
|
70
|
+
Parameters:
|
|
71
|
+
-----------
|
|
72
|
+
grid : GeoDataFrame
|
|
73
|
+
Input grid with geometries and values
|
|
74
|
+
rectangle_vertices : list
|
|
75
|
+
List of coordinates defining the bounding box vertices
|
|
76
|
+
meshsize : float
|
|
77
|
+
Size of the mesh grid
|
|
78
|
+
value_name : str, default 'value'
|
|
79
|
+
Name of the column containing values in the grid
|
|
80
|
+
**kwargs : dict
|
|
81
|
+
Optional arguments including:
|
|
82
|
+
- network_type : str, default 'walk'
|
|
83
|
+
Type of network to download ('walk', 'drive', 'all', etc.)
|
|
84
|
+
- vis_graph : bool, default True
|
|
85
|
+
Whether to visualize the graph
|
|
86
|
+
- colormap : str, default 'viridis'
|
|
87
|
+
Matplotlib colormap name for visualization
|
|
88
|
+
- vmin : float, optional
|
|
89
|
+
Minimum value for color scaling
|
|
90
|
+
- vmax : float, optional
|
|
91
|
+
Maximum value for color scaling
|
|
92
|
+
- edge_width : float, default 1
|
|
93
|
+
Width of the edges in visualization
|
|
94
|
+
- fig_size : tuple, default (15,15)
|
|
95
|
+
Figure size for visualization
|
|
96
|
+
- zoom : int, default 16
|
|
97
|
+
Zoom level for the basemap
|
|
98
|
+
- basemap_style : ctx.providers, default CartoDB.Positron
|
|
99
|
+
Contextily basemap provider
|
|
100
|
+
- save_path : str, optional
|
|
101
|
+
Path to save the output GeoPackage
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
--------
|
|
105
|
+
tuple : (NetworkX Graph, GeoDataFrame)
|
|
106
|
+
Returns the processed graph and edge GeoDataFrame
|
|
107
|
+
"""
|
|
108
|
+
# Set default values for optional arguments
|
|
109
|
+
defaults = {
|
|
110
|
+
'network_type': 'walk',
|
|
111
|
+
'vis_graph': True,
|
|
112
|
+
'colormap': 'viridis',
|
|
113
|
+
'vmin': None,
|
|
114
|
+
'vmax': None,
|
|
115
|
+
'edge_width': 1,
|
|
116
|
+
'fig_size': (15,15),
|
|
117
|
+
'zoom': 16,
|
|
118
|
+
'basemap_style': ctx.providers.CartoDB.Positron,
|
|
119
|
+
'save_path': None
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# Update defaults with provided kwargs
|
|
123
|
+
settings = defaults.copy()
|
|
124
|
+
settings.update(kwargs)
|
|
125
|
+
|
|
126
|
+
grid_gdf = grid_to_geodataframe(grid, rectangle_vertices, meshsize)
|
|
127
|
+
|
|
128
|
+
# Extract bounding box coordinates
|
|
129
|
+
north, south = rectangle_vertices[1][1], rectangle_vertices[0][1]
|
|
130
|
+
east, west = rectangle_vertices[2][0], rectangle_vertices[0][0]
|
|
131
|
+
bbox = (west, south, east, north)
|
|
132
|
+
|
|
133
|
+
# Download the road network
|
|
134
|
+
G = ox.graph.graph_from_bbox(bbox=bbox, network_type=settings['network_type'], simplify=True)
|
|
135
|
+
|
|
136
|
+
# Calculate edge values using the separate function
|
|
137
|
+
edge_values = calculate_edge_values(G, grid_gdf, "value")
|
|
138
|
+
|
|
139
|
+
# Add values to the graph
|
|
140
|
+
nx.set_edge_attributes(G, edge_values, value_name)
|
|
141
|
+
|
|
142
|
+
# Create GeoDataFrame from edges
|
|
143
|
+
edges_with_values = []
|
|
144
|
+
for u, v, k, data in G.edges(data=True, keys=True):
|
|
145
|
+
if 'geometry' in data:
|
|
146
|
+
edge_line = data['geometry']
|
|
147
|
+
else:
|
|
148
|
+
start_node = G.nodes[u]
|
|
149
|
+
end_node = G.nodes[v]
|
|
150
|
+
edge_line = LineString([(start_node['x'], start_node['y']),
|
|
151
|
+
(end_node['x'], end_node['y'])])
|
|
152
|
+
|
|
153
|
+
edges_with_values.append({
|
|
154
|
+
'geometry': edge_line,
|
|
155
|
+
value_name: data.get(value_name, np.nan),
|
|
156
|
+
'u': u,
|
|
157
|
+
'v': v,
|
|
158
|
+
'key': k
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
edge_gdf = gpd.GeoDataFrame(edges_with_values)
|
|
162
|
+
|
|
163
|
+
# Set CRS and save if requested
|
|
164
|
+
if edge_gdf.crs is None:
|
|
165
|
+
edge_gdf.set_crs(epsg=4326, inplace=True)
|
|
166
|
+
|
|
167
|
+
if settings['save_path']:
|
|
168
|
+
edge_gdf.to_file(settings['save_path'], driver="GPKG")
|
|
169
|
+
|
|
170
|
+
# Visualize if requested
|
|
171
|
+
if settings['vis_graph']:
|
|
172
|
+
edge_gdf_web = edge_gdf.to_crs(epsg=3857)
|
|
173
|
+
|
|
174
|
+
fig, ax = plt.subplots(figsize=settings['fig_size'])
|
|
175
|
+
|
|
176
|
+
plot = edge_gdf_web.plot(column=value_name,
|
|
177
|
+
ax=ax,
|
|
178
|
+
cmap=settings['colormap'],
|
|
179
|
+
legend=True,
|
|
180
|
+
vmin=settings['vmin'],
|
|
181
|
+
vmax=settings['vmax'],
|
|
182
|
+
linewidth=settings['edge_width'],
|
|
183
|
+
legend_kwds={'label': value_name,
|
|
184
|
+
'shrink': 0.5}) # Make colorbar 50% smaller
|
|
185
|
+
|
|
186
|
+
ctx.add_basemap(ax,
|
|
187
|
+
source=settings['basemap_style'],
|
|
188
|
+
zoom=settings['zoom'])
|
|
189
|
+
|
|
190
|
+
ax.set_axis_off()
|
|
191
|
+
# plt.title(f'Network {value_name} Analysis', pad=20)
|
|
192
|
+
plt.show()
|
|
193
|
+
|
|
194
|
+
return G, edge_gdf
|
|
@@ -675,11 +675,11 @@ def get_global_solar_irradiance_using_epw(
|
|
|
675
675
|
return None
|
|
676
676
|
else:
|
|
677
677
|
# Calculate center point of rectangle
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
center_lat = (min(lats) + max(lats)) / 2
|
|
678
|
+
lons = [coord[0] for coord in rectangle_vertices]
|
|
679
|
+
lats = [coord[1] for coord in rectangle_vertices]
|
|
681
680
|
center_lon = (min(lons) + max(lons)) / 2
|
|
682
|
-
|
|
681
|
+
center_lat = (min(lats) + max(lats)) / 2
|
|
682
|
+
target_point = (center_lon, center_lat)
|
|
683
683
|
|
|
684
684
|
# Optional: specify maximum distance in kilometers
|
|
685
685
|
max_distance = 100 # None for no limit
|
|
@@ -687,8 +687,8 @@ def get_global_solar_irradiance_using_epw(
|
|
|
687
687
|
output_dir = kwargs.get("output_dir", "output")
|
|
688
688
|
|
|
689
689
|
epw_file_path, weather_data, metadata = get_nearest_epw_from_climate_onebuilding(
|
|
690
|
-
latitude=center_lat,
|
|
691
690
|
longitude=center_lon,
|
|
691
|
+
latitude=center_lat,
|
|
692
692
|
output_dir=output_dir,
|
|
693
693
|
max_distance=max_distance,
|
|
694
694
|
extract_zip=True,
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
def get_material_dict():
|
|
4
|
+
"""
|
|
5
|
+
Returns a dictionary mapping material names to their corresponding ID values.
|
|
6
|
+
"""
|
|
7
|
+
return {
|
|
8
|
+
"unknown": -3,
|
|
9
|
+
"brick": -11,
|
|
10
|
+
"wood": -12,
|
|
11
|
+
"concrete": -13,
|
|
12
|
+
"metal": -14,
|
|
13
|
+
"stone": -15,
|
|
14
|
+
"glass": -16,
|
|
15
|
+
"plaster": -17,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def get_modulo_numbers(window_ratio):
|
|
19
|
+
"""
|
|
20
|
+
Determines the appropriate modulo numbers for x, y, z based on window_ratio.
|
|
21
|
+
|
|
22
|
+
Parameters:
|
|
23
|
+
window_ratio: float between 0 and 1.0
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
tuple (x_mod, y_mod, z_mod): modulo numbers for each dimension
|
|
27
|
+
"""
|
|
28
|
+
if window_ratio <= 0.125 + 0.0625: # around 0.125
|
|
29
|
+
return (2, 2, 2)
|
|
30
|
+
elif window_ratio <= 0.25 + 0.125: # around 0.25
|
|
31
|
+
combinations = [(2, 2, 1), (2, 1, 2), (1, 2, 2)]
|
|
32
|
+
return combinations[hash(str(window_ratio)) % len(combinations)]
|
|
33
|
+
elif window_ratio <= 0.5 + 0.125: # around 0.5
|
|
34
|
+
combinations = [(2, 1, 1), (1, 2, 1), (1, 1, 2)]
|
|
35
|
+
return combinations[hash(str(window_ratio)) % len(combinations)]
|
|
36
|
+
elif window_ratio <= 0.75 + 0.125: # around 0.75
|
|
37
|
+
combinations = [(2, 1, 1), (1, 2, 1), (1, 1, 2)]
|
|
38
|
+
return combinations[hash(str(window_ratio)) % len(combinations)]
|
|
39
|
+
else: # above 0.875
|
|
40
|
+
return (1, 1, 1)
|
|
41
|
+
|
|
42
|
+
def set_building_material_by_id(voxelcity_grid, building_id_grid_ori, ids, mark, window_ratio=0.125, glass_id=-16):
|
|
43
|
+
"""
|
|
44
|
+
Marks cells in voxelcity_grid based on building IDs and window ratio.
|
|
45
|
+
Never sets glass_id to cells with maximum z index.
|
|
46
|
+
|
|
47
|
+
Parameters:
|
|
48
|
+
voxelcity_grid: 3D numpy array
|
|
49
|
+
building_id_grid_ori: 2D numpy array containing building IDs
|
|
50
|
+
ids: list/array of building IDs to check
|
|
51
|
+
mark: value to set for marked cells
|
|
52
|
+
window_ratio: float between 0 and 1.0, determines window density:
|
|
53
|
+
~0.125: sparse windows (2,2,2)
|
|
54
|
+
~0.25: medium-sparse windows (2,2,1), (2,1,2), or (1,2,2)
|
|
55
|
+
~0.5: medium windows (2,1,1), (1,2,1), or (1,1,2)
|
|
56
|
+
~0.75: dense windows (2,1,1), (1,2,1), or (1,1,2)
|
|
57
|
+
>0.875: maximum density (1,1,1)
|
|
58
|
+
glass_id: value to set for glass cells (default: -16)
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Modified voxelcity_grid
|
|
62
|
+
"""
|
|
63
|
+
building_id_grid = np.flipud(building_id_grid_ori.copy())
|
|
64
|
+
|
|
65
|
+
# Get modulo numbers based on window_ratio
|
|
66
|
+
x_mod, y_mod, z_mod = get_modulo_numbers(window_ratio)
|
|
67
|
+
|
|
68
|
+
# Get positions where building IDs match
|
|
69
|
+
building_positions = np.where(np.isin(building_id_grid, ids))
|
|
70
|
+
|
|
71
|
+
# Loop through each position that matches building IDs
|
|
72
|
+
for i in range(len(building_positions[0])):
|
|
73
|
+
x, y = building_positions[0][i], building_positions[1][i]
|
|
74
|
+
z_mask = voxelcity_grid[x, y, :] == -3
|
|
75
|
+
voxelcity_grid[x, y, z_mask] = mark
|
|
76
|
+
|
|
77
|
+
# Check if x and y meet the modulo conditions
|
|
78
|
+
if x % x_mod == 0 and y % y_mod == 0:
|
|
79
|
+
z_mask = voxelcity_grid[x, y, :] == mark
|
|
80
|
+
if np.any(z_mask):
|
|
81
|
+
# Find the maximum z index where z_mask is True
|
|
82
|
+
z_indices = np.where(z_mask)[0]
|
|
83
|
+
max_z_index = np.max(z_indices)
|
|
84
|
+
|
|
85
|
+
# Create base mask excluding maximum z index
|
|
86
|
+
base_mask = z_mask.copy()
|
|
87
|
+
base_mask[max_z_index] = False
|
|
88
|
+
|
|
89
|
+
# Create pattern mask based on z modulo
|
|
90
|
+
pattern_mask = np.zeros_like(z_mask)
|
|
91
|
+
valid_z_indices = z_indices[z_indices != max_z_index] # Exclude max_z_index
|
|
92
|
+
if len(valid_z_indices) > 0:
|
|
93
|
+
pattern_mask[valid_z_indices[valid_z_indices % z_mod == 0]] = True
|
|
94
|
+
|
|
95
|
+
# For window_ratio around 0.75, add additional pattern
|
|
96
|
+
if 0.625 < window_ratio <= 0.875 and len(valid_z_indices) > 0:
|
|
97
|
+
additional_pattern = np.zeros_like(z_mask)
|
|
98
|
+
additional_pattern[valid_z_indices[valid_z_indices % (z_mod + 1) == 0]] = True
|
|
99
|
+
pattern_mask = np.logical_or(pattern_mask, additional_pattern)
|
|
100
|
+
|
|
101
|
+
# Final mask combines base_mask and pattern_mask
|
|
102
|
+
final_glass_mask = np.logical_and(base_mask, pattern_mask)
|
|
103
|
+
|
|
104
|
+
# Set glass_id for all positions in the final mask
|
|
105
|
+
voxelcity_grid[x, y, final_glass_mask] = glass_id
|
|
106
|
+
|
|
107
|
+
return voxelcity_grid
|
|
108
|
+
|
|
109
|
+
def set_building_material_by_gdf(voxelcity_grid_ori, building_id_grid, gdf_buildings, material_id_dict=None):
|
|
110
|
+
"""
|
|
111
|
+
Sets building materials based on a GeoDataFrame containing building information.
|
|
112
|
+
|
|
113
|
+
Parameters:
|
|
114
|
+
voxelcity_grid_ori: 3D numpy array of the original voxel grid
|
|
115
|
+
building_id_grid: 2D numpy array containing building IDs
|
|
116
|
+
gdf_buildings: GeoDataFrame containing building information with columns:
|
|
117
|
+
'building_id', 'surface_material', 'window_ratio'
|
|
118
|
+
material_id_dict: Dictionary mapping material names to their IDs (optional)
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Modified voxelcity_grid
|
|
122
|
+
"""
|
|
123
|
+
voxelcity_grid = voxelcity_grid_ori.copy()
|
|
124
|
+
if material_id_dict == None:
|
|
125
|
+
material_id_dict = get_material_dict()
|
|
126
|
+
|
|
127
|
+
for index, row in gdf_buildings.iterrows():
|
|
128
|
+
# Access properties
|
|
129
|
+
osmid = row['building_id']
|
|
130
|
+
surface_material = row['surface_material']
|
|
131
|
+
window_ratio = row['window_ratio']
|
|
132
|
+
if surface_material is None:
|
|
133
|
+
surface_material = 'unknown'
|
|
134
|
+
set_building_material_by_id(voxelcity_grid, building_id_grid, osmid,
|
|
135
|
+
material_id_dict[surface_material],
|
|
136
|
+
window_ratio=window_ratio,
|
|
137
|
+
glass_id=material_id_dict['glass'])
|
|
138
|
+
|
|
139
|
+
return voxelcity_grid
|