voxcity 0.6.19__py3-none-any.whl → 0.6.20__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.
Potentially problematic release.
This version of voxcity might be problematic. Click here for more details.
- voxcity/downloader/citygml.py +32 -18
- voxcity/generator.py +15 -1
- voxcity/simulator/solar.py +12 -2
- voxcity/utils/weather.py +68 -6
- {voxcity-0.6.19.dist-info → voxcity-0.6.20.dist-info}/METADATA +1 -1
- {voxcity-0.6.19.dist-info → voxcity-0.6.20.dist-info}/RECORD +9 -9
- {voxcity-0.6.19.dist-info → voxcity-0.6.20.dist-info}/WHEEL +0 -0
- {voxcity-0.6.19.dist-info → voxcity-0.6.20.dist-info}/licenses/AUTHORS.rst +0 -0
- {voxcity-0.6.19.dist-info → voxcity-0.6.20.dist-info}/licenses/LICENSE +0 -0
voxcity/downloader/citygml.py
CHANGED
|
@@ -156,13 +156,16 @@ def get_tile_polygon_from_filename(filename):
|
|
|
156
156
|
# Original script logic
|
|
157
157
|
# --------------------------------------------------------------------
|
|
158
158
|
|
|
159
|
-
def download_and_extract_zip(url, extract_to='.'):
|
|
159
|
+
def download_and_extract_zip(url, extract_to='.', ssl_verify=True, ca_bundle=None, timeout=60):
|
|
160
160
|
"""
|
|
161
161
|
Download and extract a zip file from a URL to specified directory.
|
|
162
162
|
|
|
163
163
|
Args:
|
|
164
164
|
url (str): URL of the zip file to download.
|
|
165
165
|
extract_to (str): Directory to extract files to (default: current directory).
|
|
166
|
+
ssl_verify (bool): Whether to verify SSL certificates (default: True).
|
|
167
|
+
ca_bundle (str|None): Path to a CA bundle file. Overrides verify when provided.
|
|
168
|
+
timeout (int|float): Request timeout in seconds (default: 60).
|
|
166
169
|
|
|
167
170
|
Returns:
|
|
168
171
|
tuple: (extraction_path, folder_name) where files were extracted.
|
|
@@ -171,21 +174,27 @@ def download_and_extract_zip(url, extract_to='.'):
|
|
|
171
174
|
- Creates a subdirectory named after the zip file (without .zip)
|
|
172
175
|
- Prints status messages for success/failure
|
|
173
176
|
"""
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
177
|
+
verify_arg = ca_bundle if ca_bundle else ssl_verify
|
|
178
|
+
try:
|
|
179
|
+
response = requests.get(url, verify=verify_arg, timeout=timeout)
|
|
180
|
+
if response.status_code == 200:
|
|
181
|
+
parsed_url = urlparse(url)
|
|
182
|
+
zip_filename = os.path.basename(parsed_url.path)
|
|
183
|
+
folder_name = os.path.splitext(zip_filename)[0] # Remove the .zip extension
|
|
184
|
+
|
|
185
|
+
extraction_path = os.path.join(extract_to, folder_name)
|
|
186
|
+
os.makedirs(extraction_path, exist_ok=True)
|
|
187
|
+
|
|
188
|
+
zip_file = io.BytesIO(response.content)
|
|
189
|
+
with zipfile.ZipFile(zip_file) as z:
|
|
190
|
+
z.extractall(extraction_path)
|
|
191
|
+
print(f"Extracted to {extraction_path}")
|
|
192
|
+
else:
|
|
193
|
+
print(f"Failed to download the file. Status code: {response.status_code}")
|
|
194
|
+
except requests.exceptions.SSLError as e:
|
|
195
|
+
print("SSL error when downloading CityGML zip. You can pass 'ssl_verify=False' to skip verification, "
|
|
196
|
+
"or provide a CA bundle path via 'ca_bundle'. Error:", e)
|
|
197
|
+
raise
|
|
189
198
|
|
|
190
199
|
return extraction_path, folder_name
|
|
191
200
|
|
|
@@ -848,7 +857,10 @@ def swap_coordinates_if_needed(gdf, geometry_col='geometry'):
|
|
|
848
857
|
def load_buid_dem_veg_from_citygml(url=None,
|
|
849
858
|
base_dir='.',
|
|
850
859
|
citygml_path=None,
|
|
851
|
-
rectangle_vertices=None
|
|
860
|
+
rectangle_vertices=None,
|
|
861
|
+
ssl_verify=True,
|
|
862
|
+
ca_bundle=None,
|
|
863
|
+
timeout=60):
|
|
852
864
|
"""
|
|
853
865
|
Load and process PLATEAU data from URL or local files.
|
|
854
866
|
|
|
@@ -879,7 +891,9 @@ def load_buid_dem_veg_from_citygml(url=None,
|
|
|
879
891
|
rectangle_polygon = Polygon(rectangle_vertices)
|
|
880
892
|
|
|
881
893
|
if url:
|
|
882
|
-
citygml_path, foldername = download_and_extract_zip(
|
|
894
|
+
citygml_path, foldername = download_and_extract_zip(
|
|
895
|
+
url, extract_to=base_dir, ssl_verify=ssl_verify, ca_bundle=ca_bundle, timeout=timeout
|
|
896
|
+
)
|
|
883
897
|
elif citygml_path:
|
|
884
898
|
foldername = os.path.basename(citygml_path)
|
|
885
899
|
else:
|
voxcity/generator.py
CHANGED
|
@@ -921,8 +921,22 @@ def get_voxcity_CityGML(rectangle_vertices, land_cover_source, canopy_height_sou
|
|
|
921
921
|
# Remove 'output_dir' from kwargs to prevent duplication
|
|
922
922
|
kwargs.pop('output_dir', None)
|
|
923
923
|
|
|
924
|
+
# SSL/HTTP options for CityGML download (optional)
|
|
925
|
+
# Backward compatible: accept 'verify' but prefer 'ssl_verify'
|
|
926
|
+
ssl_verify = kwargs.pop('ssl_verify', kwargs.pop('verify', True))
|
|
927
|
+
ca_bundle = kwargs.pop('ca_bundle', None)
|
|
928
|
+
timeout = kwargs.pop('timeout', 60)
|
|
929
|
+
|
|
924
930
|
# get all required gdfs
|
|
925
|
-
building_gdf, terrain_gdf, vegetation_gdf = load_buid_dem_veg_from_citygml(
|
|
931
|
+
building_gdf, terrain_gdf, vegetation_gdf = load_buid_dem_veg_from_citygml(
|
|
932
|
+
url=url_citygml,
|
|
933
|
+
citygml_path=citygml_path,
|
|
934
|
+
base_dir=output_dir,
|
|
935
|
+
rectangle_vertices=rectangle_vertices,
|
|
936
|
+
ssl_verify=ssl_verify,
|
|
937
|
+
ca_bundle=ca_bundle,
|
|
938
|
+
timeout=timeout
|
|
939
|
+
)
|
|
926
940
|
|
|
927
941
|
# Normalize CRS to WGS84 (EPSG:4326) to ensure consistent operations downstream
|
|
928
942
|
try:
|
voxcity/simulator/solar.py
CHANGED
|
@@ -1007,10 +1007,15 @@ def get_global_solar_irradiance_using_epw(
|
|
|
1007
1007
|
output_dir=output_dir,
|
|
1008
1008
|
max_distance=max_distance,
|
|
1009
1009
|
extract_zip=True,
|
|
1010
|
-
load_data=True
|
|
1010
|
+
load_data=True,
|
|
1011
|
+
allow_insecure_ssl=kwargs.get("allow_insecure_ssl", False),
|
|
1012
|
+
allow_http_fallback=kwargs.get("allow_http_fallback", False),
|
|
1013
|
+
ssl_verify=kwargs.get("ssl_verify", True)
|
|
1011
1014
|
)
|
|
1012
1015
|
|
|
1013
1016
|
# Read EPW data
|
|
1017
|
+
if epw_file_path is None:
|
|
1018
|
+
raise RuntimeError("EPW file path is None. Set 'epw_file_path' or enable 'download_nearest_epw' and ensure network succeeds.")
|
|
1014
1019
|
df, lon, lat, tz, elevation_m = read_epw_for_solar_simulation(epw_file_path)
|
|
1015
1020
|
if df.empty:
|
|
1016
1021
|
raise ValueError("No data in EPW file.")
|
|
@@ -2030,10 +2035,15 @@ def get_building_global_solar_irradiance_using_epw(
|
|
|
2030
2035
|
output_dir=output_dir,
|
|
2031
2036
|
max_distance=max_distance,
|
|
2032
2037
|
extract_zip=True,
|
|
2033
|
-
load_data=True
|
|
2038
|
+
load_data=True,
|
|
2039
|
+
allow_insecure_ssl=kwargs.get("allow_insecure_ssl", False),
|
|
2040
|
+
allow_http_fallback=kwargs.get("allow_http_fallback", False),
|
|
2041
|
+
ssl_verify=kwargs.get("ssl_verify", True)
|
|
2034
2042
|
)
|
|
2035
2043
|
|
|
2036
2044
|
# Read EPW data
|
|
2045
|
+
if epw_file_path is None:
|
|
2046
|
+
raise RuntimeError("EPW file path is None. Set 'epw_file_path' or enable 'download_nearest_epw' and ensure network succeeds.")
|
|
2037
2047
|
df, lon, lat, tz, elevation_m = read_epw_for_solar_simulation(epw_file_path)
|
|
2038
2048
|
if df.empty:
|
|
2039
2049
|
raise ValueError("No data in EPW file.")
|
voxcity/utils/weather.py
CHANGED
|
@@ -209,7 +209,9 @@ def process_epw(epw_path: Union[str, Path]) -> Tuple[pd.DataFrame, Dict]:
|
|
|
209
209
|
# =============================================================================
|
|
210
210
|
|
|
211
211
|
def get_nearest_epw_from_climate_onebuilding(longitude: float, latitude: float, output_dir: str = "./", max_distance: Optional[float] = None,
|
|
212
|
-
extract_zip: bool = True, load_data: bool = True, region: Optional[Union[str, List[str]]] = None
|
|
212
|
+
extract_zip: bool = True, load_data: bool = True, region: Optional[Union[str, List[str]]] = None,
|
|
213
|
+
allow_insecure_ssl: bool = False, allow_http_fallback: bool = False,
|
|
214
|
+
ssl_verify: Union[bool, str] = True) -> Tuple[Optional[str], Optional[pd.DataFrame], Optional[Dict]]:
|
|
213
215
|
"""
|
|
214
216
|
Download and process EPW weather file from Climate.OneBuilding.Org based on coordinates.
|
|
215
217
|
|
|
@@ -243,6 +245,9 @@ def get_nearest_epw_from_climate_onebuilding(longitude: float, latitude: float,
|
|
|
243
245
|
plus legacy "Canada", "USA", "Caribbean" (Region 4).
|
|
244
246
|
Use "all" to scan every dataset.
|
|
245
247
|
If None, will auto-detect region based on coordinates.
|
|
248
|
+
allow_insecure_ssl (bool): If True, on SSL errors retry with certificate verification disabled.
|
|
249
|
+
allow_http_fallback (bool): If True, on SSL/network errors, also try HTTP (insecure) fallback.
|
|
250
|
+
ssl_verify (bool|str): Passed to requests as 'verify' parameter for HTTPS; can be False or CA bundle path.
|
|
246
251
|
|
|
247
252
|
Returns:
|
|
248
253
|
Tuple containing:
|
|
@@ -629,9 +634,36 @@ def get_nearest_epw_from_climate_onebuilding(longitude: float, latitude: float,
|
|
|
629
634
|
continue
|
|
630
635
|
tried.add(u)
|
|
631
636
|
try:
|
|
632
|
-
resp = requests.get(u, timeout=timeout_s)
|
|
637
|
+
resp = requests.get(u, timeout=timeout_s, verify=ssl_verify)
|
|
633
638
|
resp.raise_for_status()
|
|
634
639
|
return resp.content
|
|
640
|
+
except requests.exceptions.SSLError:
|
|
641
|
+
# Retry with user-controlled insecure SSL
|
|
642
|
+
if allow_insecure_ssl:
|
|
643
|
+
try:
|
|
644
|
+
resp = requests.get(u, timeout=timeout_s, verify=False)
|
|
645
|
+
resp.raise_for_status()
|
|
646
|
+
return resp.content
|
|
647
|
+
except requests.exceptions.RequestException:
|
|
648
|
+
if allow_http_fallback and u.lower().startswith("https://"):
|
|
649
|
+
insecure_url = "http://" + u.split("://", 1)[1]
|
|
650
|
+
try:
|
|
651
|
+
resp = requests.get(insecure_url, timeout=timeout_s)
|
|
652
|
+
resp.raise_for_status()
|
|
653
|
+
return resp.content
|
|
654
|
+
except requests.exceptions.RequestException:
|
|
655
|
+
pass
|
|
656
|
+
continue
|
|
657
|
+
else:
|
|
658
|
+
if allow_http_fallback and u.lower().startswith("https://"):
|
|
659
|
+
insecure_url = "http://" + u.split("://", 1)[1]
|
|
660
|
+
try:
|
|
661
|
+
resp = requests.get(insecure_url, timeout=timeout_s)
|
|
662
|
+
resp.raise_for_status()
|
|
663
|
+
return resp.content
|
|
664
|
+
except requests.exceptions.RequestException:
|
|
665
|
+
pass
|
|
666
|
+
continue
|
|
635
667
|
except requests.exceptions.HTTPError as he:
|
|
636
668
|
# Only continue on 404; raise on other HTTP errors
|
|
637
669
|
if getattr(he.response, "status_code", None) == 404:
|
|
@@ -658,9 +690,32 @@ def get_nearest_epw_from_climate_onebuilding(longitude: float, latitude: float,
|
|
|
658
690
|
List of dictionaries containing station metadata
|
|
659
691
|
"""
|
|
660
692
|
try:
|
|
661
|
-
# Download KML file with timeout
|
|
662
|
-
|
|
663
|
-
|
|
693
|
+
# Download KML file with timeout (secure first)
|
|
694
|
+
try:
|
|
695
|
+
response = requests.get(kml_url, timeout=30, verify=ssl_verify)
|
|
696
|
+
response.raise_for_status()
|
|
697
|
+
except requests.exceptions.SSLError:
|
|
698
|
+
if allow_insecure_ssl:
|
|
699
|
+
# Retry with certificate verification disabled (last resort)
|
|
700
|
+
try:
|
|
701
|
+
response = requests.get(kml_url, timeout=30, verify=False)
|
|
702
|
+
response.raise_for_status()
|
|
703
|
+
except requests.exceptions.RequestException:
|
|
704
|
+
# Try HTTP fallback if original was HTTPS and allowed
|
|
705
|
+
if allow_http_fallback and kml_url.lower().startswith("https://"):
|
|
706
|
+
insecure_url = "http://" + kml_url.split("://", 1)[1]
|
|
707
|
+
response = requests.get(insecure_url, timeout=30)
|
|
708
|
+
response.raise_for_status()
|
|
709
|
+
else:
|
|
710
|
+
raise
|
|
711
|
+
else:
|
|
712
|
+
# Try HTTP fallback only if allowed and original was HTTPS
|
|
713
|
+
if allow_http_fallback and kml_url.lower().startswith("https://"):
|
|
714
|
+
insecure_url = "http://" + kml_url.split("://", 1)[1]
|
|
715
|
+
response = requests.get(insecure_url, timeout=30)
|
|
716
|
+
response.raise_for_status()
|
|
717
|
+
else:
|
|
718
|
+
raise
|
|
664
719
|
|
|
665
720
|
# Try to decode content with multiple encodings
|
|
666
721
|
content = try_decode(response.content)
|
|
@@ -917,8 +972,15 @@ def read_epw_for_solar_simulation(epw_file_path):
|
|
|
917
972
|
Raises:
|
|
918
973
|
ValueError: If LOCATION line not found or data parsing fails
|
|
919
974
|
"""
|
|
975
|
+
# Validate input path
|
|
976
|
+
if epw_file_path is None:
|
|
977
|
+
raise TypeError("EPW file path is None. Provide a valid path or ensure download succeeded.")
|
|
978
|
+
epw_path_obj = Path(epw_file_path)
|
|
979
|
+
if not epw_path_obj.exists() or not epw_path_obj.is_file():
|
|
980
|
+
raise FileNotFoundError(f"EPW file not found: {epw_file_path}")
|
|
981
|
+
|
|
920
982
|
# Read the entire EPW file
|
|
921
|
-
with open(
|
|
983
|
+
with open(epw_path_obj, 'r', encoding='utf-8') as f:
|
|
922
984
|
lines = f.readlines()
|
|
923
985
|
|
|
924
986
|
# Find the LOCATION line (first line in EPW format)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
voxcity/__init__.py,sha256=el9v3gfybHOF_GUYPeSOqN0-vCrTW0eU1mcvi0sEfeU,252
|
|
2
2
|
voxcity/downloader/__init__.py,sha256=o_T_EU7hZLGyXxX9wVWn1x-OAa3ThGYdnpgB1_2v3AE,151
|
|
3
|
-
voxcity/downloader/citygml.py,sha256=
|
|
3
|
+
voxcity/downloader/citygml.py,sha256=I8-wWijqVOA1VeH3nFP9ZlC3l6XvXfli6lB17ZIXHb0,42232
|
|
4
4
|
voxcity/downloader/eubucco.py,sha256=ln1YNaaOgJfxNfCtVbYaMm775-bUvpAA_LDv60_i22w,17875
|
|
5
5
|
voxcity/downloader/gee.py,sha256=nvJvYqcSZyyontRtG2cFeb__ZJfeY4rRN1NBPORxLwQ,23557
|
|
6
6
|
voxcity/downloader/mbfp.py,sha256=UXDVjsO0fnb0fSal9yqrSFEIBThnRmnutnp08kZTmCA,6595
|
|
@@ -14,7 +14,7 @@ voxcity/exporter/envimet.py,sha256=Sh7s1JdQ6SgT_L2Xd_c4gtEGWK2hTS87bccaoIqik-s,3
|
|
|
14
14
|
voxcity/exporter/magicavoxel.py,sha256=SfGEgTZRlossKx3Xrv9d3iKSX-HmfQJEL9lZHgWMDX4,12782
|
|
15
15
|
voxcity/exporter/netcdf.py,sha256=48rJ3wDsFhi9ANbElhMjXLxWMJoJzBt1gFbN0ekPp-A,7404
|
|
16
16
|
voxcity/exporter/obj.py,sha256=7xmVSQ_y-X8QLjNdASDPsaltGvmyW9-yAacocByYKl4,58160
|
|
17
|
-
voxcity/generator.py,sha256=
|
|
17
|
+
voxcity/generator.py,sha256=OHSZU4z7Vj6Iqbp1J0xXlFUSPyMsvOgPv3Tren8mEBc,67302
|
|
18
18
|
voxcity/geoprocessor/__init__.py,sha256=WYxcAQrjGucIvGHF0JTC6rONZzL3kCms1S2vpzS6KaU,127
|
|
19
19
|
voxcity/geoprocessor/draw.py,sha256=AZMWq23wxxDJygNloCbVzWAAr1JO7nC94umf9LSxJ5o,49248
|
|
20
20
|
voxcity/geoprocessor/grid.py,sha256=NmlQwl1nJpS7MduVtJeJCG-xBfVIwKTOip7pMm3RhsY,76722
|
|
@@ -23,16 +23,16 @@ voxcity/geoprocessor/network.py,sha256=YynqR0nq_NUra_cQ3Z_56KxfRia1b6-hIzGCj3QT-
|
|
|
23
23
|
voxcity/geoprocessor/polygon.py,sha256=DfzXf6R-qoWXEZv1z1aHCVfr-DCuCFw6lieQT5cNHPA,61188
|
|
24
24
|
voxcity/geoprocessor/utils.py,sha256=s17XpgkLBelmNCk2wcUwTK1tEiFpguWR2BF_n7K17jg,31378
|
|
25
25
|
voxcity/simulator/__init__.py,sha256=APdkcdaovj0v_RPOaA4SBvFUKT2RM7Hxuuz3Sux4gCo,65
|
|
26
|
-
voxcity/simulator/solar.py,sha256=
|
|
26
|
+
voxcity/simulator/solar.py,sha256=4D5t2I79vBW1qXd90BZR0tMiA9WOEXWLG5b-d6E2XbQ,107127
|
|
27
27
|
voxcity/simulator/utils.py,sha256=sEYBB2-hLJxTiXQps1_-Fi7t1HN3-1OPOvBCWtgIisA,130
|
|
28
28
|
voxcity/simulator/view.py,sha256=k3FoS6gsibR-eDrTHJivJSQfvN3Tg8R8eSTeMqd9ans,93942
|
|
29
29
|
voxcity/utils/__init__.py,sha256=Q-NYCqYnAAaF80KuNwpqIjbE7Ec3Gr4y_khMLIMhJrg,68
|
|
30
30
|
voxcity/utils/lc.py,sha256=722Gz3lPbgAp0mmTZ-g-QKBbAnbxrcgaYwb1sa7q8Sk,16189
|
|
31
31
|
voxcity/utils/material.py,sha256=H8K8Lq4wBL6dQtgj7esUW2U6wLCOTeOtelkTDJoRgMo,10007
|
|
32
32
|
voxcity/utils/visualization.py,sha256=_c5WnhA0fawkseUviKEIpUUsF-M-OOLOT9FrpgQIh1A,118556
|
|
33
|
-
voxcity/utils/weather.py,sha256=
|
|
34
|
-
voxcity-0.6.
|
|
35
|
-
voxcity-0.6.
|
|
36
|
-
voxcity-0.6.
|
|
37
|
-
voxcity-0.6.
|
|
38
|
-
voxcity-0.6.
|
|
33
|
+
voxcity/utils/weather.py,sha256=cb6ZooL42Hc4214OtFiJ78cCgWYM6VE-DU8S3e-urRg,48449
|
|
34
|
+
voxcity-0.6.20.dist-info/licenses/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
|
|
35
|
+
voxcity-0.6.20.dist-info/licenses/LICENSE,sha256=s_jE1Df1nTPL4A_5GCGic5Zwex0CVaPKcAmSilxJPPE,1089
|
|
36
|
+
voxcity-0.6.20.dist-info/METADATA,sha256=dRyklxMxGHwtTvE6lT3TfMFIlWBDZ354zt1Dr_Vv7qQ,26212
|
|
37
|
+
voxcity-0.6.20.dist-info/WHEEL,sha256=M5asmiAlL6HEcOq52Yi5mmk9KmTVjY2RDPtO4p9DMrc,88
|
|
38
|
+
voxcity-0.6.20.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|