PageSourceSearch

https://droneskycheck-d0136.web.app/js/core/account/mission-atomic-timeline-core.js?v=__VERSION__

js droneskycheck-d0136.web.app collected 2026-10-03 12:22:35 UTC 11,316 bytes, 201 lines download raw bytes

1(function initMissionAtomicTimeline(global, factory) {
2  const api = factory(global);
3  if (typeof module !== "undefined" && module.exports) module.exports = api;
4  if (global) global.DSCMissionAtomicTimeline = api;
5})(typeof window !== "undefined" ? window : globalThis, function buildMissionAtomicTimelineCore(global) {
6  "use strict";
7
8  const MAX_DURATION_MS = 168 * 60 * 60 * 1000;
9  const MAX_WEATHER_SLOTS = 169;
10  const MAX_ATOMICS = 512;
11  const LIGHT_STATES = new Set(["DAYLIGHT", "NIGHT", "UNKNOWN"]);
12
13  class MissionAtomicTimelineInputError extends TypeError {
14    constructor(code, path, message = code) {
15      super(message);
16      this.name = "MissionAtomicTimelineInputError";
17      this.code = code;
18      this.path = path;
19    }
20  }
21
22  class MissionAtomicTimelineLimitError extends RangeError {
23    constructor(code, path, message = code) {
24      super(message);
25      this.name = "MissionAtomicTimelineLimitError";
26      this.code = code;
27      this.path = path;
28    }
29  }
30
31  function fail(code, path, message) { throw new MissionAtomicTimelineInputError(code, path, message); }
32  function limit(code, path) { throw new MissionAtomicTimelineLimitError(code, path); }
33  function isObject(value) { return value !== null && typeof value === "object" && !Array.isArray(value); }
34  function exactKeys(value, keys, path) {
35    if (!isObject(value)) fail("MALFORMED_INPUT", path);
36    const actual = Object.keys(value).sort();
37    const expected = [...keys].sort();
38    if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) fail("MALFORMED_INPUT", path);
39  }
40  function assertPlain(value, path = "input", seen = new Set()) {
41    if (value === null || ["string", "boolean"].includes(typeof value)) return;
42    if (typeof value === "number") { if (!Number.isFinite(value)) fail("MALFORMED_INPUT", path); return; }
43    if (typeof value !== "object" || seen.has(value)) fail("MALFORMED_INPUT", path);
44    const prototype = Object.getPrototypeOf(value);
45    if (prototype !== Object.prototype && prototype !== Array.prototype && prototype !== null) fail("MALFORMED_INPUT", path);
46    seen.add(value);
47    if (Array.isArray(value)) value.forEach((item, index) => assertPlain(item, `${path}[${index}]`, seen));
48    else Object.entries(value).forEach(([key, item]) => assertPlain(item, `${path}.${key}`, seen));
49    seen.delete(value);
50  }
51  function iso(value, path) {
52    if (typeof value !== "string") fail("MALFORMED_INPUT", path);
53    const ms = Date.parse(value);
54    if (!Number.isFinite(ms) || new Date(ms).toISOString() !== value) fail("MALFORMED_INPUT", path);
55    return { value, ms };
56  }
57  function validateInterval(value, path) {
58    exactKeys(value, ["fromUtc", "toUtc"], path);
59    const from = iso(value.fromUtc, `${path}.fromUtc`);
60    const to = iso(value.toUtc, `${path}.toUtc`);
61    if (to.ms <= from.ms || to.ms - from.ms > MAX_DURATION_MS) fail("MALFORMED_INPUT", path);
62    return { from, to };
63  }
64  function validateSegments(values, interval, path, withStatus = false) {
65    if (!Array.isArray(values) || !values.length) fail("MALFORMED_INPUT", path);
66    const segments = values.map((value, index) => {
67      exactKeys(value, withStatus ? ["fromUtc", "toUtc", "status"] : ["fromUtc", "toUtc"], `${path}[${index}]`);
68      const from = iso(value.fromUtc, `${path}[${index}].fromUtc`);
69      const to = iso(value.toUtc, `${path}[${index}].toUtc`);
70      if (to.ms <= from.ms || from.ms < interval.from.ms || to.ms > interval.to.ms) fail("MALFORMED_INPUT", path);
71      if (withStatus && !LIGHT_STATES.has(value.status)) fail("MALFORMED_INPUT", `${path}[${index}].status`);
72      return { from, to, status: withStatus ? value.status : undefined };
73    });
74    if (segments[0].from.ms !== interval.from.ms || segments[segments.length - 1].to.ms !== interval.to.ms) fail("MALFORMED_INPUT", path);
75    for (let index = 1; index < segments.length; index += 1) {
76      if (segments[index - 1].to.ms !== segments[index].from.ms) fail("MALFORMED_INPUT", path);
77    }
78    return segments;
79  }
80
81  function buildMissionAtomicTimeline(input) {
82    assertPlain(input);
83    exactKeys(input, ["schemaVersion", "interval", "legalSegments", "weatherSlots", "lightSegments"], "input");
84    if (input.schemaVersion !== 1) fail("UNSUPPORTED_SCHEMA", "input.schemaVersion");
85    const interval = validateInterval(input.interval, "input.interval");
86    const legal = validateSegments(input.legalSegments, interval, "input.legalSegments");
87    if (!Array.isArray(input.weatherSlots) || input.weatherSlots.length > MAX_WEATHER_SLOTS) {
88      if (Array.isArray(input.weatherSlots) && input.weatherSlots.length > MAX_WEATHER_SLOTS) limit("WEATHER_SLOT_LIMIT_EXCEEDED", "input.weatherSlots");
89      fail("MALFORMED_INPUT", "input.weatherSlots");
90    }
91    const weather = validateSegments(input.weatherSlots, interval, "input.weatherSlots");
92    const light = validateSegments(input.lightSegments, interval, "input.lightSegments", true);
93    const boundaries = new Set([interval.from.ms, interval.to.ms]);
94    for (const segments of [legal, weather, light]) segments.forEac
94h(segment => { boundaries.add(segment.from.ms); boundaries.add(segment.to.ms); });
95    const points = [...boundaries].sort((a, b) => a - b);
96    if (points.length - 1 > MAX_ATOMICS) limit("ATOMIC_INTERVAL_LIMIT_EXCEEDED", "output.atomicIntervals");
97    const atomicIntervals = [];
98    let legalIndex = 0; let weatherIndex = 0; let lightIndex = 0;
99    for (let index = 0; index < points.length - 1; index += 1) {
100      const fromMs = points[index]; const toMs = points[index + 1];
101      while (legalIndex < legal.length - 1 && fromMs >= legal[legalIndex].to.ms) legalIndex += 1;
102      while (weatherIndex < weather.length - 1 && fromMs >= weather[weatherIndex].to.ms) weatherIndex += 1;
103      while (lightIndex < light.length - 1 && fromMs >= light[lightIndex].to.ms) lightIndex += 1;
104      const legalSegment = legal[legalIndex]; const weatherSlot = weather[weatherIndex]; const lightSegment = light[lightIndex];
105      if (!legalSegment || fromMs < legalSegment.from.ms || toMs > legalSegment.to.ms ||
106          !weatherSlot || fromMs < weatherSlot.from.ms || toMs > weatherSlot.to.ms ||
107          !lightSegment || fromMs < lightSegment.from.ms || toMs > lightSegment.to.ms) fail("MALFORMED_INPUT", "input.timeline");
108      atomicIntervals.push({
109        index: atomicIntervals.length,
110        fromUtc: new Date(fromMs).toISOString(),
111        toUtc: new Date(toMs).toISOString(),
112        durationMinutes: (toMs - fromMs) / 60000,
113        legalSegmentIndex: legalIndex,
114        weatherSlotIndex: weatherIndex,
115        lightSegmentIndex: lightIndex
116      });
117    }
118    return {
119      schemaVersion: 1,
120      interval: { fromUtc: interval.from.value, toUtc: interval.to.value },
121      atomicIntervals
122    };
123  }
124
125  const romeFormatter = new Intl.DateTimeFormat("en-CA", { timeZone: "Europe/Rome", year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", hourCycle: "h23" });
126  function parts(ms) {
127    return Object.fromEntries(romeFormatter.formatToParts(new Date(ms)).filter(item => item.type !== "literal").map(item => [item.type, Number(item.value)]));
128  }
129  function zonedMs(year, month, day, hour = 0) {
130    let guess = Date.UTC(year, month - 1, day, hour, 0, 0, 0);
131    for (let index = 0; index < 3; index += 1) {
132      const actual = parts(guess);
133      const delta = Date.UTC(year, month - 1, day, hour) - Date.UTC(actual.year, actual.month - 1, actual.day, actual.hour, actual.minute, actual.second);
134      guess += delta;
135    }
136    return guess;
137  }
138  function addUtcDate(date, days) {
139    const next = new Date(Date.UTC(date.year, date.month - 1, date.day + days));
140    return { year: next.getUTCFullYear(), month: next.getUTCMonth() + 1, day: next.getUTCDate() };
141  }
142  function geometryContract() {
143    const value = global?.DSCMissionGeometryContract;
144    if (!value?.representativePoint) fail("UNSUPPORTED_CONTRACT", "input.operationalGeometry", "REPRESENTATIVE_POINT_UNAVAILABLE");
145    return value;
146  }
147
148  function buildMissionLightSegments(input) {
149    assertPlain(input);
150    exactKeys(input, ["schemaVersion", "interval", "operationalGeometry"], "input");
151    if (input.schemaVersion !== 1) fail("UNSUPPORTED_SCHEMA", "input.schemaVersion");
152    const interval = validateInterval(input.interval, "input.interval");
153    let representative;
154    try {
155      representative = geometryContract().representativePoint(input.operationalGeometry, feature => global?.turf?.pointOnFeature(feature));
156    } catch (error) {
157      if (error instanceof MissionAtomicTimelineInputError) throw error;
158      fail("UNSUPPORTED_CONTRACT", "input.operationalGeometry", "REPRESENTATIVE_POINT_UNAVAILABLE");
159    }
160    const coordinates = representative?.point?.coordinates;
161    if (!Array.isArray(coordinates) || !Number.isFinite(coordinates[0]) || !Number.isFinite(coordinates[1])) fail("MALFORMED_INPUT", "input.operationalGeometry");
162    const first = parts(interval.from.ms); const last = parts(interval.to.ms - 1);
163    let date = { year: first.year, month: first.month, day: first.day };
164    const endKey = `${last.year}-${last.month}-${last.day}
164`;
165    const lightSegments = [];
166    for (let guard = 0; guard < 10; guard += 1) {
167      const startMs = zonedMs(date.year, date.month, date.day, 0);
168      const next = addUtcDate(date, 1); const endMs = zonedMs(next.year, next.month, next.day, 0);
169      const anchor = new Date(zonedMs(date.year, date.month, date.day, 12));
170      let sunriseMs = null; let sunsetMs = null;
171      try {
172        const times = global?.SunCalc?.getTimes?.(anchor, coordinates[1], coordinates[0]);
173        const rise = times?.sunrise?.getTime?.(); const set = times?.sunset?.getTime?.();
174        if (Number.isFinite(rise) && Number.isFinite(set) && rise < set) { sunriseMs = rise; sunsetMs = set; }
175      } catch { /* UNKNOWN for this local day */ }
176      const clippedFrom = Math.max(startMs, interval.from.ms); const clippedTo = Math.min(endMs, interval.to.ms);
177      if (clippedTo > clippedFrom) {
178        const points = [clippedFrom, clippedTo];
179        if (sunriseMs !== null && sunriseMs > clippedFrom && sunriseMs < clippedTo) points.push(sunriseMs);
180        if (sunsetMs !== null && sunsetMs > clippedFrom && sunsetMs < clippedTo) points.push(sunsetMs);
181        points.sort((a, b) => a - b);
182        for (let index = 0; index < points.length - 1; index += 1) {
183          const fromMs = points[index]; const toMs = points[index + 1];
184          const status = sunriseMs === null || sunsetMs === null ? "UNKNOWN" : fromMs >= sunriseMs && fromMs < sunsetMs ? "DAYLIGHT" : "NIGHT";
185          lightSegments.push({ fromUtc: new Date(fromMs).toISOString(), toUtc: new Date(toMs).toISOString(), status });
186        }
187      }
188      if (`${date.year}-${date.month}-${date.day}` === endKey) break;
189      date = next;
190    }
191    if (!lightSegments.length || lightSegments[0].fromUtc !== interval.from.value || lightSegments[lightSegments.length - 1].toUtc !== interval.to.value) fail("MALFORMED_INPUT", "input.lightSegments");
192    return lightSegments;
193  }
194
195  return Object.freeze({
196    buildMissionAtomicTimeline,
197    buildMissionLightSegments,
198    MissionAtomicTimelineInputError,
199    MissionAtomicTimelineLimitError
200  });
201});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.