voxcity 0.6.28__tar.gz → 0.6.29__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.28 → voxcity-0.6.29}/PKG-INFO +1 -1
- {voxcity-0.6.28 → voxcity-0.6.29}/pyproject.toml +1 -1
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/downloader/oemj.py +80 -8
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/exporter/obj.py +26 -22
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/generator.py +20 -1
- {voxcity-0.6.28 → voxcity-0.6.29}/AUTHORS.rst +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/LICENSE +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/README.md +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/__init__.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/downloader/__init__.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/downloader/citygml.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/downloader/eubucco.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/downloader/gba.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/downloader/gee.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/downloader/mbfp.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/downloader/osm.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/downloader/overture.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/downloader/utils.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/exporter/__init__.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/exporter/cityles.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/exporter/envimet.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/exporter/magicavoxel.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/exporter/netcdf.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/geoprocessor/__init__.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/geoprocessor/draw.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/geoprocessor/grid.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/geoprocessor/mesh.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/geoprocessor/network.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/geoprocessor/polygon.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/geoprocessor/utils.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/simulator/__init__.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/simulator/solar.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/simulator/utils.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/simulator/view.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/utils/__init__.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/utils/lc.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/utils/material.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/src/voxcity/utils/visualization.py +0 -0
- {voxcity-0.6.28 → voxcity-0.6.29}/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.29"
|
|
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"
|
|
@@ -19,6 +19,7 @@ Example Usage:
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
import requests
|
|
22
|
+
import os
|
|
22
23
|
from PIL import Image, ImageDraw
|
|
23
24
|
from io import BytesIO
|
|
24
25
|
import math
|
|
@@ -73,7 +74,7 @@ def num2deg(xtile, ytile, zoom):
|
|
|
73
74
|
lat_deg = math.degrees(lat_rad)
|
|
74
75
|
return (lon_deg, lat_deg)
|
|
75
76
|
|
|
76
|
-
def download_tiles(polygon, zoom):
|
|
77
|
+
def download_tiles(polygon, zoom, *, ssl_verify=True, allow_insecure_ssl=False, allow_http_fallback=False, timeout_s=30):
|
|
77
78
|
"""Download satellite imagery tiles covering a polygon region.
|
|
78
79
|
|
|
79
80
|
Downloads all tiles that intersect with the given polygon at the specified zoom level
|
|
@@ -112,11 +113,70 @@ def download_tiles(polygon, zoom):
|
|
|
112
113
|
for x in range(min(min_x, max_x), max(min_x, max_x) + 1):
|
|
113
114
|
for y in range(min(min_y, max_y), max(min_y, max_y) + 1):
|
|
114
115
|
url = f"https://www.open-earth-map.org/demo/Japan/{zoom}/{x}/{y}.png"
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
# Try secure HTTPS first with user-provided verification option
|
|
117
|
+
content = None
|
|
118
|
+
try:
|
|
119
|
+
resp = requests.get(url, timeout=timeout_s, verify=ssl_verify)
|
|
120
|
+
if resp.status_code == 200:
|
|
121
|
+
content = resp.content
|
|
122
|
+
else:
|
|
123
|
+
print(f"Failed to download tile (status {resp.status_code}): {url}")
|
|
124
|
+
except requests.exceptions.SSLError:
|
|
125
|
+
# Optionally retry with certificate verification disabled
|
|
126
|
+
if allow_insecure_ssl:
|
|
127
|
+
try:
|
|
128
|
+
resp = requests.get(url, timeout=timeout_s, verify=False)
|
|
129
|
+
if resp.status_code == 200:
|
|
130
|
+
content = resp.content
|
|
131
|
+
else:
|
|
132
|
+
print(f"Failed to download tile (status {resp.status_code}) with insecure SSL: {url}")
|
|
133
|
+
except requests.exceptions.RequestException as e:
|
|
134
|
+
# Optionally try HTTP fallback
|
|
135
|
+
if allow_http_fallback and url.lower().startswith("https://"):
|
|
136
|
+
http_url = "http://" + url.split("://", 1)[1]
|
|
137
|
+
try:
|
|
138
|
+
resp = requests.get(http_url, timeout=timeout_s)
|
|
139
|
+
if resp.status_code == 200:
|
|
140
|
+
content = resp.content
|
|
141
|
+
else:
|
|
142
|
+
print(f"Failed to download tile over HTTP (status {resp.status_code}): {http_url}")
|
|
143
|
+
except requests.exceptions.RequestException as e2:
|
|
144
|
+
print(f"HTTP fallback failed for tile: {http_url} ({e2})")
|
|
145
|
+
else:
|
|
146
|
+
print(f"SSL error downloading tile: {url} ({e})")
|
|
147
|
+
else:
|
|
148
|
+
if allow_http_fallback and url.lower().startswith("https://"):
|
|
149
|
+
http_url = "http://" + url.split("://", 1)[1]
|
|
150
|
+
try:
|
|
151
|
+
resp = requests.get(http_url, timeout=timeout_s)
|
|
152
|
+
if resp.status_code == 200:
|
|
153
|
+
content = resp.content
|
|
154
|
+
else:
|
|
155
|
+
print(f"Failed to download tile over HTTP (status {resp.status_code}): {http_url}")
|
|
156
|
+
except requests.exceptions.RequestException as e:
|
|
157
|
+
print(f"HTTP fallback failed for tile: {http_url} ({e})")
|
|
158
|
+
else:
|
|
159
|
+
print(f"SSL error downloading tile: {url}")
|
|
160
|
+
except requests.exceptions.RequestException as e:
|
|
161
|
+
# Network error (timeout/connection). Try HTTP if allowed.
|
|
162
|
+
if allow_http_fallback and url.lower().startswith("https://"):
|
|
163
|
+
http_url = "http://" + url.split("://", 1)[1]
|
|
164
|
+
try:
|
|
165
|
+
resp = requests.get(http_url, timeout=timeout_s)
|
|
166
|
+
if resp.status_code == 200:
|
|
167
|
+
content = resp.content
|
|
168
|
+
else:
|
|
169
|
+
print(f"Failed to download tile over HTTP (status {resp.status_code}): {http_url}")
|
|
170
|
+
except requests.exceptions.RequestException as e2:
|
|
171
|
+
print(f"HTTP fallback failed for tile: {http_url} ({e2})")
|
|
172
|
+
else:
|
|
173
|
+
print(f"Error downloading tile: {url} ({e})")
|
|
174
|
+
|
|
175
|
+
if content is not None:
|
|
176
|
+
try:
|
|
177
|
+
tiles[(x, y)] = Image.open(BytesIO(content))
|
|
178
|
+
except Exception as e:
|
|
179
|
+
print(f"Error decoding tile image for {url}: {e}")
|
|
120
180
|
|
|
121
181
|
return tiles, (min(min_x, max_x), min(min_y, max_y), max(min_x, max_x), max(min_y, max_y))
|
|
122
182
|
|
|
@@ -231,6 +291,11 @@ def save_as_geotiff(image, polygon, zoom, bbox, bounds, output_path):
|
|
|
231
291
|
pixel_size_x = (lower_right_x - upper_left_x) / image.width
|
|
232
292
|
pixel_size_y = (upper_left_y - lower_right_y) / image.height
|
|
233
293
|
|
|
294
|
+
# Ensure output directory exists
|
|
295
|
+
out_dir = os.path.dirname(output_path)
|
|
296
|
+
if out_dir:
|
|
297
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
298
|
+
|
|
234
299
|
# Create GeoTIFF
|
|
235
300
|
driver = gdal.GetDriverByName('GTiff')
|
|
236
301
|
dataset = driver.Create(output_path, image.width, image.height, 3, gdal.GDT_Byte)
|
|
@@ -249,7 +314,7 @@ def save_as_geotiff(image, polygon, zoom, bbox, bounds, output_path):
|
|
|
249
314
|
|
|
250
315
|
dataset = None
|
|
251
316
|
|
|
252
|
-
def save_oemj_as_geotiff(polygon, filepath, zoom=16):
|
|
317
|
+
def save_oemj_as_geotiff(polygon, filepath, zoom=16, *, ssl_verify=True, allow_insecure_ssl=False, allow_http_fallback=False, timeout_s=30):
|
|
253
318
|
"""Download and save OpenEarthMap Japan imagery as a georeferenced GeoTIFF file.
|
|
254
319
|
|
|
255
320
|
This is the main function that orchestrates the entire process of downloading,
|
|
@@ -281,7 +346,14 @@ def save_oemj_as_geotiff(polygon, filepath, zoom=16):
|
|
|
281
346
|
- The output GeoTIFF will be in Web Mercator projection (EPSG:3857)
|
|
282
347
|
"""
|
|
283
348
|
try:
|
|
284
|
-
tiles, bounds = download_tiles(
|
|
349
|
+
tiles, bounds = download_tiles(
|
|
350
|
+
polygon,
|
|
351
|
+
zoom,
|
|
352
|
+
ssl_verify=ssl_verify,
|
|
353
|
+
allow_insecure_ssl=allow_insecure_ssl,
|
|
354
|
+
allow_http_fallback=allow_http_fallback,
|
|
355
|
+
timeout_s=timeout_s,
|
|
356
|
+
)
|
|
285
357
|
if not tiles:
|
|
286
358
|
raise ValueError("No tiles were downloaded. Please check the polygon coordinates and zoom level.")
|
|
287
359
|
|
|
@@ -681,6 +681,7 @@ def export_netcdf_to_obj(
|
|
|
681
681
|
classes_to_show=None,
|
|
682
682
|
voxel_color_scheme="default",
|
|
683
683
|
max_faces_warn=1_000_000,
|
|
684
|
+
export_vox_base=True,
|
|
684
685
|
):
|
|
685
686
|
"""
|
|
686
687
|
Export two OBJ/MTL files using the same local meter frame:
|
|
@@ -718,6 +719,8 @@ def export_netcdf_to_obj(
|
|
|
718
719
|
classes_to_show (set[int]|None): Optional subset of voxel classes to export; None -> all present (except 0).
|
|
719
720
|
voxel_color_scheme (str): Color scheme name passed to get_voxel_color_map.
|
|
720
721
|
max_faces_warn (int): Warn if a single class exceeds this many faces.
|
|
722
|
+
export_vox_base (bool): If False, skip exporting VoxCity OBJ/MTL; VoxCity input
|
|
723
|
+
is still used to define the shared coordinate system for scalar OBJ.
|
|
721
724
|
|
|
722
725
|
Returns:
|
|
723
726
|
dict: Paths of written files: keys 'vox_obj','vox_mtl','tm_obj','tm_mtl' (values may be None).
|
|
@@ -1345,27 +1348,28 @@ def export_netcdf_to_obj(
|
|
|
1345
1348
|
vox_meshes = {}
|
|
1346
1349
|
tm_meshes = {}
|
|
1347
1350
|
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1351
|
+
if export_vox_base:
|
|
1352
|
+
present = set(np.unique(Av_kji))
|
|
1353
|
+
present.discard(0)
|
|
1354
|
+
if classes_to_show is not None:
|
|
1355
|
+
present &= set(classes_to_show)
|
|
1356
|
+
present = sorted(present)
|
|
1357
|
+
|
|
1358
|
+
faces_total = 0
|
|
1359
|
+
voxel_color_map = get_voxel_color_map(color_scheme=voxel_color_scheme)
|
|
1360
|
+
for cls in present:
|
|
1361
|
+
mask = Av_kji == cls
|
|
1362
|
+
if not np.any(mask):
|
|
1363
|
+
continue
|
|
1364
|
+
rgb = voxel_color_map.get(int(cls), [200, 200, 200])
|
|
1365
|
+
if greedy_vox:
|
|
1366
|
+
m_cls, faces = make_voxel_mesh_uniform_color_greedy(mask, Xv_s, Yv_s, Zv_s, rgb=rgb, name=f"class_{int(cls)}")
|
|
1367
|
+
else:
|
|
1368
|
+
m_cls, faces = make_voxel_mesh_uniform_color(mask, Xv_s, Yv_s, Zv_s, rgb=rgb, name=f"class_{int(cls)}")
|
|
1369
|
+
if m_cls is not None:
|
|
1370
|
+
vox_meshes[f"voxclass_{int(cls)}"] = m_cls
|
|
1371
|
+
faces_total += faces
|
|
1372
|
+
print(f"[VoxCity] total voxel faces: {faces_total:,}")
|
|
1369
1373
|
|
|
1370
1374
|
iso_meshes = build_tm_isosurfaces_regular_grid(
|
|
1371
1375
|
A_scalar=A_s,
|
|
@@ -1390,7 +1394,7 @@ def export_netcdf_to_obj(
|
|
|
1390
1394
|
|
|
1391
1395
|
os.makedirs(output_dir, exist_ok=True)
|
|
1392
1396
|
obj_vox = mtl_vox = obj_tm = mtl_tm = None
|
|
1393
|
-
if vox_meshes:
|
|
1397
|
+
if export_vox_base and vox_meshes:
|
|
1394
1398
|
obj_vox, mtl_vox = save_obj_with_mtl_and_normals(vox_meshes, output_dir, vox_base_filename)
|
|
1395
1399
|
if tm_meshes:
|
|
1396
1400
|
obj_tm, mtl_tm = save_obj_with_mtl_and_normals(tm_meshes, output_dir, tm_base_filename)
|
|
@@ -147,7 +147,26 @@ def get_land_cover_grid(rectangle_vertices, meshsize, source, output_dir, **kwar
|
|
|
147
147
|
save_geotiff_dynamic_world_v1(roi, geotiff_path, dynamic_world_date)
|
|
148
148
|
elif source == 'OpenEarthMapJapan':
|
|
149
149
|
# Japan-specific land cover dataset
|
|
150
|
-
|
|
150
|
+
# Allow SSL/HTTP options to be passed through kwargs
|
|
151
|
+
ssl_verify = kwargs.get('ssl_verify', kwargs.get('verify', True))
|
|
152
|
+
allow_insecure_ssl = kwargs.get('allow_insecure_ssl', False)
|
|
153
|
+
allow_http_fallback = kwargs.get('allow_http_fallback', False)
|
|
154
|
+
timeout_s = kwargs.get('timeout', 30)
|
|
155
|
+
|
|
156
|
+
save_oemj_as_geotiff(
|
|
157
|
+
rectangle_vertices,
|
|
158
|
+
geotiff_path,
|
|
159
|
+
ssl_verify=ssl_verify,
|
|
160
|
+
allow_insecure_ssl=allow_insecure_ssl,
|
|
161
|
+
allow_http_fallback=allow_http_fallback,
|
|
162
|
+
timeout_s=timeout_s,
|
|
163
|
+
)
|
|
164
|
+
# Ensure the file was actually created before proceeding
|
|
165
|
+
if not os.path.exists(geotiff_path):
|
|
166
|
+
raise FileNotFoundError(
|
|
167
|
+
f"OEMJ download failed; expected GeoTIFF not found: {geotiff_path}. "
|
|
168
|
+
"You can try setting ssl_verify=False or allow_http_fallback=True in kwargs."
|
|
169
|
+
)
|
|
151
170
|
elif source == 'OpenStreetMap':
|
|
152
171
|
# Vector-based land cover from OpenStreetMap
|
|
153
172
|
# This bypasses the GeoTIFF workflow and gets data directly as GeoJSON
|
|
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
|
|
File without changes
|