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.

@@ -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
- response = requests.get(url)
175
- if response.status_code == 200:
176
- parsed_url = urlparse(url)
177
- zip_filename = os.path.basename(parsed_url.path)
178
- folder_name = os.path.splitext(zip_filename)[0] # Remove the .zip extension
179
-
180
- extraction_path = os.path.join(extract_to, folder_name)
181
- os.makedirs(extraction_path, exist_ok=True)
182
-
183
- zip_file = io.BytesIO(response.content)
184
- with zipfile.ZipFile(zip_file) as z:
185
- z.extractall(extraction_path)
186
- print(f"Extracted to {extraction_path}")
187
- else:
188
- print(f"Failed to download the file. Status code: {response.status_code}")
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(url, extract_to=base_dir)
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(url=url_citygml, citygml_path=citygml_path, base_dir=output_dir, rectangle_vertices=rectangle_vertices)
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:
@@ -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) -> Tuple[Optional[str], Optional[pd.DataFrame], Optional[Dict]]:
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
- response = requests.get(kml_url, timeout=30)
663
- response.raise_for_status()
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(epw_file_path, 'r', encoding='utf-8') as f:
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
  Metadata-Version: 2.4
2
2
  Name: voxcity
3
- Version: 0.6.19
3
+ Version: 0.6.20
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
  License: MIT
6
6
  License-File: AUTHORS.rst
@@ -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=vEsMDCjK64UwpCmQi6ELoqC7h5sbIUW8hWAxVf5ViNI,41321
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=4WSjO09f8z3zt0tpKY0fyAfOMym1JT4VH3Tt1lOg2Bk,66900
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=4WBFgMm25-9rZ5bSGBmIpaxq2mma9X46Fom7UvGEnT8,106361
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=WcmohW0yx2-uNMO5ffgxCtKUt32q6PQsAT43P5DwJx4,44760
34
- voxcity-0.6.19.dist-info/licenses/AUTHORS.rst,sha256=m82vkI5QokEGdcHof2OxK39lf81w1P58kG9ZNNAKS9U,175
35
- voxcity-0.6.19.dist-info/licenses/LICENSE,sha256=s_jE1Df1nTPL4A_5GCGic5Zwex0CVaPKcAmSilxJPPE,1089
36
- voxcity-0.6.19.dist-info/METADATA,sha256=AUmkbdSOZM7RywhizzSM_xzphJ-QLbQEuHx9IjvGSKQ,26212
37
- voxcity-0.6.19.dist-info/WHEEL,sha256=M5asmiAlL6HEcOq52Yi5mmk9KmTVjY2RDPtO4p9DMrc,88
38
- voxcity-0.6.19.dist-info/RECORD,,
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,,