localSolarEclipse function

LocalSolarEclipse? localSolarEclipse(
  1. Ephemeris ephemeris,
  2. SolarEclipse eclipse, {
  3. required double latitude,
  4. required double longitude,
  5. double height = 0,
  6. double? deltaTSeconds,
})

Local circumstances of solar eclipse eclipse for a place at geodetic latitude and longitude (degrees, east positive) and height above sea level in metres; null if the eclipse is not seen there at all (the penumbra misses the place).

The Moon is a sphere with the radii of the NASA Five Millennium Canon (moonRadiusRatio, and moonRadiusRatioUmbra for the second and third contacts), so the limb profile is ignored: contacts II and III may differ from observed ones by a second or two. Polar motion is ignored.

deltaTSeconds overrides the ΔT model (it sets the Earth's orientation at a given moment of Terrestrial Time), e.g. to reproduce predictions made with a different ΔT.

Implementation

LocalSolarEclipse? localSolarEclipse(
  Ephemeris ephemeris,
  SolarEclipse eclipse, {
  required double latitude,
  required double longitude,
  double height = 0,
  double? deltaTSeconds,
}) {
  final observer = Observer(
    latitude: latitude,
    longitude: longitude,
    height: height,
  );
  _Local at(double t) =>
      _Local(_shadow(ephemeris, t), observer, t, deltaTSeconds);

  // Maksimum lokalne: najmniejsza odległość obserwatora od osi cienia.
  // Całe zaćmienie trwa najwyżej ok. 6 h, więc ±0,2 doby wystarcza.
  final start = eclipse.jdTT - 0.2, end = eclipse.jdTT + 0.2;
  final tMax = _minimize((t) => at(t).m, start, end);
  final max = at(tMax);
  if (max.m >= max.l1) return null;

  // Obserwator na brzegu półcienia (I, IV) i cienia (II, III).
  double outer(double t) {
    final l = at(t);
    return l.m - l.l1;
  }

  double inner(double t) {
    final l = at(t);
    return l.m - l.l2.abs();
  }

  final first = _root(outer, start, tMax);
  final fourth = _root(outer, tMax, end);
  double? second, third;
  if (max.m < max.l2.abs()) {
    second = _root(inner, first, tMax);
    third = _root(inner, tMax, fourth);
  }

  SolarEclipseContact contact(double t) {
    final local = at(t);
    final sun = horizontalPosition(
      ephemeris,
      Naif.sun,
      observer,
      t,
      deltaTSeconds: deltaTSeconds,
    );
    // Kierunek od obserwatora do osi cienia na płaszczyźnie podstawowej
    // = kierunek od środka Słońca do środka Księżyca na niebie.
    final p = math.atan2(local.u, local.v) / _degree;
    return SolarEclipseContact(
      jdTT: t,
      altitude: sun.altitude,
      azimuth: sun.azimuth,
      positionAngle: p < 0 ? p + 360 : p,
    );
  }

  // W jednostkach promienia Słońca: Księżyc ma promień r, odległość
  // środków c = 2m/(L1 + L2) (c = 1 + r przy m = L1, c = |1 − r| przy
  // m = |L2|).
  final sum = max.l1 + max.l2;
  final r = (max.l1 - max.l2) / sum;
  final c = 2 * max.m / sum;
  return LocalSolarEclipse(
    type: second == null
        ? SolarEclipseType.partial
        : max.l2 < 0
        ? SolarEclipseType.total
        : SolarEclipseType.annular,
    firstContact: contact(first),
    secondContact: second == null ? null : contact(second),
    thirdContact: third == null ? null : contact(third),
    maximum: contact(tMax),
    fourthContact: contact(fourth),
    magnitude: (max.l1 - max.m) / sum,
    obscuration: _overlap(r, c),
    diameterRatio: r,
  );
}