longitudeCrossings function

List<LongitudeCrossing> longitudeCrossings(
  1. LongitudeFunction longitudeAt,
  2. double startJdTT,
  3. double endJdTT, {
  4. required double period,
  5. double offset = 0,
  6. double step = 1,
})

All moments in startJdTT … endJdTT when longitudeAt reaches offset + k·period degrees for any integer k, in time order.

Works through retrograde motion: the span is first cut at stations, and between stations the longitude changes in one direction only, so no crossing is missed. step (days) must be shorter than the shortest stretch of direct or retrograde motion and short enough for the longitude to change by less than 180° – 1 day suits the Sun, the Moon, the planets and the mean lunar points; use about 0.25 day for the true node and the osculating apogee.

Times are accurate to about 1 ms of the longitude model.

Implementation

List<LongitudeCrossing> longitudeCrossings(
  LongitudeFunction longitudeAt,
  double startJdTT,
  double endJdTT, {
  required double period,
  double offset = 0,
  double step = 1,
}) {
  final result = <LongitudeCrossing>[];
  for (final (a, b) in _pieces(longitudeAt, startJdTT, endJdTT, step)) {
    final la = longitudeAt(a);
    final lb = la + _wrap180(longitudeAt(b) - la);
    final retrograde = lb < la;
    // Values strictly after a and up to b in the direction of motion, so a
    // value at a span boundary is counted once.
    final low = retrograde ? lb : la, high = retrograde ? la : lb;
    var k = ((low - offset) / period).floor();
    final values = <double>[];
    for (var v = offset + k * period; v <= high; v = offset + ++k * period) {
      if (retrograde ? v >= low && v < high : v > low && v <= high) {
        values.add(v);
      }
    }
    if (retrograde) values.sort((x, y) => y.compareTo(x));
    for (final v in values) {
      final t = _solve((t) => _wrap180(longitudeAt(t) - v), a, b);
      result.add(LongitudeCrossing(t, _normalize(v), retrograde));
    }
  }
  return result;
}