Show queued subscription access and fix forecast time plotting

This commit is contained in:
2569718930@qq.com
2026-04-13 16:35:22 +08:00
parent 57acbc41c9
commit 41cfb65b92
8 changed files with 374 additions and 61 deletions
+2
View File
@@ -516,6 +516,8 @@ export interface ProAccessState {
subscriptionActive: boolean;
subscriptionPlanCode: string | null;
subscriptionExpiresAt: string | null;
subscriptionTotalExpiresAt: string | null;
subscriptionQueuedDays: number;
points: number;
error: string | null;
}
+121 -27
View File
@@ -248,6 +248,70 @@ function hmToMinutes(value?: string | null) {
return hour * 60 + minute;
}
function findNearestTimeIndex(
times: string[],
targetTime?: string | null,
) {
const targetMinutes = hmToMinutes(targetTime);
if (targetMinutes == null || !times.length) return -1;
let nearestIndex = -1;
let nearestDelta = Number.POSITIVE_INFINITY;
times.forEach((time, index) => {
const minute = hmToMinutes(time);
if (minute == null) return;
const delta = Math.abs(minute - targetMinutes);
if (delta < nearestDelta) {
nearestDelta = delta;
nearestIndex = index;
}
});
return nearestIndex;
}
function buildTemperatureTickLabels(times: string[]) {
const lastIndex = Math.max(0, times.length - 1);
return times.map((time, index) => {
if (index === 0 || index === lastIndex) return time;
const minute = hmToMinutes(time);
if (minute == null) return "";
const hour = Math.floor(minute / 60);
const minutePart = minute % 60;
if (minutePart !== 0) return "";
return hour % 2 === 0 ? time : "";
});
}
function buildSeriesPoints(
times: string[],
values: Array<number | null | undefined>,
) {
return times
.map((time, index) => {
const x = hmToMinutes(time);
const y = values[index];
return x != null && y != null && Number.isFinite(Number(y))
? { index, labelTime: time, x, y: Number(y) }
: null;
})
.filter(
(point): point is { index: number; labelTime: string; x: number; y: number } =>
point != null,
);
}
function buildObservationPoints(items: Array<{ time?: string; temp?: number | null }>) {
return items
.map((item) => {
const labelTime = normalizeHm(String(item.time || ""));
const x = hmToMinutes(labelTime);
const y = item.temp;
return x != null && y != null && Number.isFinite(Number(y))
? { labelTime: labelTime || "", x, y: Number(y) }
: null;
})
.filter((point): point is { labelTime: string; x: number; y: number } => point != null);
}
function interpolateSeriesAtMinutes(
times: string[],
values: Array<number | null | undefined>,
@@ -516,10 +580,7 @@ export function getTemperatureChartData(
if (!times.length) return null;
const currentHour = detail.local_time
? `${detail.local_time.split(":")[0]}:00`
: null;
const currentIndex = currentHour ? times.indexOf(currentHour) : -1;
const currentIndex = findNearestTimeIndex(times, detail.local_time);
const omMax = detail.forecast?.today_high;
const debMax = detail.deb?.prediction;
const offset =
@@ -582,11 +643,7 @@ export function getTemperatureChartData(
const metarPoints = new Array(times.length).fill(null);
observationSource.forEach((item) => {
const parts = String(item.time || "").split(":");
let hour = Number.parseInt(parts[0], 10);
if (Number.isNaN(hour)) return;
const key = `${String(hour).padStart(2, "0")}:00`;
const index = times.indexOf(key);
const index = findNearestTimeIndex(times, String(item.time || ""));
const temp = item.temp ?? null;
if (index >= 0 && temp != null) {
const existing = metarPoints[index];
@@ -598,11 +655,7 @@ export function getTemperatureChartData(
});
const airportMetarPoints = new Array(times.length).fill(null);
airportMetarSource.forEach((item) => {
const parts = String(item.time || "").split(":");
const hour = Number.parseInt(parts[0], 10);
if (Number.isNaN(hour)) return;
const key = `${String(hour).padStart(2, "0")}:00`;
const index = times.indexOf(key);
const index = findNearestTimeIndex(times, String(item.time || ""));
const temp = item.temp ?? null;
if (index >= 0 && temp != null) {
const existing = airportMetarPoints[index];
@@ -617,24 +670,16 @@ export function getTemperatureChartData(
detail.mgm?.temp != null &&
detail.mgm?.time
) {
const match = detail.mgm.time.match(/T?(\d{2}):(\d{2})/);
if (match) {
let hour = Number.parseInt(match[1], 10);
const key = `${String(hour).padStart(2, "0")}:00`;
const index = times.indexOf(key);
if (index >= 0) {
mgmPoints[index] = detail.mgm.temp;
}
const index = findNearestTimeIndex(times, detail.mgm.time);
if (index >= 0) {
mgmPoints[index] = detail.mgm.temp;
}
}
const mgmHourlyPoints = new Array(times.length).fill(null);
let hasMgmHourly = false;
detail.mgm?.hourly?.forEach((item) => {
const match = String(item.time || "").match(/T?(\d{2}):(\d{2})/);
if (!match) return;
const key = `${match[1]}:00`;
const index = times.indexOf(key);
const index = findNearestTimeIndex(times, String(item.time || ""));
if (index >= 0) {
mgmHourlyPoints[index] = item.temp ?? null;
hasMgmHourly = true;
@@ -723,7 +768,7 @@ export function getTemperatureChartData(
const tafMarkers = tafMarkersRaw
.map((marker) => {
const labelTime = String(marker?.label_time || "").trim();
const index = times.indexOf(labelTime);
const index = findNearestTimeIndex(times, labelTime);
if (index >= 0) {
tafMarkerPoints[index] = tafMarkerValue;
}
@@ -890,20 +935,66 @@ export function getTemperatureChartData(
);
}
const debPastSeries = buildSeriesPoints(times, debPast);
const debFutureSeries = buildSeriesPoints(times, debFuture);
const tempsSeries = buildSeriesPoints(times, temps);
const mgmHourlySeries = buildSeriesPoints(times, mgmHourlyPoints);
const metarSeries = buildObservationPoints(observationSource);
const airportMetarSeries = buildObservationPoints(airportMetarSource);
const mgmSeries =
!suppressAnkaraMgmObservation && detail.mgm?.temp != null && detail.mgm?.time
? buildObservationPoints([{ time: detail.mgm.time, temp: detail.mgm.temp }])
: [];
const tafCurrentMarkerSeries = tafMarkers
.filter((marker) => marker.isCurrent)
.map((marker) => ({
marker,
x: hmToMinutes(marker.labelTime) ?? 0,
y: tafMarkerValue,
}))
.filter((point) => point.x > 0);
const tafPeakWindowMarkerSeries = tafMarkers
.filter((marker) => marker.isPeakWindow && !marker.isCurrent)
.map((marker) => ({
marker,
x: hmToMinutes(marker.labelTime) ?? 0,
y: tafMarkerValue - 0.15,
}))
.filter((point) => point.x > 0);
const tafMarkerSeries = tafMarkers
.map((marker) => ({
marker,
x: hmToMinutes(marker.labelTime) ?? 0,
y: tafMarkerValue,
}))
.filter((point) => point.x > 0);
const xMin = times.length ? hmToMinutes(times[0]) ?? 0 : 0;
const xMax = times.length ? hmToMinutes(times[times.length - 1]) ?? 24 * 60 : 24 * 60;
return {
datasets: {
airportMetarPoints,
airportMetarSeries,
debFuture,
debFutureSeries,
debPast,
debPastSeries,
hasMgmHourly,
metarPoints,
metarSeries,
mgmHourlyPoints,
mgmHourlySeries,
mgmPoints,
mgmSeries,
offset,
tafCurrentMarkerPoints,
tafCurrentMarkerSeries,
tafMarkerPoints,
tafMarkerSeries,
tafPeakWindowMarkerPoints,
tafPeakWindowMarkerSeries,
temps,
tempsSeries,
},
observationLabel:
observationCode === "noaa" &&
@@ -918,7 +1009,10 @@ export function getTemperatureChartData(
max,
min,
tafMarkers,
tickLabels: buildTemperatureTickLabels(times),
times,
xMax,
xMin,
};
}