voxcity 0.6.25__tar.gz → 0.6.27__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.6.25 → voxcity-0.6.27}/PKG-INFO +1 -1
- {voxcity-0.6.25 → voxcity-0.6.27}/pyproject.toml +1 -1
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/downloader/__init__.py +2 -1
- voxcity-0.6.27/src/voxcity/downloader/gba.py +210 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/exporter/cityles.py +76 -95
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/generator.py +17 -2
- {voxcity-0.6.25 → voxcity-0.6.27}/AUTHORS.rst +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/LICENSE +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/README.md +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/__init__.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/downloader/citygml.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/downloader/eubucco.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/downloader/gee.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/downloader/mbfp.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/downloader/oemj.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/downloader/osm.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/downloader/overture.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/downloader/utils.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/exporter/__init__.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/exporter/envimet.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/exporter/magicavoxel.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/exporter/netcdf.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/exporter/obj.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/geoprocessor/__init__.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/geoprocessor/draw.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/geoprocessor/grid.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/geoprocessor/mesh.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/geoprocessor/network.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/geoprocessor/polygon.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/geoprocessor/utils.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/simulator/__init__.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/simulator/solar.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/simulator/utils.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/simulator/view.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/utils/__init__.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/utils/lc.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/utils/material.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/utils/visualization.py +0 -0
- {voxcity-0.6.25 → voxcity-0.6.27}/src/voxcity/utils/weather.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "voxcity"
|
|
3
|
-
version = "0.6.
|
|
3
|
+
version = "0.6.27"
|
|
4
4
|
description = "voxcity is an easy and one-stop tool to output 3d city models for microclimate simulation by integrating multiple geospatial open-data"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Downloader for Global Building Atlas (GBA) LOD1 polygons.
|
|
3
|
+
|
|
4
|
+
This module downloads GeoParquet tiles from the Global Building Atlas (GBA)
|
|
5
|
+
hosted at data.source.coop, selects tiles intersecting a user-specified
|
|
6
|
+
rectangle, loads them into a GeoDataFrame, and filters features to the
|
|
7
|
+
rectangle extent.
|
|
8
|
+
|
|
9
|
+
Tile scheme:
|
|
10
|
+
- Global 5x5-degree tiles named like: e010_n50_e015_n45.parquet
|
|
11
|
+
- longitudes: e/w with 3-digit zero padding (e.g., e010, w060)
|
|
12
|
+
- latitudes: n/s with 2-digit zero padding (e.g., n50, s25)
|
|
13
|
+
- filename order: west_lon, north_lat, east_lon, south_lat
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
gdf = load_gdf_from_gba(rectangle_vertices=[(lon1, lat1), (lon2, lat2), ...])
|
|
17
|
+
|
|
18
|
+
Notes:
|
|
19
|
+
- Output CRS is EPSG:4326.
|
|
20
|
+
- Requires pyarrow or fastparquet for parquet reading via GeoPandas.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import math
|
|
26
|
+
import os
|
|
27
|
+
import tempfile
|
|
28
|
+
from typing import Iterable, List, Optional, Sequence, Tuple
|
|
29
|
+
|
|
30
|
+
import geopandas as gpd
|
|
31
|
+
import pandas as pd
|
|
32
|
+
import requests
|
|
33
|
+
from shapely.geometry import Polygon
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _bbox_from_rectangle_vertices(vertices: Sequence[Tuple[float, float]]) -> Tuple[float, float, float, float]:
|
|
37
|
+
"""
|
|
38
|
+
Convert rectangle vertices in (lon, lat) into bbox as (min_lon, min_lat, max_lon, max_lat).
|
|
39
|
+
"""
|
|
40
|
+
if not vertices:
|
|
41
|
+
raise ValueError("rectangle_vertices must be a non-empty sequence of (lon, lat)")
|
|
42
|
+
lons = [v[0] for v in vertices]
|
|
43
|
+
lats = [v[1] for v in vertices]
|
|
44
|
+
return (min(lons), min(lats), max(lons), max(lats))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _pad_lon(deg: int) -> str:
|
|
48
|
+
return f"{abs(deg):03d}"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _pad_lat(deg: int) -> str:
|
|
52
|
+
return f"{abs(deg):02d}"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _lon_tag(deg: int) -> str:
|
|
56
|
+
return ("e" if deg >= 0 else "w") + _pad_lon(deg)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _lat_tag(deg: int) -> str:
|
|
60
|
+
return ("n" if deg >= 0 else "s") + _pad_lat(deg)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _snap_down(value: float, step: int) -> int:
|
|
64
|
+
return int(math.floor(value / step) * step)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _snap_up(value: float, step: int) -> int:
|
|
68
|
+
return int(math.ceil(value / step) * step)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _generate_tile_bounds_for_bbox(
|
|
72
|
+
min_lon: float, min_lat: float, max_lon: float, max_lat: float, tile_size_deg: int = 5
|
|
73
|
+
) -> Iterable[Tuple[int, int, int, int]]:
|
|
74
|
+
"""
|
|
75
|
+
Generate 5-degree tile bounds (west, south, east, north) covering bbox.
|
|
76
|
+
All values are integer degrees aligned to tile_size_deg.
|
|
77
|
+
"""
|
|
78
|
+
west = _snap_down(min_lon, tile_size_deg)
|
|
79
|
+
east = _snap_up(max_lon, tile_size_deg)
|
|
80
|
+
south = _snap_down(min_lat, tile_size_deg)
|
|
81
|
+
north = _snap_up(max_lat, tile_size_deg)
|
|
82
|
+
|
|
83
|
+
for lon in range(west, east, tile_size_deg):
|
|
84
|
+
for lat in range(south, north, tile_size_deg):
|
|
85
|
+
yield (lon, lat, lon + tile_size_deg, lat + tile_size_deg)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _tile_filename(west: int, south: int, east: int, north: int) -> str:
|
|
89
|
+
"""
|
|
90
|
+
Construct GBA tile filename for given integer-degree bounds.
|
|
91
|
+
Naming convention examples:
|
|
92
|
+
e010_n50_e015_n45.parquet
|
|
93
|
+
e140_s25_e145_s30.parquet
|
|
94
|
+
w060_s30_w055_s35.parquet
|
|
95
|
+
"""
|
|
96
|
+
return f"{_lon_tag(west)}_{_lat_tag(north)}_{_lon_tag(east)}_{_lat_tag(south)}.parquet"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _tile_url(base_url: str, west: int, south: int, east: int, north: int) -> str:
|
|
100
|
+
filename = _tile_filename(west, south, east, north)
|
|
101
|
+
return f"{base_url.rstrip('/')}/{filename}"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _download_parquet(url: str, download_dir: str, timeout: int = 60) -> Optional[str]:
|
|
105
|
+
"""
|
|
106
|
+
Download a parquet file to download_dir. Returns local filepath or None if not found.
|
|
107
|
+
"""
|
|
108
|
+
try:
|
|
109
|
+
with requests.get(url, stream=True, timeout=timeout) as r:
|
|
110
|
+
if r.status_code != 200:
|
|
111
|
+
return None
|
|
112
|
+
filename = os.path.basename(url)
|
|
113
|
+
local_path = os.path.join(download_dir, filename)
|
|
114
|
+
with open(local_path, "wb") as f:
|
|
115
|
+
for chunk in r.iter_content(chunk_size=1024 * 1024):
|
|
116
|
+
if chunk:
|
|
117
|
+
f.write(chunk)
|
|
118
|
+
return local_path
|
|
119
|
+
except requests.RequestException:
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _filter_to_rectangle(gdf: gpd.GeoDataFrame, rectangle: Polygon, clip: bool) -> gpd.GeoDataFrame:
|
|
124
|
+
gdf = gdf[gdf.geometry.notnull()].copy()
|
|
125
|
+
# Ensure CRS is WGS84
|
|
126
|
+
if gdf.crs is None:
|
|
127
|
+
gdf.set_crs(epsg=4326, inplace=True)
|
|
128
|
+
elif gdf.crs.to_epsg() != 4326:
|
|
129
|
+
gdf = gdf.to_crs(epsg=4326)
|
|
130
|
+
|
|
131
|
+
intersects = gdf.intersects(rectangle)
|
|
132
|
+
gdf = gdf[intersects].copy()
|
|
133
|
+
if clip and not gdf.empty:
|
|
134
|
+
# GeoPandas clip performs overlay to trim geometries to rectangle
|
|
135
|
+
gdf = gpd.clip(gdf, gpd.GeoSeries([rectangle], crs="EPSG:4326").to_frame("geometry"))
|
|
136
|
+
return gdf
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def load_gdf_from_gba(
|
|
140
|
+
rectangle_vertices: Sequence[Tuple[float, float]],
|
|
141
|
+
base_url: str = "https://data.source.coop/tge-labs/globalbuildingatlas-lod1",
|
|
142
|
+
download_dir: Optional[str] = None,
|
|
143
|
+
clip_to_rectangle: bool = False,
|
|
144
|
+
) -> Optional[gpd.GeoDataFrame]:
|
|
145
|
+
"""
|
|
146
|
+
Download GBA tiles intersecting a rectangle and return combined GeoDataFrame.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
rectangle_vertices: Sequence of (lon, lat) defining the area of interest.
|
|
150
|
+
base_url: Base URL hosting GBA parquet tiles.
|
|
151
|
+
download_dir: Optional directory to store downloaded tiles. If None, a
|
|
152
|
+
temporary directory is used and cleaned up by the OS later.
|
|
153
|
+
clip_to_rectangle: If True, geometries are clipped to rectangle extent.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
GeoDataFrame with EPSG:4326 geometry and an 'id' column, or None if no data.
|
|
157
|
+
"""
|
|
158
|
+
min_lon, min_lat, max_lon, max_lat = _bbox_from_rectangle_vertices(rectangle_vertices)
|
|
159
|
+
rectangle = Polygon([
|
|
160
|
+
(min_lon, min_lat),
|
|
161
|
+
(max_lon, min_lat),
|
|
162
|
+
(max_lon, max_lat),
|
|
163
|
+
(min_lon, max_lat),
|
|
164
|
+
(min_lon, min_lat),
|
|
165
|
+
])
|
|
166
|
+
|
|
167
|
+
tmp_dir_created = False
|
|
168
|
+
if download_dir is None:
|
|
169
|
+
download_dir = tempfile.mkdtemp(prefix="gba_tiles_")
|
|
170
|
+
tmp_dir_created = True
|
|
171
|
+
else:
|
|
172
|
+
os.makedirs(download_dir, exist_ok=True)
|
|
173
|
+
|
|
174
|
+
local_files: List[str] = []
|
|
175
|
+
for west, south, east, north in _generate_tile_bounds_for_bbox(min_lon, min_lat, max_lon, max_lat):
|
|
176
|
+
url = _tile_url(base_url, west, south, east, north)
|
|
177
|
+
local = _download_parquet(url, download_dir)
|
|
178
|
+
if local is not None:
|
|
179
|
+
local_files.append(local)
|
|
180
|
+
|
|
181
|
+
if not local_files:
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
gdfs: List[gpd.GeoDataFrame] = []
|
|
185
|
+
for path in local_files:
|
|
186
|
+
try:
|
|
187
|
+
# GeoParquet read
|
|
188
|
+
gdf = gpd.read_parquet(path)
|
|
189
|
+
if gdf is not None and not gdf.empty:
|
|
190
|
+
gdfs.append(gdf)
|
|
191
|
+
except Exception:
|
|
192
|
+
# Skip unreadable tiles
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
if not gdfs:
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
combined = pd.concat(gdfs, ignore_index=True)
|
|
199
|
+
combined = gpd.GeoDataFrame(combined, geometry="geometry")
|
|
200
|
+
combined = _filter_to_rectangle(combined, rectangle, clip=clip_to_rectangle)
|
|
201
|
+
|
|
202
|
+
if combined.empty:
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
# Ensure sequential ids
|
|
206
|
+
combined["id"] = combined.index.astype(int)
|
|
207
|
+
combined.set_crs(epsg=4326, inplace=True, allow_override=True)
|
|
208
|
+
return combined
|
|
209
|
+
|
|
210
|
+
|
|
@@ -36,126 +36,107 @@ VOXCITY_STANDARD_CLASSES = {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
## Source-specific class name to CityLES land use mappings
|
|
39
|
-
# CityLES land use codes
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
#
|
|
43
|
-
# 7: Dryland Cropland and Pasture, 8: Barren or Sparsely Vegetated,
|
|
44
|
-
# 9: WATER, 10: Grassland, 11: CONCRETE (proxy of block),
|
|
45
|
-
# 12: ASPHALT without AH, 13: ASPHALT,
|
|
46
|
-
# 14-17: Deciduous Broadleaf Forest
|
|
47
|
-
|
|
48
|
-
# OpenStreetMap / Standard (mapped to updated CityLES landuse codes)
|
|
39
|
+
# CityLES land use codes: 1=Water, 2=Rice Paddy, 3=Crops, 4=Grassland, 5=Deciduous Broadleaf Forest,
|
|
40
|
+
# 9=Bare Land, 10=Building, 16=Asphalt (road), etc.
|
|
41
|
+
|
|
42
|
+
# OpenStreetMap / Standard
|
|
49
43
|
OSM_CLASS_TO_CITYLES = {
|
|
50
|
-
'Bareland':
|
|
51
|
-
'Rangeland':
|
|
52
|
-
'Shrub':
|
|
53
|
-
'Moss and lichen':
|
|
54
|
-
'Agriculture land':
|
|
55
|
-
'Tree':
|
|
56
|
-
'Wet land':
|
|
57
|
-
'Mangroves':
|
|
58
|
-
'Water':
|
|
59
|
-
'Snow and ice':
|
|
60
|
-
'Developed space':
|
|
61
|
-
'Road':
|
|
62
|
-
'Building':
|
|
63
|
-
'No Data':
|
|
44
|
+
'Bareland': 9,
|
|
45
|
+
'Rangeland': 4,
|
|
46
|
+
'Shrub': 4,
|
|
47
|
+
'Moss and lichen': 4,
|
|
48
|
+
'Agriculture land': 3,
|
|
49
|
+
'Tree': 5,
|
|
50
|
+
'Wet land': 2,
|
|
51
|
+
'Mangroves': 5,
|
|
52
|
+
'Water': 1,
|
|
53
|
+
'Snow and ice': 9,
|
|
54
|
+
'Developed space': 10,
|
|
55
|
+
'Road': 16,
|
|
56
|
+
'Building': 10,
|
|
57
|
+
'No Data': 4
|
|
64
58
|
}
|
|
65
59
|
|
|
66
60
|
# Urbanwatch
|
|
67
61
|
URBANWATCH_CLASS_TO_CITYLES = {
|
|
68
|
-
'Building':
|
|
69
|
-
'Road':
|
|
70
|
-
'Parking Lot':
|
|
71
|
-
'Tree Canopy':
|
|
72
|
-
'Grass/Shrub':
|
|
73
|
-
'Agriculture':
|
|
74
|
-
'Water':
|
|
75
|
-
'Barren':
|
|
76
|
-
'Unknown':
|
|
77
|
-
'Sea':
|
|
62
|
+
'Building': 10,
|
|
63
|
+
'Road': 16,
|
|
64
|
+
'Parking Lot': 16,
|
|
65
|
+
'Tree Canopy': 5,
|
|
66
|
+
'Grass/Shrub': 4,
|
|
67
|
+
'Agriculture': 3,
|
|
68
|
+
'Water': 1,
|
|
69
|
+
'Barren': 9,
|
|
70
|
+
'Unknown': 4,
|
|
71
|
+
'Sea': 1
|
|
78
72
|
}
|
|
79
73
|
|
|
80
74
|
# OpenEarthMapJapan
|
|
81
75
|
OEMJ_CLASS_TO_CITYLES = {
|
|
82
|
-
'Bareland':
|
|
83
|
-
'Rangeland':
|
|
84
|
-
'Developed space':
|
|
85
|
-
'Road':
|
|
86
|
-
'Tree':
|
|
87
|
-
'Water':
|
|
88
|
-
'Agriculture land':
|
|
89
|
-
'Building':
|
|
76
|
+
'Bareland': 9,
|
|
77
|
+
'Rangeland': 4,
|
|
78
|
+
'Developed space': 10,
|
|
79
|
+
'Road': 16,
|
|
80
|
+
'Tree': 5,
|
|
81
|
+
'Water': 1,
|
|
82
|
+
'Agriculture land': 3,
|
|
83
|
+
'Building': 10
|
|
90
84
|
}
|
|
91
85
|
|
|
92
86
|
# ESA WorldCover
|
|
93
87
|
ESA_CLASS_TO_CITYLES = {
|
|
94
|
-
'Trees':
|
|
95
|
-
'Shrubland':
|
|
96
|
-
'Grassland':
|
|
97
|
-
'Cropland':
|
|
98
|
-
'Built-up':
|
|
99
|
-
'Barren / sparse vegetation':
|
|
100
|
-
'Snow and ice':
|
|
101
|
-
'Open water':
|
|
102
|
-
'Herbaceous wetland':
|
|
103
|
-
'Mangroves':
|
|
104
|
-
'Moss and lichen':
|
|
88
|
+
'Trees': 5,
|
|
89
|
+
'Shrubland': 4,
|
|
90
|
+
'Grassland': 4,
|
|
91
|
+
'Cropland': 3,
|
|
92
|
+
'Built-up': 10,
|
|
93
|
+
'Barren / sparse vegetation': 9,
|
|
94
|
+
'Snow and ice': 9,
|
|
95
|
+
'Open water': 1,
|
|
96
|
+
'Herbaceous wetland': 2,
|
|
97
|
+
'Mangroves': 5,
|
|
98
|
+
'Moss and lichen': 9
|
|
105
99
|
}
|
|
106
100
|
|
|
107
101
|
# ESRI 10m Annual Land Cover
|
|
108
102
|
ESRI_CLASS_TO_CITYLES = {
|
|
109
|
-
'No Data':
|
|
110
|
-
'Water':
|
|
111
|
-
'Trees':
|
|
112
|
-
'Grass':
|
|
113
|
-
'Flooded Vegetation':
|
|
114
|
-
'Crops':
|
|
115
|
-
'Scrub/Shrub':
|
|
116
|
-
'Built Area':
|
|
117
|
-
'Bare Ground':
|
|
118
|
-
'Snow/Ice':
|
|
119
|
-
'Clouds':
|
|
103
|
+
'No Data': 4,
|
|
104
|
+
'Water': 1,
|
|
105
|
+
'Trees': 5,
|
|
106
|
+
'Grass': 4,
|
|
107
|
+
'Flooded Vegetation': 2,
|
|
108
|
+
'Crops': 3,
|
|
109
|
+
'Scrub/Shrub': 4,
|
|
110
|
+
'Built Area': 10,
|
|
111
|
+
'Bare Ground': 9,
|
|
112
|
+
'Snow/Ice': 9,
|
|
113
|
+
'Clouds': 4
|
|
120
114
|
}
|
|
121
115
|
|
|
122
116
|
# Dynamic World V1
|
|
123
117
|
DYNAMIC_WORLD_CLASS_TO_CITYLES = {
|
|
124
|
-
'Water':
|
|
125
|
-
'Trees':
|
|
126
|
-
'Grass':
|
|
127
|
-
'Flooded Vegetation':
|
|
128
|
-
'Crops':
|
|
129
|
-
'Shrub and Scrub':
|
|
130
|
-
'Built':
|
|
131
|
-
'Bare':
|
|
132
|
-
'Snow and Ice':
|
|
118
|
+
'Water': 1,
|
|
119
|
+
'Trees': 5,
|
|
120
|
+
'Grass': 4,
|
|
121
|
+
'Flooded Vegetation': 2,
|
|
122
|
+
'Crops': 3,
|
|
123
|
+
'Shrub and Scrub': 4,
|
|
124
|
+
'Built': 10,
|
|
125
|
+
'Bare': 9,
|
|
126
|
+
'Snow and Ice': 9
|
|
133
127
|
}
|
|
134
128
|
|
|
135
|
-
# Building material mapping based on corrected
|
|
129
|
+
# Building material mapping based on corrected documentation
|
|
136
130
|
BUILDING_MATERIAL_MAPPING = {
|
|
137
|
-
'building':
|
|
138
|
-
'concrete':
|
|
139
|
-
'residential':
|
|
140
|
-
'wooden':
|
|
141
|
-
'commercial':
|
|
142
|
-
'industrial':
|
|
143
|
-
'default':
|
|
131
|
+
'building': 110, # Building (general)
|
|
132
|
+
'concrete': 110, # Building (concrete)
|
|
133
|
+
'residential': 111, # Old wooden house
|
|
134
|
+
'wooden': 111, # Old wooden house
|
|
135
|
+
'commercial': 110, # Building (commercial)
|
|
136
|
+
'industrial': 110, # Building (industrial)
|
|
137
|
+
'default': 110 # Default to general building
|
|
144
138
|
}
|
|
145
139
|
|
|
146
|
-
# Helper to convert landuse code (1-17) to building material code (101-117)
|
|
147
|
-
def landuse_to_building_material_code(landuse_code: int) -> int:
|
|
148
|
-
"""Map landuse code to building-material code with required adjustments.
|
|
149
|
-
|
|
150
|
-
The general rule is 100 + landuse_code, except for ASPHALT classes where
|
|
151
|
-
landuse 12 (ASPHALT without AH) maps to 113 and landuse 13 (ASPHALT) maps to 112.
|
|
152
|
-
"""
|
|
153
|
-
# if landuse_code == 12:
|
|
154
|
-
# return 113
|
|
155
|
-
# if landuse_code == 13:
|
|
156
|
-
# return 112
|
|
157
|
-
return 100 + int(landuse_code)
|
|
158
|
-
|
|
159
140
|
# Tree type mapping for vmap.txt
|
|
160
141
|
TREE_TYPE_MAPPING = {
|
|
161
142
|
'deciduous': 101, # Leaf
|
|
@@ -248,7 +229,7 @@ def export_topog(building_height_grid, building_id_grid, output_path,
|
|
|
248
229
|
# Decide material code per cell
|
|
249
230
|
if cityles_landuse_grid is not None:
|
|
250
231
|
cell_lu = int(cityles_landuse_grid[j, i])
|
|
251
|
-
material_code_cell =
|
|
232
|
+
material_code_cell = cell_lu + 100
|
|
252
233
|
else:
|
|
253
234
|
if height > 0:
|
|
254
235
|
material_code_cell = material_code
|
|
@@ -48,6 +48,7 @@ from .downloader.oemj import save_oemj_as_geotiff
|
|
|
48
48
|
from .downloader.eubucco import load_gdf_from_eubucco
|
|
49
49
|
from .downloader.overture import load_gdf_from_overture
|
|
50
50
|
from .downloader.citygml import load_buid_dem_veg_from_citygml
|
|
51
|
+
from .downloader.gba import load_gdf_from_gba
|
|
51
52
|
|
|
52
53
|
# Google Earth Engine related imports - for satellite and elevation data
|
|
53
54
|
from .downloader.gee import (
|
|
@@ -206,8 +207,9 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, b
|
|
|
206
207
|
- list: Filtered building features
|
|
207
208
|
"""
|
|
208
209
|
|
|
209
|
-
# Initialize Earth Engine for
|
|
210
|
-
|
|
210
|
+
# Initialize Earth Engine only for building sources that require it
|
|
211
|
+
ee_required_sources = {"Open Building 2.5D Temporal"}
|
|
212
|
+
if source in ee_required_sources:
|
|
211
213
|
initialize_earth_engine()
|
|
212
214
|
|
|
213
215
|
print("Creating Building Height grid\n ")
|
|
@@ -243,6 +245,11 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, b
|
|
|
243
245
|
elif source == "Overture":
|
|
244
246
|
# Open building dataset from Overture Maps Foundation
|
|
245
247
|
gdf = load_gdf_from_overture(rectangle_vertices, floor_height=floor_height)
|
|
248
|
+
elif source in ("GBA", "Global Building Atlas"):
|
|
249
|
+
# Global Building Atlas LOD1 polygons (GeoParquet tiles)
|
|
250
|
+
clip_gba = kwargs.get("gba_clip", False)
|
|
251
|
+
gba_download_dir = kwargs.get("gba_download_dir")
|
|
252
|
+
gdf = load_gdf_from_gba(rectangle_vertices, download_dir=gba_download_dir, clip_to_rectangle=clip_gba)
|
|
246
253
|
elif source == "Local file":
|
|
247
254
|
# Handle user-provided local building data files
|
|
248
255
|
_, extension = os.path.splitext(kwargs["building_path"])
|
|
@@ -290,6 +297,10 @@ def get_building_height_grid(rectangle_vertices, meshsize, source, output_dir, b
|
|
|
290
297
|
# gdf_comp = load_gdf_from_openmaptiles(rectangle_vertices, kwargs["maptiler_API_key"])
|
|
291
298
|
elif building_complementary_source == "Overture":
|
|
292
299
|
gdf_comp = load_gdf_from_overture(rectangle_vertices, floor_height=floor_height)
|
|
300
|
+
elif building_complementary_source in ("GBA", "Global Building Atlas"):
|
|
301
|
+
clip_gba = kwargs.get("gba_clip", False)
|
|
302
|
+
gba_download_dir = kwargs.get("gba_download_dir")
|
|
303
|
+
gdf_comp = load_gdf_from_gba(rectangle_vertices, download_dir=gba_download_dir, clip_to_rectangle=clip_gba)
|
|
293
304
|
elif building_complementary_source == "Local file":
|
|
294
305
|
_, extension = os.path.splitext(kwargs["building_complementary_path"])
|
|
295
306
|
if extension == ".gpkg":
|
|
@@ -979,6 +990,10 @@ def get_voxcity_CityGML(rectangle_vertices, land_cover_source, canopy_height_sou
|
|
|
979
990
|
gdf_comp = load_gdf_from_eubucco(rectangle_vertices, kwargs.get("output_dir", "output"))
|
|
980
991
|
elif building_complementary_source == 'Overture':
|
|
981
992
|
gdf_comp = load_gdf_from_overture(rectangle_vertices, floor_height=floor_height)
|
|
993
|
+
elif building_complementary_source in ("GBA", "Global Building Atlas"):
|
|
994
|
+
clip_gba = kwargs.get("gba_clip", False)
|
|
995
|
+
gba_download_dir = kwargs.get("gba_download_dir")
|
|
996
|
+
gdf_comp = load_gdf_from_gba(rectangle_vertices, download_dir=gba_download_dir, clip_to_rectangle=clip_gba)
|
|
982
997
|
elif building_complementary_source == 'Local file':
|
|
983
998
|
comp_path = kwargs.get("building_complementary_path")
|
|
984
999
|
if comp_path is not None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|