feat(группировка): реальная съёмка/сброс/связь и заполнение борта
Дашборд «Группировка» наполняется из плана: режимы SURVEY/DROP → интервалы съёмки и сброса с контуром и станцией, окна ЗРВ выделены в отдельную «связь» (раньше показывались как сброс). Заполнение бортового накопителя считается из объёмов маршрутов (volumeMb) с модельной ёмкостью 500 ГБ (ONBOARD_CAPACITY_MB) — memAt по таймлайну, подсветка контура/линии сброса на карте.
This commit is contained in:
@@ -44,7 +44,7 @@ function App({ env: extEnv, domain: extDomain, sched: extSched } = {}) {
|
||||
const liveRef = useRef(false);
|
||||
const focusRef = useRef(null);
|
||||
const hoverRef = useRef(null);
|
||||
const memRef = useRef(Object.fromEntries(env.sats.map((s) => [s.id, s.mem0])));
|
||||
const memRef = useRef(Object.fromEntries(env.sats.map((s) => [s.id, env.memAt ? env.memAt(s, env.epochMs) : s.mem0])));
|
||||
const lastT = useRef(performance.now());
|
||||
|
||||
const [, setTick] = useState(0);
|
||||
@@ -80,17 +80,24 @@ function App({ env: extEnv, domain: extDomain, sched: extSched } = {}) {
|
||||
else simRef.current += dt * speedRef.current;
|
||||
if (simRef.current > domain.end) simRef.current = domain.end;
|
||||
if (simRef.current < domain.start) simRef.current = domain.start;
|
||||
// memory dynamics
|
||||
const secEq = (dt * speedRef.current) / 1000;
|
||||
for (const s of env.sats) {
|
||||
const stt = SIM.statusAt(s, simRef.current, env);
|
||||
let m = memRef.current[s.id];
|
||||
if (stt.state === "capture") m += 0.7 * secEq;
|
||||
else if (stt.state === "downlink") m -= 1.5 * secEq;
|
||||
else m += 0.02 * secEq; // idle housekeeping drift
|
||||
memRef.current[s.id] = Math.max(0, Math.min(100, m));
|
||||
// memory dynamics — только когда нет авторитетного источника заполнения борта
|
||||
if (!env.memAt) {
|
||||
const secEq = (dt * speedRef.current) / 1000;
|
||||
for (const s of env.sats) {
|
||||
const stt = SIM.statusAt(s, simRef.current, env);
|
||||
let m = memRef.current[s.id];
|
||||
if (stt.state === "capture") m += 0.7 * secEq;
|
||||
else if (stt.state === "downlink") m -= 1.5 * secEq;
|
||||
else m += 0.02 * secEq; // idle housekeeping drift
|
||||
memRef.current[s.id] = Math.max(0, Math.min(100, m));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Реальное заполнение борта (объёмы маршрутов плана) — сэмплируем каждый кадр,
|
||||
// чтобы значение следовало и за скрабом на паузе.
|
||||
if (env.memAt) {
|
||||
for (const s of env.sats) memRef.current[s.id] = env.memAt(s, simRef.current);
|
||||
}
|
||||
if (apiRef.current.draw) apiRef.current.draw(simRef.current, { focusId: focusRef.current });
|
||||
acc += dt;
|
||||
if (acc >= 80) { acc = 0; setTick((t) => (t + 1) % 1e6); }
|
||||
@@ -164,6 +171,7 @@ function App({ env: extEnv, domain: extDomain, sched: extSched } = {}) {
|
||||
<div className="tl-legend">
|
||||
<LegItem c="#d99a4e" t="СЪЁМКА" />
|
||||
<LegItem c="#5b9bd5" t="СБРОС" />
|
||||
<LegItem c="rgba(111,143,174,0.6)" t="СВЯЗЬ С КА" />
|
||||
<LegItem c="#d9685f" t="ЗАПРЕТ РАБОТЫ" hatch />
|
||||
<LegItem c="rgba(120,140,180,0.5)" t="ТЕНЬ ЗЕМЛИ" />
|
||||
</div>
|
||||
@@ -206,6 +214,7 @@ function MapLegend() {
|
||||
<div className="ml-row"><span className="ml-sw" style={{ background: COL.radar }} />ЛОКАЦИЯ</div>
|
||||
<div className="ml-row"><span className="ml-sw amber" />МАРШРУТ СЪЁМКИ</div>
|
||||
<div className="ml-row"><span className="ml-sw blue dash" />СБРОС НА СТАНЦИЮ</div>
|
||||
<div className="ml-row"><span className="ml-sw dash" style={{ background: "#6f8fae" }} />СВЯЗЬ С КА</div>
|
||||
<div className="ml-row"><span className="ml-sw night" />ТЕНЬ ЗЕМЛИ</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -31,6 +31,7 @@ const MAP_C = {
|
||||
gratText: "rgba(150,180,178,0.25)",
|
||||
amber: "#d99a4e",
|
||||
blue: "#5b9bd5",
|
||||
link: "#6f8fae",
|
||||
red: "#d9685f",
|
||||
teal: "#5fb3a3",
|
||||
night: "rgba(20,28,46,0.55)",
|
||||
@@ -217,13 +218,32 @@ function MapView({ env, apiRef }) {
|
||||
}
|
||||
strokeTrack(ctx, seg, MAP_C.amber, dim ? 3 : 6, dim ? 0.3 : 0.85);
|
||||
ctx.setLineDash([]);
|
||||
tracePoly(ctx, s.proj, stt.roi.poly);
|
||||
ctx.fillStyle = "rgba(217,154,78,0.16)"; ctx.fill();
|
||||
ctx.strokeStyle = MAP_C.amber; ctx.lineWidth = 1.4; ctx.stroke();
|
||||
// контур съёмки есть не у всех режимов плана — рисуем только когда задан
|
||||
if (stt.roi && stt.roi.poly && stt.roi.poly.length) {
|
||||
tracePoly(ctx, s.proj, stt.roi.poly);
|
||||
ctx.fillStyle = "rgba(217,154,78,0.16)"; ctx.fill();
|
||||
ctx.strokeStyle = MAP_C.amber; ctx.lineWidth = 1.4; ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
// link (связь с КА) — пунктир к станции на всём окне радиовидимости (ЗРВ),
|
||||
// даже когда сброс не идёт. Тоньше/бледнее сброса; реальный сброс ляжет поверх.
|
||||
if (env.sched) {
|
||||
const lk = SIM.intervalAt((env.sched[sat.id] || {}).link, tMs);
|
||||
if (lk && lk.lon != null && lk.lat != null) {
|
||||
const lp = s.proj([lk.lon, lk.lat]);
|
||||
if (lp) {
|
||||
ctx.save();
|
||||
ctx.setLineDash([3, 6]); ctx.lineDashOffset = -dash;
|
||||
ctx.strokeStyle = hexA(MAP_C.link, dim ? 0.22 : 0.55); ctx.lineWidth = 1;
|
||||
ctx.beginPath(); ctx.moveTo(pt[0], pt[1]); ctx.lineTo(lp[0], lp[1]); ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// downlink (сброс) — animated dashed line to station
|
||||
if (stt.state === "downlink") {
|
||||
if (stt.state === "downlink" && stt.station && stt.station.lon != null && stt.station.lat != null) {
|
||||
const sp = s.proj([stt.station.lon, stt.station.lat]);
|
||||
if (sp) {
|
||||
ctx.save();
|
||||
|
||||
@@ -14,6 +14,7 @@ const TL_AXIS = 26;
|
||||
const TL_COL = {
|
||||
capture: "#d99a4e",
|
||||
downlink: "#5b9bd5",
|
||||
link: "rgba(111,143,174,0.38)", // связь с КА (ЗРВ) — фоновое окно радиовидимости
|
||||
prohibit: "#d9685f",
|
||||
shadow: "rgba(120,140,180,0.30)",
|
||||
};
|
||||
@@ -125,7 +126,9 @@ function Timeline({ env, sched, simTime, domain, focusId, onScrub, onFocus, live
|
||||
<line x1={TL_GUTTER} y1={cy} x2={w} y2={cy} stroke="rgba(255,255,255,0.04)" />
|
||||
{/* shadow (under) */}
|
||||
{(tr.shadow || []).map((iv, k) => band(iv, "sh" + k, TL_COL.shadow, TL_ROW - 8))}
|
||||
{/* downlink */}
|
||||
{/* link (связь с КА — окна радиовидимости, фоновая полоса) */}
|
||||
{(tr.link || []).map((iv, k) => band(iv, "lk" + k, TL_COL.link, TL_ROW - 10))}
|
||||
{/* downlink (реальный сброс из плана) */}
|
||||
{(tr.downlink || []).map((iv, k) => band(iv, "dl" + k, TL_COL.downlink, 8))}
|
||||
{/* capture */}
|
||||
{(tr.capture || []).map((iv, k) => band(iv, "cp" + k, TL_COL.capture, 10))}
|
||||
|
||||
@@ -10,19 +10,23 @@
|
||||
Возвращает { env, domain, sched } в том же виде, что buildEnv() прототипа,
|
||||
так что ConstellationDashboard рендерит реальные данные без изменений разметки.
|
||||
============================================================ */
|
||||
import { fetchPlatforms, fetchFlightLine, type FlightLinePoint } from "../../api/tguApi";
|
||||
import { fetchPlatforms, fetchFlightLine, fetchPlans, type FlightLinePoint } from "../../api/tguApi";
|
||||
import { fetchStations, type StationDto } from "../../api/stationsApi";
|
||||
import { fetchRequestsForMap } from "../../api/requestApi";
|
||||
import { fetchSatelliteRva } from "../../api/ballisticsApi";
|
||||
import type { TguPlatform } from "../../model/tguTypes";
|
||||
import { fetchPlanModes, type PlanMode } from "../tgu-editor/editorApi";
|
||||
import type { TguPlatform, TguPlan } from "../../model/tguTypes";
|
||||
import type { RequestMapItem } from "../../model/requestTypes";
|
||||
import { isOperationalPlatform } from "../tgu-planning/tguTimelineMapper";
|
||||
import { parseWktPolygon } from "../tgu-map-2d/geometry/wktParser";
|
||||
import { parseLocal } from "../../utils/localTime";
|
||||
// sim.js/data.js — JS-модули прототипа (allowJs); типы any, нам достаточно.
|
||||
import { buildSchedule, wrapLon } from "./sim.js";
|
||||
import { buildSchedule, wrapLon, pointInPoly } from "./sim.js";
|
||||
import { COL } from "./data.js";
|
||||
|
||||
/** Полный объём бортового накопителя, Мбайт (500 ГБ). Согласовано как модельная ёмкость. */
|
||||
const ONBOARD_CAPACITY_MB = 500 * 1024;
|
||||
|
||||
type Payload = "optic" | "radar";
|
||||
|
||||
export interface ConstellationSat {
|
||||
@@ -54,6 +58,20 @@ export interface Interval {
|
||||
start: number;
|
||||
end: number;
|
||||
label?: string;
|
||||
/** Контур съёмки (для подсветки на карте), [lon,lat][]. Только у интервалов capture. */
|
||||
poly?: [number, number][];
|
||||
/** Координаты станции приёма (для линии сброса на карте). Только у интервалов downlink. */
|
||||
lon?: number;
|
||||
lat?: number;
|
||||
}
|
||||
|
||||
export interface SchedTracks {
|
||||
capture: Interval[];
|
||||
downlink: Interval[];
|
||||
/** Связь с КА — окна радиовидимости (ЗРВ). Раньше показывались как «сброс». */
|
||||
link: Interval[];
|
||||
prohibit: Interval[];
|
||||
shadow: Interval[];
|
||||
}
|
||||
|
||||
export interface ConstellationEnv {
|
||||
@@ -64,12 +82,16 @@ export interface ConstellationEnv {
|
||||
prohibits: Record<string, Interval[]>;
|
||||
/** Подспутниковая точка по реальной трассе; null — нет данных на это время. */
|
||||
subSat: (sat: ConstellationSat, tMs: number) => { lon: number; lat: number } | null;
|
||||
/** Готовое расписание (съёмка/сброс/связь/тень) — из него statusAt берёт состояние. */
|
||||
sched?: Record<string, SchedTracks>;
|
||||
/** Заполнение бортового накопителя на момент tMs, % от ёмкости. Из объёмов маршрутов плана. */
|
||||
memAt?: (sat: ConstellationSat, tMs: number) => number;
|
||||
}
|
||||
|
||||
export interface ConstellationConfig {
|
||||
env: ConstellationEnv;
|
||||
domain: { start: number; end: number };
|
||||
sched: Record<string, { capture: Interval[]; downlink: Interval[]; prohibit: Interval[]; shadow: Interval[] }>;
|
||||
sched: Record<string, SchedTracks>;
|
||||
}
|
||||
|
||||
// --- классификация полезной нагрузки по имени КА (из НСИ) -------------------
|
||||
@@ -164,6 +186,115 @@ function compareSats(a: ConstellationSat, b: ConstellationSat): number {
|
||||
return a.name.localeCompare(b.name, "ru");
|
||||
}
|
||||
|
||||
// --- съёмка/сброс из реального плана -----------------------------------------
|
||||
type StationGeo = { name: string; lon: number; lat: number };
|
||||
|
||||
interface PlanTracks {
|
||||
capture: Interval[];
|
||||
downlink: Interval[];
|
||||
/** Точки изменения заполнения борта: +volumeMb на конце съёмки, −volumeMb на конце сброса. */
|
||||
memEvents: { t: number; delta: number }[];
|
||||
}
|
||||
|
||||
// Историю тянем так, чтобы покрыть начало горизонта (now-6ч): дни назад + запас.
|
||||
function planHistoryDays(domainStart: number): number {
|
||||
const back = Math.ceil(Math.max(0, Date.now() - domainStart) / 86400e3);
|
||||
return Math.max(1, back + 1);
|
||||
}
|
||||
|
||||
// Имя заявки, над которой идёт съёмка (по центру контура) — для подписи на таймлайне/карте.
|
||||
function captureLabel(lon: number, lat: number, rois: ConstellationRoi[]): string {
|
||||
for (const r of rois) if (pointInPoly(lon, lat, r.poly)) return r.name;
|
||||
return "Съёмка";
|
||||
}
|
||||
|
||||
// Режимы плана (SURVEY/DROP) → интервалы съёмки/сброса + дельты заполнения борта.
|
||||
function buildPlanTracks(
|
||||
modes: PlanMode[],
|
||||
rois: ConstellationRoi[],
|
||||
stationByNumber: Map<number, StationGeo>
|
||||
): PlanTracks {
|
||||
const capture: Interval[] = [];
|
||||
const downlink: Interval[] = [];
|
||||
const memEvents: { t: number; delta: number }[] = [];
|
||||
|
||||
for (const m of modes) {
|
||||
const start = m.timeStart ? parseLocal(m.timeStart) : NaN;
|
||||
if (!Number.isFinite(start)) continue;
|
||||
const end = start + (m.duration ?? 0) * 1000;
|
||||
const vol = m.volumeMb ?? 0;
|
||||
if (m.type === "SURVEY") {
|
||||
const rings = m.contourWkt ? parseWktPolygon(m.contourWkt) : [];
|
||||
const poly = rings[0]?.map((pt) => [wrapLon(pt.lon), pt.lat] as [number, number]);
|
||||
capture.push({ start, end, label: captureLabel(m.longitude, m.lat, rois), poly });
|
||||
memEvents.push({ t: end, delta: vol });
|
||||
} else if (m.type === "DROP") {
|
||||
const st = m.station != null ? stationByNumber.get(Number(m.station)) : undefined;
|
||||
downlink.push({
|
||||
start,
|
||||
end,
|
||||
label: st?.name ?? (m.station != null ? `ст. ${m.station}` : "Сброс"),
|
||||
lon: st?.lon,
|
||||
lat: st?.lat
|
||||
});
|
||||
memEvents.push({ t: end, delta: -vol });
|
||||
}
|
||||
}
|
||||
|
||||
capture.sort((a, b) => a.start - b.start);
|
||||
downlink.sort((a, b) => a.start - b.start);
|
||||
memEvents.sort((a, b) => a.t - b.t);
|
||||
return { capture, downlink, memEvents };
|
||||
}
|
||||
|
||||
// Планы из pcp-tgu, пересекающие горизонт, → их режимы по КА (spacecraftId == NORAD).
|
||||
async function loadPlanModesBySat(
|
||||
satIds: Set<string>,
|
||||
domainStart: number,
|
||||
domainEnd: number
|
||||
): Promise<Map<string, PlanMode[]>> {
|
||||
const byId = new Map<string, PlanMode[]>();
|
||||
let plans: TguPlan[];
|
||||
try {
|
||||
plans = await fetchPlans(planHistoryDays(domainStart));
|
||||
} catch {
|
||||
return byId; // нет планов — съёмка/сброс просто не покажутся
|
||||
}
|
||||
const relevant = plans.filter((p) => {
|
||||
if (!satIds.has(p.spacecraftId)) return false;
|
||||
const s = parseLocal(p.startTime);
|
||||
const e = parseLocal(p.endTime);
|
||||
return Number.isFinite(s) && Number.isFinite(e) && e >= domainStart && s <= domainEnd;
|
||||
});
|
||||
const loaded = await Promise.all(
|
||||
relevant.map((p) =>
|
||||
fetchPlanModes(p.planId)
|
||||
.then((m) => ({ sid: p.spacecraftId, m }))
|
||||
.catch(() => ({ sid: p.spacecraftId, m: [] as PlanMode[] }))
|
||||
)
|
||||
);
|
||||
for (const { sid, m } of loaded) {
|
||||
const arr = byId.get(sid) ?? [];
|
||||
arr.push(...m);
|
||||
byId.set(sid, arr);
|
||||
}
|
||||
return byId;
|
||||
}
|
||||
|
||||
// Заполнение борта на момент t: накопленный объём маршрутов (старт 0), % от ёмкости.
|
||||
function memorySampler(memEvents: { t: number; delta: number }[]): (t: number) => number {
|
||||
if (memEvents.length === 0) return () => 0;
|
||||
return (t: number) => {
|
||||
let mb = 0;
|
||||
for (const e of memEvents) {
|
||||
if (e.t > t) break;
|
||||
mb += e.delta;
|
||||
}
|
||||
const pct = (Math.max(0, Math.min(ONBOARD_CAPACITY_MB, mb)) / ONBOARD_CAPACITY_MB) * 100;
|
||||
return pct;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Грузит реальный состав и геометрию группировки и собирает конфиг дашборда.
|
||||
* Горизонт по умолчанию now-6ч … now+18ч (как в прототипе). КА без трассы
|
||||
@@ -187,8 +318,11 @@ export async function loadConstellationEnv(): Promise<ConstellationConfig> {
|
||||
lon: s.position.long,
|
||||
lat: s.position.lat
|
||||
}));
|
||||
const stationNameByNumber = new Map<number, string>(
|
||||
(stationDtos as StationDto[]).map((s) => [s.number, s.name])
|
||||
const stationByNumber = new Map<number, StationGeo>(
|
||||
(stationDtos as StationDto[]).map((s) => [
|
||||
s.number,
|
||||
{ name: s.name, lon: s.position.long, lat: s.position.lat }
|
||||
])
|
||||
);
|
||||
|
||||
const rois: ConstellationRoi[] = requestItems
|
||||
@@ -237,6 +371,7 @@ export async function loadConstellationEnv(): Promise<ConstellationConfig> {
|
||||
|
||||
sats.sort(compareSats);
|
||||
|
||||
// Запреты работы (ВНЗ/питание) — источника в проекте нет: оставляем пустыми (заготовка).
|
||||
const prohibits: Record<string, Interval[]> = Object.fromEntries(sats.map((s) => [s.id, []]));
|
||||
|
||||
const env: ConstellationEnv = {
|
||||
@@ -248,23 +383,42 @@ export async function loadConstellationEnv(): Promise<ConstellationConfig> {
|
||||
subSat: (sat, t) => (samplers[sat.id] ? samplers[sat.id](t) : null)
|
||||
};
|
||||
|
||||
// Съёмка (по пролёту над ROI), тень и геометрический сброс — из реальных позиций.
|
||||
// Шаг 0.5 мин, чтобы не проскочить мелкие полигоны заявок.
|
||||
// Съёмку и сброс берём из реального плана; тень считаем геометрически по трассе.
|
||||
const satIds = new Set(sats.map((s) => s.id));
|
||||
const planModesBySat = await loadPlanModesBySat(satIds, domain.start, domain.end);
|
||||
|
||||
// buildSchedule даёт нам тень (shadow) по реальным позициям; съёмку/сброс из него игнорируем.
|
||||
const sched = buildSchedule(sats, env, domain.start, domain.end, 0.5) as ConstellationConfig["sched"];
|
||||
|
||||
// Сброс заменяем авторитетными зонами радиовидимости, где они есть.
|
||||
const memSamplers: Record<string, (t: number) => number> = {};
|
||||
for (const s of sats) {
|
||||
const tracks = sched[s.id];
|
||||
// Съёмка и сброс — из режимов плана (SURVEY/DROP).
|
||||
const plan = buildPlanTracks(planModesBySat.get(s.id) ?? [], rois, stationByNumber);
|
||||
tracks.capture = plan.capture;
|
||||
tracks.downlink = plan.downlink;
|
||||
memSamplers[s.id] = memorySampler(plan.memEvents);
|
||||
|
||||
// «Связь с КА» — окна радиовидимости (ЗРВ). Раньше показывались как «сброс».
|
||||
const rva = rvaBySat[s.id];
|
||||
if (!rva || rva.length === 0) continue;
|
||||
sched[s.id].downlink = rva
|
||||
.map((z) => ({
|
||||
start: parseLocal(z.onStart.time),
|
||||
end: parseLocal(z.onStop.time),
|
||||
label: stationNameByNumber.get(z.stationId) ?? `ст. ${z.stationId}`
|
||||
}))
|
||||
tracks.link = (rva ?? [])
|
||||
.map((z) => {
|
||||
const st = stationByNumber.get(z.stationId);
|
||||
return {
|
||||
start: parseLocal(z.onStart.time),
|
||||
end: parseLocal(z.onStop.time),
|
||||
label: st?.name ?? `ст. ${z.stationId}`,
|
||||
lon: st?.lon,
|
||||
lat: st?.lat
|
||||
};
|
||||
})
|
||||
.filter((iv) => Number.isFinite(iv.start) && Number.isFinite(iv.end) && iv.end > iv.start)
|
||||
.sort((a, b) => a.start - b.start);
|
||||
}
|
||||
|
||||
// Расписание и заполнение борта — на env, чтобы statusAt/панель читали авторитетные данные.
|
||||
env.sched = sched;
|
||||
env.memAt = (sat, t) => (memSamplers[sat.id] ? memSamplers[sat.id](t) : 0);
|
||||
|
||||
return { env, domain, sched };
|
||||
}
|
||||
|
||||
@@ -109,12 +109,31 @@ const D2R = Math.PI / 180, R2D = 180 / Math.PI;
|
||||
return env.subSat ? env.subSat(sat, tMs) : subSat(sat, tMs, env.epochMs);
|
||||
}
|
||||
|
||||
// interval containing t in a sorted/unsorted list, else null
|
||||
function intervalAt(list, tMs) {
|
||||
if (!list) return null;
|
||||
for (const iv of list) if (tMs >= iv.start && tMs < iv.end) return iv;
|
||||
return null;
|
||||
}
|
||||
|
||||
// live status of a sat at time t (priority order)
|
||||
// returns {state, roi, station, eclipse, prohibit}
|
||||
// С реальным расписанием (env.sched) состояние берём из готовых интервалов плана
|
||||
// (съёмка/сброс/запрет); иначе — встроенная геометрическая симуляция прототипа.
|
||||
function statusAt(sat, tMs, env) {
|
||||
const p = subSatOf(sat, tMs, env);
|
||||
const prohibit = inProhibit(env.prohibits[sat.id], tMs);
|
||||
const eclipse = inShadow(p.lon, p.lat, tMs);
|
||||
if (env.sched) {
|
||||
const tr = env.sched[sat.id] || {};
|
||||
const prohibit = intervalAt(tr.prohibit, tMs);
|
||||
if (prohibit) return { state: "prohibit", p, prohibit: { reason: prohibit.label }, eclipse };
|
||||
const cap = intervalAt(tr.capture, tMs);
|
||||
if (cap) return { state: "capture", p, roi: { name: cap.label, poly: cap.poly }, eclipse };
|
||||
const dl = intervalAt(tr.downlink, tMs);
|
||||
if (dl) return { state: "downlink", p, station: { name: dl.label, lon: dl.lon, lat: dl.lat }, eclipse };
|
||||
return { state: "operational", p, eclipse };
|
||||
}
|
||||
const prohibit = inProhibit(env.prohibits[sat.id], tMs);
|
||||
if (prohibit) return { state: "prohibit", p, prohibit, eclipse };
|
||||
const roi = overROI(sat, p, env.rois);
|
||||
if (roi) return { state: "capture", p, roi, eclipse };
|
||||
@@ -160,5 +179,5 @@ const D2R = Math.PI / 180, R2D = 180 / Math.PI;
|
||||
export {
|
||||
D2R, R2D, wrapLon, subSat, subSolar, angDist, inShadow,
|
||||
pointInPoly, overROI, nearStation, buildProhibits, inProhibit,
|
||||
statusAt, buildSchedule, STATION_RANGE,
|
||||
statusAt, intervalAt, buildSchedule, STATION_RANGE,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user