Source code for openalea.astk.sky_sources

# -*- python -*-
#
#       Copyright 2016-2025 Inria - CIRAD - INRAe
#
#       Distributed under the Cecill-C License.
#       See accompanying file LICENSE.txt or copy at
#           http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html
#
#       WebSite : https://github.com/openalea-incubator/astk
#
#       File author(s): Christian Fournier <christian.fournier@inrae.fr>
#
# ==============================================================================
""" A collection of equation for modelling sun position, sun irradiance and sky
irradiance
"""
import numpy

from .icosphere import turtle_mesh, spherical_face_centers
from .sky_luminance import sky_luminance
from .sky_map import sky_grid, sky_map, sky_hi


[docs] def regular_sky(d_az=10, d_z=10, n_az=None, n_z=None): az, z, _ = sky_grid(d_az=d_az, d_z=d_z, n_az=n_az, n_z=n_z) return [*zip(90 - z.flatten(), az.flatten())]
[docs] def hierarchical_turtle(sectors=46): # Hierarchical direction from turtle.xls file of J Dauzat elevations_h = [90.] + [26.57] * 5 + [52.62] * 5 + [10.81] * 5 + [69.16] * 5 + [ 47.41] * 5 + [31.08] * 10 + [9.23] * 10 az1 = [0, 72, 144, 216, 288] az2 = [36, 108, 180, 252, 324] azimuths_h = [180.] + az1 + az2 * 2 + az1 * 2 + [ 23.27, 48.73, 95.27, 120.73, 167.27, 192.73, 239.27, 264.73, 311.27, 336.73] + [ 12.23, 59.77, 84.23, 131.77, 156.23, 203.77, 228.23, 275.77, 300.23, 347.77] nb_sect = [1, 6, 16, 46][numpy.searchsorted([1, 6, 16, 46], min(46, sectors))] return [*zip(elevations_h[:nb_sect], azimuths_h[:nb_sect])]
[docs] def icospherical_turtle(sectors=46): sky_mesh = turtle_mesh(sectors) return spherical_face_centers(sky_mesh)
[docs] def sky_turtle(sectors=46): if sectors <= 46: return hierarchical_turtle(sectors) else: return icospherical_turtle(sectors)
[docs] def polar_angle(azimuth, north=90): """Convert azimuth (from north, positive clockwise) to polar angle (from X+, positive counter-clockwise) Args: azimuth (array-like): the azimuth angle (deg), from north, positive clockwise north: the angle between X+ and North (deg, positive counter-clockwise) """ az = numpy.array(azimuth, dtype=float) polar = north - az modulo = 360 # force to [0, modulo] range return (polar + modulo) % modulo
[docs] def sky_sources(sky_type='soc', sky_irradiance=None, sky_dirs=None, scale=None, sun_in_sky=False, force_hi=True): """ Light sources representing the sun and the sky in a scene Args: sky_type (str): sky type (see details below), one of ('soc', 'uoc', 'clear_sky', 'sun_soc', 'blended', 'all_weather'). sky_irradiance: a datetime indexed dataframe specifying sky irradiances for the period, such as returned by astk.meteorology.sky_irradiance.sky_irradiance. Needed for all sky_types except 'uoc' and 'soc' sky_dirs (list): a [(elevation,azimuth),...] list of directions sampling the sky hemisphere. If None (default) a hierarchical turtle discretisation of 46 directions is used. Azimuths are relative to North, positive clockwise scale (str): How should sun/sky luminance be scaled ? If None (default) luminance are scaled so that sun+sky horizontal irradiance equals one. Other options are: - 'ghi': sun+sky horizontal flux equals mean ghi (W.m-2.s-1) - 'ppfd' : sun+sky horizontal flux equals mean PPFD (micromolPAR.m-2.s-1) - 'global': sun+sky horizontal flux equals time-integrated global irradiance (MJ.m-2) - 'par': sun+sky horizontal flux equals time-integrated PPFD (molPAR.m-2) sun_in_sky: Should the sun be added to the sky ? If True, sky luminance is set to sun luminance in the sun region, and sun luminance list is emptied. Ignored for sky types 'uoc' and 'soc'. force_hi: if True (default), sky sources are rescaled to ensure that global horizontal irradiance of discretised sources is the same as the original sky luminance distribution. If False , no rescaling append, ensuring that global direct irradiance of sky is preserved Returns: sun, sky tuple sun and sky are lists of (elevation (degrees), azimuth (degrees, from North positive clockwise), horizontal_irradiance) tuples of sources representing the sun or the sky Details: sky_type refer to different sky models: - 'clear_sky', 'uoc' and 'soc' are CIE sky types defined in [1]. For all these types, only relative sky luminance are returned (sun source list is empty), and total sky horizontal irradiance equals one. - 'sun_soc' refers to the approach defined in [2] where direct normal irradiance comes from the sun, and diffuse horizontal irradiance comes from an CIE soc sky luminance distribution - 'blended' refers to the sky blending approach defined in [3], where direct normal irradiance comes from the sun, and diffuse horizontal irradiance comes from a blending of CIE clear-sky and CIE soc sky luminance distribution, that depends on sky clearness index - 'all_weather' refers to the approach defined in [4], where direct normal irradiance comes from the sun, and diffuse horizontal irradiance comes from a luminance distribution that depends on sky clearness and sky brightness [1] CIE, 2002, Spatial distribution of daylight CIE standard general sky, CIE standard, CIE Central Bureau, Vienna [2] Sinoquet H. & Bonhomme R. (1992) Modeling radiative transfer in mixed and row intercropping systems. Agricultural and Forest Meteorology 62, 219–240 [3] J. Mardaljevic. Daylight Simulation: Validation, Sky Models and Daylight Coefficients. PhD thesis, De Montfort University, Leicester, UK, 2000. p193,eq. 5-10 [4] R. Perez, R. Seals, J. Michalsky, "All-weather model for sky luminance distribution—Preliminary configuration and validation", Solar Energy, Volume 50, Issue 3, 1993, Pages 235-245 """ grid = sky_grid() sun, sky = sky_luminance(grid, sky_type=sky_type, sky_irradiance=sky_irradiance, scale=scale, sun_in_sky=sun_in_sky) if sky_dirs is None: sky_dirs = sky_turtle() sky_agg, grid_agg, _ = sky_map(grid, sky, sky_dirs, force_hi=force_hi) sky_irr = sky_hi(grid_agg, sky_agg) sky_elevation, sky_azimuth = zip(*sky_dirs) sky_sources = list(zip(sky_elevation, sky_azimuth, sky_irr)) if len(sun) > 0: sun_elevation, sun_azimuth, _ = zip(*sun) sun_irr = source_hi(sun) sun_sources = list(zip(sun_elevation, sun_azimuth, sun_irr)) else: sun_sources = sun return sun_sources, sky_sources
[docs] def source_hi(src_ni): el, az, ni = zip(*src_ni) return ni * numpy.sin(numpy.radians(el))
[docs] def source_ni(src_hi): el, az, hi = zip(*src_hi) return numpy.where(el == 0, numpy.nan, hi / numpy.sin(numpy.radians(el)))
[docs] def caribu_light_sources(sun, sky, north=90): def _vect_dir(elevation, azimuth, north): """ coordinate of look_at source vector from elevation and azimuth (deg, f rom X+ positive counter-clockwise)""" theta = numpy.radians(90 - numpy.array(elevation)) phi = numpy.radians(polar_angle(azimuth, north)) return -numpy.sin(theta) * numpy.cos(phi), -numpy.sin(theta) * numpy.sin(phi), -numpy.cos(theta) el, az, irrad = zip(*(sun + sky)) x, y, z = _vect_dir(el, az, north) return [(irr, (xx, yy, zz)) for irr, xx, yy, zz in zip(irrad, x, y, z)]