voxcity.simulator_gpu.solar.integration.utilsยถ

Common utility functions for VoxCity solar integration module.

This module contains shared helper functions used across ground, building, and volumetric solar irradiance calculations to reduce code duplication.

Attributesยถ

Classesยถ

ArrayWithMetadata

NumPy array subclass that can hold metadata.

Functionsยถ

get_location_from_voxcity(โ†’ Tuple[float, float])

Extract latitude/longitude from VoxCity object or return defaults.

convert_voxel_data_to_arrays(โ†’ Tuple[numpy.ndarray, ...)

Convert VoxCity voxel codes to is_solid and LAD arrays using vectorized operations.

compute_valid_ground_vectorized(โ†’ Tuple[numpy.ndarray, ...)

Compute valid ground mask and ground k-levels using vectorized operations.

compute_ground_k_from_voxels(โ†’ numpy.ndarray)

Compute ground surface k-level for each (i,j) cell from voxel data.

compute_sun_direction(โ†’ Tuple[float, float, float, float])

Compute sun direction vector from azimuth and elevation angles.

parse_time_period(โ†’ Tuple[datetime.datetime, ...)

Parse start and end time strings.

filter_df_to_period(df, start_time, end_time, tz)

Filter weather DataFrame to specified time period and convert to UTC.

get_hour_range_from_period(โ†’ Tuple[int, int])

Get hour-of-year range from time period strings.

get_timezone_offset_from_location(โ†’ float)

Get UTC timezone offset (in hours) from longitude/latitude using timezonefinder.

generate_annual_hourly_dataframe([year])

Generate a pandas DataFrame with hourly timestamps for a full year.

load_epw_data(โ†’ Tuple)

Load EPW weather data, optionally downloading the nearest file.

get_solar_positions_astral(times, lon, lat)

Compute solar azimuth and elevation for given times and location using Astral.

extract_terrain_following_slice(โ†’ numpy.ndarray)

Extract a terrain-following 2D slice from a 3D flux field (vectorized).

accumulate_terrain_following_slice(โ†’ None)

Accumulate terrain-following values from a 3D flux field into a 2D map (vectorized, in-place).

add_metadata_to_array(โ†’ numpy.ndarray)

Add metadata dict to a numpy array as an attribute.

compute_boundary_vertical_mask(โ†’ numpy.ndarray)

Compute mask for vertical faces on domain boundary.

apply_computation_mask_to_faces(โ†’ numpy.ndarray)

Apply 2D computation mask to mesh face values.

Module Contentsยถ

voxcity.simulator_gpu.solar.integration.utils.VOXCITY_GROUND_CODE = -1ยถ
voxcity.simulator_gpu.solar.integration.utils.VOXCITY_TREE_CODE = -2ยถ
voxcity.simulator_gpu.solar.integration.utils.VOXCITY_BUILDING_CODE = -3ยถ
voxcity.simulator_gpu.solar.integration.utils.get_location_from_voxcity(voxcity, default_lat: float = 1.35, default_lon: float = 103.82) Tuple[float, float][source]ยถ

Extract latitude/longitude from VoxCity object or return defaults.

Parameters:
  • voxcity โ€“ VoxCity object with extras containing rectangle_vertices

  • default_lat โ€“ Default latitude if not found (Singapore)

  • default_lon โ€“ Default longitude if not found (Singapore)

Returns:

Tuple of (origin_lat, origin_lon)

voxcity.simulator_gpu.solar.integration.utils.convert_voxel_data_to_arrays(voxel_data: numpy.ndarray, default_lad: float = 1.0) Tuple[numpy.ndarray, numpy.ndarray][source]ยถ

Convert VoxCity voxel codes to is_solid and LAD arrays using vectorized operations.

This is 10-100x faster than triple-nested Python loops for large grids.

Parameters:
  • voxel_data โ€“ 3D array of VoxCity voxel class codes

  • default_lad โ€“ Default Leaf Area Density for tree voxels (mยฒ/mยณ)

Returns:

Tuple of (is_solid, lad) numpy arrays with same shape as voxel_data

voxcity.simulator_gpu.solar.integration.utils.compute_valid_ground_vectorized(voxel_data: numpy.ndarray) Tuple[numpy.ndarray, numpy.ndarray][source]ยถ

Compute valid ground mask and ground k-levels using vectorized operations.

Valid ground cells are those where: - The transition from solid to air/tree occurs - The solid below is not water (7,8,9) or building/underground (negative codes)

Parameters:

voxel_data โ€“ 3D array of VoxCity voxel class codes (ni, nj, nk)

Returns:

Tuple of (valid_ground 2D bool array, ground_k 2D int array) ground_k[i,j] = -1 means no valid ground found

voxcity.simulator_gpu.solar.integration.utils.compute_ground_k_from_voxels(voxel_data: numpy.ndarray) numpy.ndarray[source]ยถ

Compute ground surface k-level for each (i,j) cell from voxel data.

This finds the terrain top - the highest k where the cell below the first air cell is solid ground (not building). This is used for terrain-following height extraction in volumetric calculations.

Parameters:

voxel_data โ€“ 3D array of voxel class codes

Returns:

2D array of ground k-levels (ni, nj). -1 means no valid ground found.

voxcity.simulator_gpu.solar.integration.utils.compute_sun_direction(azimuth_degrees_ori: float, elevation_degrees: float, rotation_angle: float = 0) Tuple[float, float, float, float][source]ยถ

Compute sun direction vector from azimuth and elevation angles.

Parameters:
  • azimuth_degrees_ori โ€“ Solar azimuth in VoxCity convention (0=North, clockwise)

  • elevation_degrees โ€“ Solar elevation in degrees above horizon

  • rotation_angle โ€“ Grid rotation angle in degrees (clockwise, from voxcity.extras)

Returns:

Tuple of (sun_dir_x, sun_dir_y, sun_dir_z, cos_zenith)

voxcity.simulator_gpu.solar.integration.utils.parse_time_period(start_time: str, end_time: str) Tuple[datetime.datetime, datetime.datetime][source]ยถ

Parse start and end time strings.

Parameters:
  • start_time โ€“ Start time in format โ€˜MM-DD HH:MM:SSโ€™

  • end_time โ€“ End time in format โ€˜MM-DD HH:MM:SSโ€™

Returns:

Tuple of (start_dt, end_dt) datetime objects

Raises:

ValueError โ€“ If time format is invalid

voxcity.simulator_gpu.solar.integration.utils.filter_df_to_period(df, start_time: str, end_time: str, tz: float)[source]ยถ

Filter weather DataFrame to specified time period and convert to UTC.

Parameters:
  • df โ€“ pandas DataFrame with datetime index

  • start_time โ€“ Start time in format โ€˜MM-DD HH:MM:SSโ€™

  • end_time โ€“ End time in format โ€˜MM-DD HH:MM:SSโ€™

  • tz โ€“ Timezone offset in hours

Returns:

Tuple of (df_period_utc, df with hour_of_year column)

Raises:

ValueError โ€“ If time format is invalid or no data in period

voxcity.simulator_gpu.solar.integration.utils.get_hour_range_from_period(start_time: str, end_time: str) Tuple[int, int][source]ยถ

Get hour-of-year range from time period strings.

Parameters:
  • start_time โ€“ Start time in format โ€˜MM-DD HH:MM:SSโ€™

  • end_time โ€“ End time in format โ€˜MM-DD HH:MM:SSโ€™

Returns:

Tuple of (start_hour, end_hour) as hour-of-year values

voxcity.simulator_gpu.solar.integration.utils.get_timezone_offset_from_location(lon: float, lat: float) float[source]ยถ

Get UTC timezone offset (in hours) from longitude/latitude using timezonefinder.

Falls back to a simple longitude-based estimate if timezonefinder is not available.

Parameters:
  • lon โ€“ Longitude in degrees

  • lat โ€“ Latitude in degrees

Returns:

Timezone offset in hours (e.g. 9.0 for JST, -5.0 for EST)

voxcity.simulator_gpu.solar.integration.utils.generate_annual_hourly_dataframe(year: int = 2020)[source]ยถ

Generate a pandas DataFrame with hourly timestamps for a full year.

The DataFrame has a datetime index (timezone-naive) and no weather columns, suitable for DSH (Direct Sun Hours) calculations that only need solar position data and do not require weather/EPW data.

Parameters:

year โ€“ The year to generate timestamps for (default: 2020, a non-leap year is fine since solar geometry varies negligibly between years)

Returns:

pandas DataFrame with hourly datetime index spanning the full year

voxcity.simulator_gpu.solar.integration.utils.load_epw_data(epw_file_path: str | None = None, download_nearest_epw: bool = False, voxcity=None, **kwargs) Tuple[source]ยถ

Load EPW weather data, optionally downloading the nearest file.

Parameters:
  • epw_file_path โ€“ Path to EPW file (required if download_nearest_epw=False)

  • download_nearest_epw โ€“ If True, download nearest EPW based on location

  • voxcity โ€“ VoxCity object (needed for location when downloading)

  • **kwargs โ€“ Additional parameters (output_dir, max_distance, rectangle_vertices)

Returns:

Tuple of (df, lon, lat, tz) where df is the weather DataFrame

Raises:
  • ValueError โ€“ If EPW file not provided and download_nearest_epw=False

  • ImportError โ€“ If required modules not available

voxcity.simulator_gpu.solar.integration.utils.get_solar_positions_astral(times, lon: float, lat: float)[source]ยถ

Compute solar azimuth and elevation for given times and location using Astral.

Parameters:
  • times โ€“ Pandas DatetimeIndex of times (should be timezone-aware, preferably UTC)

  • lon โ€“ Longitude in degrees

  • lat โ€“ Latitude in degrees

Returns:

DataFrame indexed by times with columns [โ€˜azimuthโ€™, โ€˜elevationโ€™] in degrees

voxcity.simulator_gpu.solar.integration.utils.extract_terrain_following_slice(flux_3d: numpy.ndarray, ground_k: numpy.ndarray, height_offset_k: int, is_solid: numpy.ndarray) numpy.ndarray[source]ยถ

Extract a terrain-following 2D slice from a 3D flux field (vectorized).

For each (i,j), extracts the value at ground_k[i,j] + height_offset_k. Cells that are solid at the extraction point, have no valid ground, or are above the domain are marked as NaN.

Parameters:
  • flux_3d โ€“ 3D array of flux values (ni, nj, nk)

  • ground_k โ€“ 2D array of ground k-levels (ni, nj), -1 means no valid ground

  • height_offset_k โ€“ Number of cells above ground to extract

  • is_solid โ€“ 3D array marking solid cells (ni, nj, nk)

Returns:

2D array of extracted values (ni, nj) with NaN for invalid cells

voxcity.simulator_gpu.solar.integration.utils.accumulate_terrain_following_slice(cumulative_map: numpy.ndarray, flux_3d: numpy.ndarray, ground_k: numpy.ndarray, height_offset_k: int, is_solid: numpy.ndarray, weight: float = 1.0) None[source]ยถ

Accumulate terrain-following values from a 3D flux field into a 2D map (vectorized, in-place).

For each (i,j), adds flux_3d[i,j,k_extract] * weight to cumulative_map[i,j] where k_extract = ground_k[i,j] + height_offset_k.

Parameters:
  • cumulative_map โ€“ 2D array to accumulate into (ni, nj), modified in-place

  • flux_3d โ€“ 3D array of flux values (ni, nj, nk)

  • ground_k โ€“ 2D array of ground k-levels (ni, nj), -1 means no valid ground

  • height_offset_k โ€“ Number of cells above ground to extract

  • is_solid โ€“ 3D array marking solid cells (ni, nj, nk)

  • weight โ€“ Multiplier for values before accumulating (e.g., time_step_hours)

voxcity.simulator_gpu.solar.integration.utils.add_metadata_to_array(arr: numpy.ndarray, metadata: dict) numpy.ndarray[source]ยถ

Add metadata dict to a numpy array as an attribute.

Parameters:
  • arr โ€“ Input numpy array

  • metadata โ€“ Dictionary of metadata to attach

Returns:

Array with metadata attribute

voxcity.simulator_gpu.solar.integration.utils.compute_boundary_vertical_mask(mesh_face_centers: numpy.ndarray, mesh_face_normals: numpy.ndarray, grid_bounds: numpy.ndarray, boundary_epsilon: float) numpy.ndarray[source]ยถ

Compute mask for vertical faces on domain boundary.

Parameters:
  • mesh_face_centers โ€“ (N, 3) array of face center coordinates

  • mesh_face_normals โ€“ (N, 3) array of face normal vectors

  • grid_bounds โ€“ (2, 3) array of [[min_x, min_y, min_z], [max_x, max_y, max_z]]

  • boundary_epsilon โ€“ Tolerance for boundary detection

Returns:

Boolean mask (N,) - True for vertical boundary faces

voxcity.simulator_gpu.solar.integration.utils.apply_computation_mask_to_faces(values: numpy.ndarray, mesh_face_centers: numpy.ndarray, computation_mask: numpy.ndarray, meshsize: float, grid_shape: Tuple[int, int]) numpy.ndarray[source]ยถ

Apply 2D computation mask to mesh face values.

Parameters:
  • values โ€“ (N,) array of face values

  • mesh_face_centers โ€“ (N, 3) array of face center coordinates

  • computation_mask โ€“ 2D boolean mask matching grid_shape

  • meshsize โ€“ Grid cell size

  • grid_shape โ€“ (ny_vc, nx_vc) grid dimensions

Returns:

Modified values array with NaN for masked-out faces