~tomterl/public-inbox

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch

[PATCH thp] New module: +star

Carlos Une <une@fastmail.fm>
Details
Message ID
<20240412100934.8059-1-une@fastmail.fm>
DKIM signature
pass
Download raw message
Patch: +250 -4
---
 README.org        |   8 +-
 man/thp.1.scd     |   2 +
 man/thpd.5.scd    |  31 +++++++
 mods/star+star.ha | 213 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 250 insertions(+), 4 deletions(-)
 create mode 100644 mods/star+star.ha

diff --git a/README.org b/README.org
index 809b97d..488ae9a 100644
--- a/README.org
+++ b/README.org
@@ -32,16 +32,16 @@ one patche applied (as of now):
To build thp with all modules included, you need libgit2 and your platforms libgit2-dev equivalent installed, then issued

#+BEGIN_SRC sh :exports code
make EXTRA_MODS="project git clock timer shind uptime moon sun"
make EXTRA_MODS="project git clock timer shind uptime moon sun" test
make EXTRA_MODS="project git clock timer shind uptime moon sun star"
make EXTRA_MODS="project git clock timer shind uptime moon sun star" test
make PREFIX="${HOME}" install
#+END_SRC

To build with just the modules you use in your spec, use ~MODS~:

#+BEGIN_SRC sh :exports code
make MODS="path host project timer uptime moon sun"
make MODS="path host project timer uptime moon sun" test
make MODS="path host project timer uptime moon sun star"
make MODS="path host project timer uptime moon sun star" test
make PREFIX="${HOME}" install
#+END_SRC

diff --git a/man/thp.1.scd b/man/thp.1.scd
index cd6a11f..f070fe2 100644
--- a/man/thp.1.scd
+++ b/man/thp.1.scd
@@ -110,6 +110,8 @@ All modules are optionally compiled into the server executable.
*sunrise* and *sunset* display the time of sunrise and sunset, respectively.
	The location and time format can be configured. See thpd(5).

*star* displays the position of a star as seen by an observer on Earth.

# STYLES

Available colors are: black, red, green, yellow, blue, magenta, cyan, white,
diff --git a/man/thpd.5.scd b/man/thpd.5.scd
index 1673a01..6808162 100644
--- a/man/thpd.5.scd
+++ b/man/thpd.5.scd
@@ -204,6 +204,37 @@ sunrise.layout=%H:%M|
sunset.layout=%H:%M|
```

## star

Display the position of a star as seen by an observer on Earth.

- name[=Antares]
	Star name.

- ra[=16h29m24.0s]
	Right ascension coordinate.

- dec[=-26d25m55.0s]
	Declination coordinate.

- ao[=N]
	Azimuth origin. Azimuth is usually measured from the north direction.
To measure azimuth from the south, define ao=S.

- layout[=${NAME}(${AZIMUTH}°,${ALTITUDE}°)]
	Format string.

Note: The observer's coordinates on Earth are defined in the _sun_ section.

*example*:

```
[star]
name=Betelgeuse
ra=5h55m10.3s
dec=+7d24m25.4s
```

# SEE ALSO

*thpd*(1) *thp*(1) *thp*(5)
diff --git a/mods/star+star.ha b/mods/star+star.ha
new file mode 100644
index 0000000..7ced726
--- /dev/null
+++ b/mods/star+star.ha
@@ -0,0 +1,213 @@
// Author: Carlos Une <une@fastmail.fm>
// Maintainer: Carlos Une <une@fastmail.fm>
// SPDX-FileCopyrightText:  2024 Carlos Une <une@fastmail.fm>
// SPDX-License-Identifier: GPL-3.0-or-later
// Compute star position. The error in the calculation is approximately ±1 arcminute
// relative to the true value.
use config;
use env;
use fmt;
use io;
use math;
use memio;
use regex;
use strconv;
use strings;
use strings::template;
use time::date;

// Default observer coordinates. (São Paulo/BR)
def DEFAULT_LATITUDE = "-23.5898764";
def DEFAULT_LONGITUDE = "-46.6589231";
// Default star coordinates. (Antares/Alpha Scorpii)
def DEFAULT_STAR_NAME = "Antares";
def DEFAULT_STAR_RA = "16h29m24.0s";
def DEFAULT_STAR_DEC = "-26d25m55.0s";
def DEFAULT_STAR_LAYOUT = "${NAME}(${AZIMUTH}°,${ALTITUDE}°)";
// Standard epoch
def J2000: f64 = 2451545.0;
type azimuth_origin = enum { north, south };
type invalid = !void;
type coordinate_error = (regex::error | strconv::error | invalid);

type starconfig = struct {
	lat: f64,
	lon: f64,
	name: str,
	ra: f64,
	dec: f64,
	ao: azimuth_origin,
	template: template::template,
};

// Parse right ascension coordinate.
// Example Input: "5h55m10.3053s"; Output: 88.79293875 [decimal degrees]
fn parse_ra(s: str) (f64 | coordinate_error) = {
	let re = regex::compile(`([[:digit:]]{1,2})h([[:digit:]]{1,2})m([[:digit:]]{1,2}\.[[:digit:]]+)s`)?;
	defer regex::finish(&re);
	let cap = regex::findall(&re, s);
	defer regex::result_freeall(cap);

	if (len(cap) == 1) {
		let ra = 15.0*strconv::stof64(cap[0][1].content)?
		+ 15.0*strconv::stof64(cap[0][2].content)?/60.0
		+ 15.0*strconv::stof64(cap[0][3].content)?/3600.0;
		if (ra <= 360.0) {
			return ra;
		};
	};
	return invalid;
};

// Parse declination coordinate.
// Example Input: "+7d24m25.426s"; Output: 7.407062777777778 [decimal degrees]
fn parse_dec(s: str) (f64 | coordinate_error) = {
	let re = regex::compile(`([-+]?[[:digit:]]{1,2})d([[:digit:]]{1,2})m([[:digit:]]{1,2}\.[[:digit:]]+)s`)?;
	defer regex::finish(&re);
	let cap = regex::findall(&re, s);
	defer regex::result_freeall(cap);

	if (len(cap) == 1) {
		let degree = strconv::stof64(cap[0][1].content)?;
		let dec = math::signf64(degree): f64 *(math::absf64(degree)
		+ strconv::stof64(cap[0][2].content)?/60.0
		+ strconv::stof64(cap[0][3].content)?/3600.0);
		if (dec >= -90.0 && dec <= 90.0) {
			return dec;
		};
	};
	return invalid;
};

fn read_starconfig() (starconfig | error) = {
	let lat = match (strconv::stof64(config::setting("sun.latitude", DEFAULT_LATITUDE))) {
		case let x: f64 => yield x;
		case => return "mod_star:invalid latitude";
	};
	let lon = match (strconv::stof64(config::setting("sun.longitude", DEFAULT_LONGITUDE))) {
		case let x: f64 => yield x;
		case => return "mod_star:invalid longitude";
	};
	let ra = match (parse_ra(config::setting("star.ra", DEFAULT_STAR_RA))) {
		case let r: f64 => yield r;
		case => return "mod_star:invalid right ascension";
	};
	let dec = match (parse_dec(config::setting("star.dec", DEFAULT_STAR_DEC))) {
		case let d: f64 => yield d;
		case => return "mod_star:invalid declination";
	};
	let ao = switch (config::setting("star.ao", "N")) {
		case "N" => yield azimuth_origin::north;
		case "S" => yield azimuth_origin::south;
		case => return "mod_star:invalid azimuth_origin";
	};
	return starconfig {
		lat = lat,
		lon = lon,
		name = config::setting("star.name", DEFAULT_STAR_NAME),
		ra = ra,
		dec = dec,
		ao = ao,
		template = template::compile(config::setting("star.layout", DEFAULT_STAR_LAYOUT))! };
};

@init fn register_star() void = {
	register("star", &mod_star);
};

export fn mod_star(pe: *env::env) str = {
	let cfg = match (read_starconfig()) {
		case let c: starconfig => yield c;
		case let e: error => return strings::dup(e);
	};
	let JD = julian_day(date::now());
	let (alpha, delta) = precess(cfg.ra, cfg.dec, JD);
	let (az, alt) = to_horizontal(JD, cfg.lat, cfg.lon, alpha, delta, cfg.ao);
	let sink = memio::dynamic();
	match (template::execute(&cfg.template, &sink, ("NAME", cfg.name),
		("AZIMUTH", az: int), ("ALTITUDE", alt: int))) {
	case size =>
		return memio::string(&sink)!;
	case io::error =>
		return strings::dup("mod_star:template::execute() failed");
	};
};

// Compute the effects of precession on equatorial coordinates (α, δ).
// Precession Model: IAU 1976.
// Equinox J2000, FK5 reference frame.
// Reference: Meeus, J. — 'Astronomical Algorithms, 1991'
fn precess(alpha: f64, delta: f64, JD: f64) (f64, f64) = {
	let t = (JD - J2000)/36525.0;
	let tt = t*t;
	let ttt = tt*t;
	// Euler angles (ζ, z, θ).
	let zeta = 2306.2181*t + 0.30188*tt + 0.017998*ttt;
	let z = 2306.2181*t + 1.09468*tt + 0.018203*ttt;
	let theta = 2004.3109*t - 0.42665*tt - 0.041833*ttt;
	let arcsecond = 1.0/3600.0;
	z *= arcsecond;
	alpha = rad(alpha);
	delta = rad(delta);
	zeta = rad(zeta*arcsecond);
	theta = rad(theta*arcsecond);
	let A = math::cosf64(delta)*math::sinf64(alpha + zeta);
	let B = math::cosf64(theta)*math::cosf64(delta)*math::cosf64(alpha + zeta) - math::sinf64(theta)*math::sinf64(delta);
	let C = math::sinf64(theta)*math::cosf64(delta)*math::cosf64(alpha + zeta) + math::cosf64(theta)*math::sinf64(delta);
	let alpha2 = deg(math::atan2f64(A, B)) + z;
	if (alpha2 < 0.0) { alpha2 += 360.0; };
	return (alpha2, deg(math::asinf64(C)));
};

// Compute horizontal coordinates (azimuth, altitude).
// Reference: Meeus, J. — 'Astronomical Algorithms, 1991'
fn to_horizontal(JD: f64, lat: f64, lon: f64, alpha: f64, delta: f64, ao: azimuth_origin = azimuth_origin::north) (f64, f64) = {
	let mst = mean_sidereal_time_gmst(JD);
	let H = rad(mst + lon - alpha);
	lat = rad(lat);
	delta = rad(delta);
	let A = deg(math::atan2f64(math::sinf64(H), math::cosf64(H)*math::sinf64(lat) - math::tanf64(delta)*math::cosf64(lat)));
	let sinh = math::sinf64(lat)*math::sinf64(delta) + math::cosf64(lat)*math::cosf64(delta)*math::cosf64(H);
	let h = deg(math::asinf64(sinh));
	if (ao == azimuth_origin::north) {
		A += 180.0; // azimuth measured from the north
	};
	if (A > 360.0) { A -= 360.0; };
	return (A, h);
};

// Compute Julian Day
fn julian_day(d: date::date) f64 = {
	let Y = date::year(&d);
	let M = date::month(&d);
	let D = date::day(&d);
	let H = date::hour(&d);
	let MIN = date::minute(&d);
	let S = date::second(&d): f64 + date::nanosecond(&d): f64/1.0e9;
	return julian_day_ymd(Y, M, D) + ((H*3600 + MIN*60): f64 + S)/86400.0;
};

// Compute Julian Day
// Reference: Meeus, J. — 'Astronomical Algorithms, 1991'
fn julian_day_ymd(Y: int, M: int, D: int) f64 = {
	if (M <= 2) {
		Y -= 1;
		M += 12;
	};
	let A = Y/100;
	let B = if (Y > 1582 || (Y == 1582 && (M > 10 || (M == 10 && D > 4)))) {
		yield 2 - A + A/4;
	} else {
		yield 0;
	};
	return (365.25 * (Y + 4716): f64): int: f64 + (30.6001 * (M + 1): f64): int: f64 + D: f64 + B: f64 - 1524.5;
};

// Mean sidereal time at Greenwich — GMST
// Reference: Meeus, J. — 'Astronomical Algorithms, 1991'
fn mean_sidereal_time_gmst(JD: f64) f64 = {
	let T = (JD - J2000)/36525.0;
	let r = math::modf64(280.46061837 + 360.98564736629 * (JD - J2000) + (0.000387933*T*T) - (T*T*T)/38710000.0, 360.0);
	if (r < 0.0) r += 360.0;
	return r;
};
-- 
2.39.2
Reply to thread Export thread (mbox)