star-cem

Compute the position of the sun
git clone git://git.meso-star.com/star-cem.git
Log | Files | Refs | README | LICENSE

commit 4063e991dac98879f7af669cac8807a266cfb914
parent 013e2e967e1e1dd72cb8b369e8bce3cc8799433c
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date:   Fri, 19 Jun 2026 11:48:16 +0200

Merge branch 'release_0.1'

Diffstat:
MMakefile | 4++--
MREADME.md | 9+++++++++
Mconfig.mk | 6+++++-
Mdoc/scem.1 | 95+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Msrc/scem.c | 30++++++++++++++++++++++++++++++
Msrc/scem.h | 18+++++++++++++-----
Msrc/scem_main.c | 37++++++++++++++++++++++++++++---------
Msrc/scem_meeus.c | 2+-
Msrc/scem_psa.c | 95+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/test_scem_sun_position.c | 9++++++++-
10 files changed, 197 insertions(+), 108 deletions(-)

diff --git a/Makefile b/Makefile @@ -22,7 +22,7 @@ LIBNAME_SHARED = libscem.so LIBNAME = $(LIBNAME_$(LIB_TYPE)) default: library -all: default tests util +all: default tests util scem ################################################################################ # Library @@ -75,7 +75,7 @@ UTIL_DEP = $(UTIL_SRC:.c=.d) PKG_CONFIG_LOCAL = PKG_CONFIG_PATH="./:$${PKG_CONFIG_PATH}" $(PKG_CONFIG) INCS_UTIL = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --cflags rsys scem-local) -LIBS_UTIL = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --libs rsys scem-local) +LIBS_UTIL = $$($(PKG_CONFIG_LOCAL) $(PCFLAGS) --libs rsys scem-local) -lm CFLAGS_UTIL = $(CFLAGS_EXE) $(INCS_UTIL) LDFLAGS_UTIL = $(LDFLAGS_EXE) $(LIBS_UTIL) diff --git a/README.md b/README.md @@ -19,6 +19,15 @@ Edit config.mk as needed, then run: ## Release notes +### Version 0.1 + +- Rename zenith in elevation in man and code. + This breaks the API as the scem_sun_pos struct is modified. + However, the code still compute the same value: this is only a rename. +- Add an utility function to convert a scem_sun_pos to a 3D vector giving the + direction from the sun to the observer. +- Man wording improvement. + ### Version 0.0 - Provide the scem library, which calculates the position of the sun at diff --git a/config.mk b/config.mk @@ -1,4 +1,8 @@ -VERSION = 0.0.0 +VERSION_MAJOR = 0 +VERSION_MINOR = 1 +VERSION_PATCH = 0 +VERSION = $(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_PATCH) + PREFIX = /usr/local LIB_TYPE = SHARED diff --git a/doc/scem.1 b/doc/scem.1 @@ -23,7 +23,7 @@ .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .Sh SYNOPSIS .Nm -.Op Fl hvr +.Op Fl DhVvr .Op Fl a Ar algo .Op Fl d Ar utc_date .Ar latitude @@ -34,36 +34,43 @@ computes the position of the sun relative to a given point on the Earth's surface, on a given date. .Pp -The longitude of the Earth's position is between -.Bq -180, 180 -decimal degrees relative to Greenwich. -The angle is positive towards the east. -The latitude of the Earth's position is between -.Bq -90, 90 -decimal degrees relative to the equator. +The latitude must be in [-90, 90] degrees relative to the equator. It is positive towards the north. +The longitude must be in [-180, 180] degrees relative to Greenwich. +The angle is positive towards the east. .Pp -On output, -.Nm -displays the position of the sun in zenith and azimuth on the standard -output. -The message is formatted as follows: +The output depends on two options, +.Fl D +and +.Fl V . +If +.Fl D +is not provided, +.No +outputs the position of the sun in elevation and azimuth to the standard +output with the following format: .Bd -literal -offset Ds -"%f %f\\n", zenith, azimuth +"%f %f\\n", elevation, azimuth .Ed .Pp -The zenith angle is between -.Bq -90, 90 -degrees. +The elevation angle is in [-90, 90] degrees. It defines the elevation of the sun relative to the horizon. -The azimuth angle ranges from -.Bq 0, 360 -degrees, with 0° indicating north, +The azimuth angle is in [0, 360] degrees, with 0° indicating north, 90° east, 180° south, and 270° west. .Pp -The options are as follows +If +.Fl V +is provided, +.Nm +outputs the direction from the sun as a 3D vector to the standard output +with the following format: +.Bd -literal -offset Ds +"%f %f %f\\n", vec[0], vec[1], vec[2] +.Ed +.Pp +The options are as follows: .Bl -tag -width Ds .\"""""""""""""""""""""""""""""""""""""" .It Fl a Ar algo @@ -75,13 +82,15 @@ algorithm. The solar position algorithms are as follows: .Bl -tag -width Ds .It Cm meeus -The algorithm described in the Jean Meeus' book, -.Dq Astronomical Algorithm . +The algorithm described in Jean Meeus' book, +.Dq Astronomical Algorithms . .It Cm psa The algorithm developed by the .Dq Plataforma Solar de Almería . .El .\"""""""""""""""""""""""""""""""""""""" +.It Fl D +Suppress the sun direction from the output. .It Fl d Ar utc_date Date on which the position of the sun is calculated at the specified location. @@ -99,29 +108,37 @@ date -u +"%Y-%m-%dT%H:%M:%S" .It Fl h Output short help and exit. .\"""""""""""""""""""""""""""""""""""""" +.It Fl V +Add the 3D vector from the sun to the observer to the output with the first, +second and third components pointing east, north and up, respectively. .It Fl v Make .Nm verbose. .\"""""""""""""""""""""""""""""""""""""" .It Fl r -The solar output coordinates are defined in radians rather than degrees. +Output the solar direction in radians rather than degrees. +Meaningless if +.Fl D +is defined. .El .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -.Sh EXIT +.Sh EXIT STATUS .Ex -std .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .Sh EXAMPLES -Calculate the position of the sun on the current date for a latitude of -43.559962° east and a longitude of 1.468150° north: +Compute the current position of the sun for a latitude of 43.559962° north and a +longitude of 1.468150° east. +Output both the direction in radians and the corresponding 3D vector: .Bd -literal -offset Ds -scem 43.559962 1.468150 +scem -V -r 43.559962 1.468150 .Ed .Pp -Same as above, but setting the observation date to October 21, 2015, at -7:28:00 a.m.: +Same location as above, setting the observation date to October 21, 2015, at +11:28:00 AM UTC+2. +Output the 3D vector only: .Bd -literal -offset Ds -scem -d 2015-10-21T7:28:00 43.559962 1.468150 +scem -D -V -d 2015-10-21T09:28:00 43.559962 1.468150 .Ed .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .Sh SEE ALSO @@ -131,14 +148,10 @@ scem -d 2015-10-21T7:28:00 43.559962 1.468150 .%D 1991 .Re .Rs -.%A Manuel Blanco-Muriel -.%A Diego C. Alarcón-Padilla -.%A Teodoro López-Moratalla -.%A Martín Lara-Coira -.%T Computing the solar vector -.%J Solar Energy -.%V 70 -.%N 5 -.%D 2001 -.%P 431-441 +.%A Manuel Blanco +.%A Kypros Milidonis +.%A Aristides Bonanos +.%T PSA+ updated PSA sun position algorithm +.%U https://github.com/CST-Modelling-Tools/Updated-PSA-sun-position-algorithm +.%D 2025 .Re diff --git a/src/scem.c b/src/scem.c @@ -16,6 +16,8 @@ #include "scem.h" #include "scem_c.h" +#include <math.h> + /******************************************************************************* * Exported functions ******************************************************************************/ @@ -48,3 +50,31 @@ scem_sun_position_from_earth } return res; } + +res_T +scem_sun_position_to_sun_vector + (const struct scem_sun_pos* spherical, + double sun_dir[3]) +{ + double cos_azimuth; + double sin_azimuth; + double cos_elevation; + double sin_elevation; + double azimuth; + double elevation; + + if(!spherical || !sun_dir) return RES_BAD_ARG; + + azimuth = spherical->azimuth; + elevation = spherical->elevation; + + cos_azimuth = cos(azimuth); + sin_azimuth = sin(azimuth); + cos_elevation = cos(elevation); + sin_elevation = sin(elevation); + + sun_dir[0] = -(cos_elevation * sin_azimuth); + sun_dir[1] = -(cos_elevation * cos_azimuth); + sun_dir[2] = -(sin_elevation); + return RES_OK; +} diff --git a/src/scem.h b/src/scem.h @@ -39,7 +39,7 @@ enum scem_sun_algo { /* Sun position */ struct scem_sun_pos { /* In [-PI/2, PI/2]. Positive toward the sky */ - double zenith; /* [rad] */ + double elevation; /* [rad] */ /* In [0, 2PI] with 0 aligned with north, PI/2 east, PI south, and 3PI/2 west */ double azimuth; /* [rad] */ @@ -73,10 +73,10 @@ scem_sun_algo_cstr(const enum scem_sun_algo algo) BEGIN_DECLS -/* Compute the position of the Sun in local coordinates (zenith and azimuth) for - * a given observer at the surface of the Earth (can be extended to other - * planets if required) i.e. the observer has to be located at the surface for - * the required date */ +/* For a given location and time at the surface of the Earth (the observer has + * to be located at the surface), compute the position of the sun in local + * coordinates (elevation and azimuth) in radians. + * Could be extended to other planets if required. */ SCEM_API res_T scem_sun_position_from_earth (const struct tm* time, /* In UTC */ @@ -84,6 +84,14 @@ scem_sun_position_from_earth const enum scem_sun_algo algo, struct scem_sun_pos* sun); +/* Convert the position of the sun (elevation and azimuth) in radians, as + * produced by scem_sun_position_from_earth, to a 3D vector (sun to observer). + * First component east, second component north, third component up. */ +SCEM_API res_T +scem_sun_position_to_sun_vector + (const struct scem_sun_pos* sun, + double sun_vector[3]); + END_DECLS #endif /* SCEM_H */ diff --git a/src/scem_main.c b/src/scem_main.c @@ -18,6 +18,7 @@ #include "scem.h" +#include <rsys/rsys.h> #include <rsys/cstr.h> #include <rsys/mem_allocator.h> @@ -33,9 +34,11 @@ struct args { int radian; /* Sun position in radians instead of degrees */ int verbose; int quit; + int no_dir_output; + int vec_output; }; static const struct args ARGS_DEFAULT = { - SCEM_LOCATION_NULL__, {0}, SCEM_SUN_MEEUS, 0, 0, 0 + SCEM_LOCATION_NULL__, {0}, SCEM_SUN_MEEUS, 0, 0, 0, 0, 0 }; /******************************************************************************* @@ -45,7 +48,7 @@ static INLINE void usage(FILE* stream) { fprintf(stream, - "usage: scem [-hr] [-a algo] [-d utc_date] latitude longitude\n"); + "usage: scem [-DhrVv] [-a algo] [-d utc_date] latitude longitude\n"); } static res_T @@ -66,9 +69,11 @@ parse_algo(const char* str, enum scem_sun_algo* algo) static res_T parse_date(const char* str, struct tm* time) { + char *p; ASSERT(str && time); - if(!strptime(str, "%Y-%m-%dT%H:%M:%S\n", time)) { + p = strptime(str, "%Y-%m-%dT%H:%M:%S\n", time); + if(!p || *p != '\0') { /* strptime failed or trailing chars */ return RES_BAD_ARG; } else { return RES_OK; @@ -87,7 +92,10 @@ parse_dbl ASSERT(str && out_dbl && min <= max); if((res = cstr_to_double(str, &dbl)) != RES_OK) return res; - if(dbl < min || dbl > max) return RES_BAD_ARG; + if(dbl < min || dbl > max) { + fprintf(stderr, "scem: value not in range: %g [%g %g]\n", dbl, min, max); + return RES_BAD_ARG; + } *out_dbl = dbl; return RES_OK; @@ -119,11 +127,14 @@ args_init(struct args* args, int argc, char** argv) if((res = get_current_date(&args->time)) != RES_OK) goto error; - while((opt = getopt(argc, argv, "a:d:hvr")) != -1) { + /* Don't process the last 2 args as options, they are lat and long */ + while((opt = getopt(argc - 2, argv, "a:Dd:hVvr")) != -1) { switch(opt) { case 'a': res = parse_algo(optarg, &args->algo); break; + case 'D': args->no_dir_output = 1; break; case 'd': res = parse_date(optarg, &args->time); break; case 'h': usage(stdout); args->quit = 1; goto exit; + case 'V': args->vec_output = 1; break; case 'v': args->verbose = 1; break; case 'r': args->radian = 1; break; default: res = RES_BAD_ARG; break; @@ -159,6 +170,7 @@ main(int argc, char** argv) { struct args args = ARGS_DEFAULT; struct scem_sun_pos pos = SCEM_SUN_POS_NULL; + double sun_dir[3]; int err = 0; res_T res = RES_OK; @@ -174,10 +186,17 @@ main(int argc, char** argv) goto error; } - if(args.radian) { - printf("%g %g\n", pos.zenith, pos.azimuth); - } else { - printf("%g %g\n", MRAD2DEG(pos.zenith), MRAD2DEG(pos.azimuth)); + if(!args.no_dir_output) { + if(args.radian) { + printf("%g %g\n", pos.elevation, pos.azimuth); + } else { + printf("%g %g\n", MRAD2DEG(pos.elevation), MRAD2DEG(pos.azimuth)); + } + } + if(args.vec_output) { + res = scem_sun_position_to_sun_vector(&pos, sun_dir); + if(res != RES_OK) goto error; + fprintf(stderr, "%g %g %g\n", SPLIT3(sun_dir)); } exit: diff --git a/src/scem_meeus.c b/src/scem_meeus.c @@ -202,7 +202,7 @@ sun_position_from_earth_meeus /* Zenital angle: angle between the horizontal plane and the Sun direction */ latitude_rad = MDEG2RAD(pos->latitude /* [deg] */); - sun->zenith /* [rad] */ = asin + sun->elevation /* [rad] */ = asin ( sin(latitude_rad)*sin(celestial.declination) + cos(latitude_rad)*cos(celestial.declination)*cos(H_rad)); diff --git a/src/scem_psa.c b/src/scem_psa.c @@ -26,11 +26,11 @@ static double julian_day(const struct tm* time) /* In UTC */ { - double h = 0; /* Decimal hours */ - int month = 0; /* Month in [1, 12] */ - int year = 0; - long aux1 = 0; - long aux2 = 0; + double h; /* Decimal hours */ + int month; /* Month in [1, 12] */ + int year; + long aux1; + long aux2; ASSERT(time); @@ -42,11 +42,11 @@ julian_day(const struct tm* time) /* In UTC */ aux1 = (month - 14) / 12; aux2 = (1461 * (year + 4800 + aux1)) / 4 - + (367 * (month - 2 - 12*aux1)) / 12 - - (3 * ((year + 4900 + aux1)/100)) / 4 + + (367 * (month - 2 - 12 * aux1)) / 12 + - (3 * ((year + 4900 + aux1) / 100)) / 4 + time->tm_mday - 32075; - return (double)aux2 - 0.5 + h/24.0; + return (double)aux2 - 0.5 + h / 24.0; } /* Calculate ecliptic coordinates (ecliptic longitude and obliquity of the @@ -55,26 +55,26 @@ julian_day(const struct tm* time) /* In UTC */ static void sun_pos_ecliptic_coords(const double jd, struct ecliptic_coords* ecliptic) { - double mean_lon_rad=0; - double mean_anomaly=0; - double omega=0; - double t=0; + double mean_lon_rad; + double mean_anomaly; + double omega; + double t; ASSERT(jd >= 0 && ecliptic); t = (jd - 2451545.0); - omega = 2.1429 - 0.0010394594*t; - mean_lon_rad = 4.8950630 + t*0.017202791698; - mean_anomaly = 6.2400600 + t*0.0172019699; + omega = 2.267127827 - 0.00093003392670 * t; + mean_lon_rad = 4.895036035 + 0.01720279602 * t; + mean_anomaly = 6.239468336 + 0.01720200135 * t; ecliptic->longitude = /* [rad] */ mean_lon_rad - + 0.03341607*sin(mean_anomaly) - + 0.00034894*sin(mean_anomaly*2) - 0.0001134 - - 0.0000203 *sin(omega); + + 0.03338320972 * sin(mean_anomaly) + + 0.0003497596876 * sin(mean_anomaly * 2) - 0.0001544353226 + - 0.00000868972936 * sin(omega); ecliptic->obliquity = /* [rad] */ - 0.4090928 - 6.2140e-9*t + 0.0000396*cos(omega); + 0.4090904909 - 6.213605399e-9 * t + 0.00004418094944 * cos(omega); } /* Calculate celestial coordinates (right ascension and declination) in radians @@ -85,9 +85,9 @@ sun_pos_celestial_coords (const struct ecliptic_coords* ecliptic, struct celestial_coords* celestial) { - double sin_ecliptic_lon=0; /* [rad] */ - double y=0; - double x=0; + double sin_ecliptic_lon; /* [rad] */ + double y; + double x; sin_ecliptic_lon = sin(ecliptic->longitude); y = cos(ecliptic->obliquity) * sin_ecliptic_lon; @@ -106,35 +106,34 @@ sun_pos_local_coords const struct celestial_coords* celestial, struct scem_sun_pos* sun) { - const double earth_radius = 6371.01; /* [km] */ - const double astro_unit = 149597890.0; /* [km] */ - - double greenwitch_mean_side_real_time = 0; - double local_mean_side_real_time = 0; - double hour_angle = 0; - double lat_rad = 0; - double cos_lat = 0; - double sin_lat = 0; - double cos_hour_angle = 0; - double azimuth_rad = 0; - double zenith_rad = 0; - double parallax = 0; - double x = 0; - double y = 0; - double t = 0; - - double h = 0; + const double earth_radius = 6371.01; /* km */ + const double astro_unit = 149597870.7; /* km */ + + double greenwich_mean_side_real_time; + double local_mean_side_real_time; + double hour_angle; + double lat_rad; + double cos_lat; + double sin_lat; + double cos_hour_angle; + double azimuth_rad; + double zenith_rad; + double parallax; + double x; + double y; + double t; + double h; ASSERT(time && pos && celestial && sun); t = (jd - 2451545.0); h = (double)time->tm_hour - + ((double)time->tm_min + (double)time->tm_sec/60.0) / 60.0; + + ((double)time->tm_min + (double)time->tm_sec / 60.0) / 60.0; - greenwitch_mean_side_real_time = 6.6974243242 + 0.0657098283*t + h; + greenwich_mean_side_real_time = 6.697096103 + 0.06570984737 * t + h; local_mean_side_real_time = - MDEG2RAD(greenwitch_mean_side_real_time*15 + pos->longitude); + MDEG2RAD(greenwich_mean_side_real_time * 15 + pos->longitude); hour_angle = local_mean_side_real_time - celestial->right_ascension; lat_rad = MDEG2RAD(pos->latitude); @@ -143,20 +142,20 @@ sun_pos_local_coords cos_hour_angle = cos(hour_angle); zenith_rad = acos - ( cos_lat * cos(celestial->declination) * cos_hour_angle + (cos_lat * cos(celestial->declination) * cos_hour_angle + sin_lat * sin(celestial->declination)); y = -sin(hour_angle); x = tan(celestial->declination) * cos_lat - sin_lat*cos_hour_angle; azimuth_rad = atan2(y, x); - if(azimuth_rad < 0.0) azimuth_rad += 2*PI; + if(azimuth_rad < 0.0) azimuth_rad += 2 * PI; parallax = (earth_radius / astro_unit) * sin(zenith_rad); zenith_rad += parallax; - sun->azimuth = fmod(azimuth_rad, 2*PI); - sun->zenith = PI*0.5 - fmod(zenith_rad, 2*PI); + sun->azimuth = fmod(azimuth_rad, 2 * PI); + sun->elevation = PI * 0.5 - fmod(zenith_rad, 2 * PI); } /******************************************************************************* @@ -170,7 +169,7 @@ sun_position_from_earth_psa { struct ecliptic_coords ecliptic = ECLIPTIC_COORDS_NULL; struct celestial_coords celestial = CELESTIAL_COORDS_NULL; - double jd = 0; /* Julian Day */ + double jd; /* Julian Day */ ASSERT(time && pos && sun); diff --git a/src/test_scem_sun_position.c b/src/test_scem_sun_position.c @@ -33,6 +33,7 @@ test_api(void) struct tm utc = {0}; struct scem_location pos = SCEM_LOCATION_NULL; struct scem_sun_pos sun = SCEM_SUN_POS_NULL; + double vec[3]; utc.tm_sec = 0; utc.tm_min = 28; @@ -47,6 +48,10 @@ test_api(void) pos.longitude = 1.468150; printf("Lat: %g°; lon: %g°\n", pos.latitude, pos.longitude); + CHK(scem_sun_position_to_sun_vector(NULL, NULL) == RES_BAD_ARG); + CHK(scem_sun_position_to_sun_vector(&sun, NULL) == RES_BAD_ARG); + CHK(scem_sun_position_to_sun_vector(NULL, vec) == RES_BAD_ARG); + CHK(scem_sun_position_from_earth(NULL, &pos, algo, &sun) == RES_BAD_ARG); CHK(scem_sun_position_from_earth(&utc, NULL, algo, &sun) == RES_BAD_ARG); CHK(scem_sun_position_from_earth(&utc, &pos, SCEM_SUN_ALGO_NONE__, &sun) @@ -69,8 +74,10 @@ test_api(void) CHK(scem_sun_position_from_earth(&utc, &pos, algo, &sun) == RES_OK); printf("%s - zenith: %g°; azimuth: %g°\n", scem_sun_algo_cstr(algo), - MRAD2DEG(sun.zenith), + MRAD2DEG(sun.elevation), MRAD2DEG(sun.azimuth)); + CHK(scem_sun_position_to_sun_vector(&sun, vec) == RES_OK); + printf("Vector: %g %g %g\n", SPLIT3(vec)); } }