File size: 6,331 Bytes
9d2d895 | 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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | import type {
GetOceanIceDataResponse,
IceTrendPoint as ProtoIceTrendPoint,
OceanIceData as ProtoOceanIceData,
} from '@/generated/client/worldmonitor/climate/v1/service_client';
export interface OceanIceTrendPoint {
month: string;
extentMkm2: number;
anomalyMkm2: number;
}
export interface OceanIceIndicators {
arcticExtentMkm2?: number;
arcticExtentAnomalyMkm2?: number;
arcticTrend?: string;
seaLevelMmAbove1993?: number;
seaLevelAnnualRiseMm?: number;
ohc0700mZj?: number;
sstAnomalyC?: number;
measuredAt?: Date;
iceTrend12m: OceanIceTrendPoint[];
}
interface OceanIceSeedSnakePoint {
month?: string;
extent_mkm2?: number;
anomaly_mkm2?: number;
}
export interface OceanIceSeedSnakeShape {
arctic_extent_mkm2?: number;
arctic_extent_anomaly_mkm2?: number;
arctic_trend?: string;
sea_level_mm_above_1993?: number;
sea_level_annual_rise_mm?: number;
ohc_0_700m_zj?: number;
sst_anomaly_c?: number;
measured_at?: number;
ice_trend_12m?: OceanIceSeedSnakePoint[];
}
export function toDisplayOceanIceData(proto: ProtoOceanIceData): OceanIceIndicators {
const measuredAt = Number(proto.measuredAt);
return {
...(hasFinite(proto, 'arcticExtentMkm2') ? { arcticExtentMkm2: proto.arcticExtentMkm2 } : {}),
...(hasFinite(proto, 'arcticExtentAnomalyMkm2') ? { arcticExtentAnomalyMkm2: proto.arcticExtentAnomalyMkm2 } : {}),
...(typeof proto.arcticTrend === 'string' && proto.arcticTrend ? { arcticTrend: proto.arcticTrend } : {}),
...(hasFinite(proto, 'seaLevelMmAbove1993') ? { seaLevelMmAbove1993: proto.seaLevelMmAbove1993 } : {}),
...(hasFinite(proto, 'seaLevelAnnualRiseMm') ? { seaLevelAnnualRiseMm: proto.seaLevelAnnualRiseMm } : {}),
...(hasFinite(proto, 'ohc0700mZj') ? { ohc0700mZj: proto.ohc0700mZj } : {}),
...(hasFinite(proto, 'sstAnomalyC') ? { sstAnomalyC: proto.sstAnomalyC } : {}),
measuredAt: Number.isFinite(measuredAt) && measuredAt > 0 ? new Date(measuredAt) : undefined,
iceTrend12m: (proto.iceTrend12m ?? []).map(toDisplayOceanIcePoint),
};
}
export function normalizeHydratedOceanIce(
hydrated: GetOceanIceDataResponse | OceanIceSeedSnakeShape | undefined,
): ProtoOceanIceData | null {
if (!hydrated || typeof hydrated !== 'object') return null;
if ('data' in hydrated && hydrated.data) {
return normalizeProtoOceanIce(hydrated.data as ProtoOceanIceData);
}
const raw = hydrated as OceanIceSeedSnakeShape;
const points = Array.isArray(raw.ice_trend_12m)
? raw.ice_trend_12m
.filter((point): point is OceanIceSeedSnakePoint => point != null && typeof point.month === 'string'
&& Number.isFinite(Number(point.extent_mkm2))
&& Number.isFinite(Number(point.anomaly_mkm2)))
.map((point) => ({
month: point.month ?? '',
extentMkm2: Number(point.extent_mkm2),
anomalyMkm2: Number(point.anomaly_mkm2),
}))
: [];
const proto: Partial<ProtoOceanIceData> = {
iceTrend12m: points,
};
if (Number.isFinite(Number(raw.arctic_extent_mkm2))) proto.arcticExtentMkm2 = Number(raw.arctic_extent_mkm2);
if (Number.isFinite(Number(raw.arctic_extent_anomaly_mkm2))) proto.arcticExtentAnomalyMkm2 = Number(raw.arctic_extent_anomaly_mkm2);
if (typeof raw.arctic_trend === 'string' && raw.arctic_trend) proto.arcticTrend = raw.arctic_trend;
if (Number.isFinite(Number(raw.sea_level_mm_above_1993))) proto.seaLevelMmAbove1993 = Number(raw.sea_level_mm_above_1993);
if (Number.isFinite(Number(raw.sea_level_annual_rise_mm))) proto.seaLevelAnnualRiseMm = Number(raw.sea_level_annual_rise_mm);
if (Number.isFinite(Number(raw.ohc_0_700m_zj))) proto.ohc0700mZj = Number(raw.ohc_0_700m_zj);
if (Number.isFinite(Number(raw.sst_anomaly_c))) proto.sstAnomalyC = Number(raw.sst_anomaly_c);
if (Number.isFinite(Number(raw.measured_at))) proto.measuredAt = Math.round(Number(raw.measured_at));
return normalizeOceanIcePayload(proto);
}
function toDisplayOceanIcePoint(proto: ProtoIceTrendPoint): OceanIceTrendPoint {
return {
month: proto.month,
extentMkm2: proto.extentMkm2,
anomalyMkm2: proto.anomalyMkm2,
};
}
function normalizeProtoOceanIce(rawProto: ProtoOceanIceData | undefined): ProtoOceanIceData | null {
if (!rawProto || typeof rawProto !== 'object') return null;
const proto: Partial<ProtoOceanIceData> = {};
if (Number.isFinite(Number(rawProto.arcticExtentMkm2))) proto.arcticExtentMkm2 = Number(rawProto.arcticExtentMkm2);
if (Number.isFinite(Number(rawProto.arcticExtentAnomalyMkm2))) proto.arcticExtentAnomalyMkm2 = Number(rawProto.arcticExtentAnomalyMkm2);
if (typeof rawProto.arcticTrend === 'string' && rawProto.arcticTrend) proto.arcticTrend = rawProto.arcticTrend;
if (Number.isFinite(Number(rawProto.seaLevelMmAbove1993))) proto.seaLevelMmAbove1993 = Number(rawProto.seaLevelMmAbove1993);
if (Number.isFinite(Number(rawProto.seaLevelAnnualRiseMm))) proto.seaLevelAnnualRiseMm = Number(rawProto.seaLevelAnnualRiseMm);
if (Number.isFinite(Number(rawProto.ohc0700mZj))) proto.ohc0700mZj = Number(rawProto.ohc0700mZj);
if (Number.isFinite(Number(rawProto.sstAnomalyC))) proto.sstAnomalyC = Number(rawProto.sstAnomalyC);
if (Number.isFinite(Number(rawProto.measuredAt))) proto.measuredAt = Math.round(Number(rawProto.measuredAt));
const points = Array.isArray(rawProto.iceTrend12m)
? rawProto.iceTrend12m
.filter((point) => point != null && typeof point.month === 'string'
&& Number.isFinite(Number(point.extentMkm2))
&& Number.isFinite(Number(point.anomalyMkm2)))
.map((point) => ({
month: point.month ?? '',
extentMkm2: Number(point.extentMkm2),
anomalyMkm2: Number(point.anomalyMkm2),
}))
: [];
proto.iceTrend12m = points;
return normalizeOceanIcePayload(proto);
}
function normalizeOceanIcePayload(proto: Partial<ProtoOceanIceData>): ProtoOceanIceData | null {
const normalizedKeys = Object.keys(proto);
const onlyEmptyTrend = normalizedKeys.length === 1
&& Array.isArray(proto.iceTrend12m)
&& proto.iceTrend12m.length === 0;
return normalizedKeys.length === 0 || onlyEmptyTrend ? null : (proto as ProtoOceanIceData);
}
function hasFinite<T extends object>(obj: T, key: keyof T): boolean {
return Object.prototype.hasOwnProperty.call(obj, key) && Number.isFinite(Number(obj[key]));
}
|