File size: 2,785 Bytes
9f21d0a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// The shape of a satellite's sampled-position window, as arithmetic on plain
// numbers.
//
// It lives here rather than inside SampledTrajectory because two things have to
// agree on it exactly: the trajectory, which fills the window, and the
// propagation prefetch, which computes the same samples ahead of time in a
// worker. A prefetch whose sample times are half a step out of phase with the
// window is not a cache, it is dead weight — so there is one function and both
// callers use it.

/**
 * Samples per revolution. 120 is a compromise between accuracy and both of the
 * costs it drives: propagation time, and the memory the samples occupy (see
 * SampledTrajectory).
 */
export const SAMPLES_PER_ORBIT = 120;

/** Kept behind the satellite, as a fraction of one revolution. */
export const WINDOW_ORBITS_BACK = 0.5;

/** Kept ahead of it. The Orbit component needs a full revolution of it. */
export const WINDOW_ORBITS_FORWARD = 1.5;

export interface TrajectoryWindow {
  /** Seconds from the reference time to the first sample. Negative. */
  offsetSeconds: number;
  stepSeconds: number;
  /** How many samples span the whole window, first and last inclusive. */
  sampleCount: number;
  /** For the caller that wants an interval rather than a count. */
  spanSeconds: number;
}

/**
 * The window for one satellite, from its orbital period in minutes.
 *
 * `sampleCount` is deliberately derived the way the filling loop counts rather
 * than by dividing the span: the loop steps from the start while
 * `stop >= time`, so it lands on the stop boundary and takes it, giving one more
 * sample than the number of steps. At 120 samples an orbit over two orbits that
 * is 241, not 240 — measured against the running app, which reported exactly 241
 * samples per satellite.
 */
export function trajectoryWindow(orbitalPeriodMinutes: number): TrajectoryWindow {
  // A satrec that failed to parse reports no mean motion, and the period derived
  // from it is zero or infinite. Left alone that divides to NaN and reaches the
  // worker as a NaN-sized buffer, so it is answered with an empty window instead:
  // no samples to prefetch, and the caller propagates for itself as it would for
  // any other miss.
  if (!Number.isFinite(orbitalPeriodMinutes) || orbitalPeriodMinutes <= 0) {
    return { offsetSeconds: 0, stepSeconds: 0, sampleCount: 0, spanSeconds: 0 };
  }
  const orbitalPeriodSeconds = orbitalPeriodMinutes * 60;
  const stepSeconds = orbitalPeriodSeconds / SAMPLES_PER_ORBIT;
  const spanSeconds = orbitalPeriodSeconds * (WINDOW_ORBITS_BACK + WINDOW_ORBITS_FORWARD);
  return {
    offsetSeconds: -orbitalPeriodSeconds * WINDOW_ORBITS_BACK,
    stepSeconds,
    sampleCount: Math.floor(spanSeconds / stepSeconds) + 1,
    spanSeconds,
  };
}