mirror of
https://github.com/acamarata/moon-sighting-dart.git
synced 2026-07-01 11:14:31 +00:00
106 lines
3.4 KiB
Dart
106 lines
3.4 KiB
Dart
/// Crescent visibility criteria: Yallop and Odeh.
|
|
///
|
|
/// Both criteria transform the geometric quantities (ARCL, ARCV, DAZ, W)
|
|
/// into a single score that maps to a visibility category.
|
|
///
|
|
/// References:
|
|
/// Yallop (1997), NAO Technical Note No. 69
|
|
/// Odeh (2006), Experimental Astronomy 18(1), 39-64
|
|
library;
|
|
|
|
import 'math.dart';
|
|
import 'bodies.dart';
|
|
import 'types.dart';
|
|
|
|
/// The polynomial ARCV minimum as a function of crescent width W (arc minutes).
|
|
///
|
|
/// arcv_min(W) = 11.8371 - 6.3226*W + 0.7319*W^2 - 0.1018*W^3
|
|
///
|
|
/// Represents the minimum arc of vision required for detection, derived
|
|
/// empirically from historical observations.
|
|
double arcvMinimum(double w) {
|
|
return 11.8371 - 6.3226 * w + 0.7319 * w * w - 0.1018 * w * w * w;
|
|
}
|
|
|
|
/// Compute the Yallop q parameter.
|
|
///
|
|
/// q = (ARCV - arcv_min(W')) / 10
|
|
double computeYallopQ(double arcv, double wprime) {
|
|
return (arcv - arcvMinimum(wprime)) / 10;
|
|
}
|
|
|
|
/// Map a q value to Yallop category.
|
|
YallopCategory yallopCategoryFromQ(double q) {
|
|
if (q > yallopThresholds[YallopCategory.a]!) return YallopCategory.a;
|
|
if (q > yallopThresholds[YallopCategory.b]!) return YallopCategory.b;
|
|
if (q > yallopThresholds[YallopCategory.c]!) return YallopCategory.c;
|
|
if (q > yallopThresholds[YallopCategory.d]!) return YallopCategory.d;
|
|
if (q > yallopThresholds[YallopCategory.e]!) return YallopCategory.e;
|
|
return YallopCategory.f;
|
|
}
|
|
|
|
/// Compute the full Yallop result from crescent geometry.
|
|
YallopResult computeYallop(CrescentGeometry geometry, double wprime) {
|
|
final q = computeYallopQ(geometry.arcv, wprime);
|
|
final category = yallopCategoryFromQ(q);
|
|
|
|
return YallopResult(
|
|
q: q,
|
|
category: category,
|
|
description: yallopDescriptions[category]!,
|
|
isVisibleNakedEye:
|
|
category == YallopCategory.a || category == YallopCategory.b,
|
|
requiresOpticalAid:
|
|
category == YallopCategory.c || category == YallopCategory.d,
|
|
isBelowDanjonLimit: category == YallopCategory.f,
|
|
wprime: wprime,
|
|
);
|
|
}
|
|
|
|
/// Compute the Odeh V parameter.
|
|
///
|
|
/// V = ARCV - arcv_min(W)
|
|
double computeOdehV(double arcv, double w) {
|
|
return arcv - arcvMinimum(w);
|
|
}
|
|
|
|
/// Map a V value to the Odeh zone.
|
|
OdehZone odehZoneFromV(double v) {
|
|
if (v >= odehThresholds[OdehZone.a]!) return OdehZone.a;
|
|
if (v >= odehThresholds[OdehZone.b]!) return OdehZone.b;
|
|
if (v >= odehThresholds[OdehZone.c]!) return OdehZone.c;
|
|
return OdehZone.d;
|
|
}
|
|
|
|
/// Compute the full Odeh result from crescent geometry.
|
|
OdehResult computeOdeh(CrescentGeometry geometry) {
|
|
final v = computeOdehV(geometry.arcv, geometry.w);
|
|
final zone = odehZoneFromV(v);
|
|
|
|
return OdehResult(
|
|
v: v,
|
|
zone: zone,
|
|
description: odehDescriptions[zone]!,
|
|
isVisibleNakedEye: zone == OdehZone.a,
|
|
isVisibleWithOpticalAid: zone == OdehZone.a || zone == OdehZone.b,
|
|
);
|
|
}
|
|
|
|
/// Compute crescent geometry quantities from airless topocentric positions.
|
|
CrescentGeometry computeCrescentGeometry({
|
|
required AzAlt moonAirless,
|
|
required AzAlt sunAirless,
|
|
required Vec3 moonTopo,
|
|
required Vec3 sunTopo,
|
|
}) {
|
|
final arcv = moonAirless.altitude - sunAirless.altitude;
|
|
|
|
var daz = sunAirless.azimuth - moonAirless.azimuth;
|
|
if (daz > 180) daz -= 360;
|
|
if (daz < -180) daz += 360;
|
|
|
|
final arcl = angularSep(moonTopo, sunTopo) * (180 / 3.141592653589793);
|
|
final (:w, wprime: _) = computeCrescentWidth(moonTopo, arcl);
|
|
|
|
return CrescentGeometry(arcl: arcl, arcv: arcv, daz: daz, w: w);
|
|
}
|