import { fetchRemoteJson, isRemoteImageUrl, resolveResourceUrl, } from "./wrwRemoteData"; import { formatWakeMetric, formatWakePercent } from "./wrwTableUtils"; /** 接口 *Path 字段 → 前端图表数据 key */ const CHART_PATH_MAP = [ { chartKey: "windSpeedDistribution", pathKey: "windSpeedDistributionChart" }, { chartKey: "wakeRose", pathKey: "wakeRoseDiagramPath" }, { chartKey: "wakeDistribute", pathKey: "wakeDistributePath" }, { chartKey: "wakeLossConfident", pathKey: "wakeLossConfidentPath" }, { chartKey: "velocityDeficitBar", pathKey: "velocityDeficitPath" }, ]; const CHART_PAYLOAD_ALIASES = { windSpeedDistribution: ["windSpeedDistribution", "windSpeedDistributionChart"], wakeRose: ["wakeRose", "windRose"], wakeDistribute: ["wakeDistribute", "layoutLossScatter"], wakeLossConfident: ["wakeLossConfident", "lossConfidenceScatter"], velocityDeficitBar: ["velocityDeficitBar", "velocityDeficit", "lossDelBar"], }; function emptyPageData() { return { charts: {}, tableData: [], turbineNames: [], wakeDescription: "", meta: {}, }; } function unwrapWindRosePayload(payload) { if (!payload || typeof payload !== "object") return null; let current = payload; for (let depth = 0; depth < 4; depth += 1) { if (current.windRose && typeof current.windRose === "object") { current = current.windRose; continue; } if (current.wakeRose && typeof current.wakeRose === "object") { current = current.wakeRose; continue; } if (current.data && typeof current.data === "object") { const nested = current.data.windRose || current.data.wakeRose || current.data; if (nested && nested !== current) { current = nested; continue; } } break; } return current; } function normalizeChartPayload(chartKey, payload) { if (!payload || typeof payload !== "object") return null; if (chartKey === "wakeRose") { return unwrapWindRosePayload(payload); } const aliases = CHART_PAYLOAD_ALIASES[chartKey] || [chartKey]; for (let i = 0; i < aliases.length; i += 1) { if (payload[aliases[i]]) { return payload[aliases[i]]; } } if (Array.isArray(payload.turbines) || Array.isArray(payload.points)) { return payload; } if (Array.isArray(payload.items)) { return payload; } if (Array.isArray(payload.bands)) { return payload; } return payload; } function hasChartPayload(chartKey, data) { if (!data || typeof data !== "object") return false; if (data.__imageUrl) return true; if (Array.isArray(data.points) && data.points.length) return true; if (Array.isArray(data.items) && data.items.length) return true; if (Array.isArray(data.turbines) && data.turbines.length) return true; if (chartKey === "wakeRose" && !isEmptyWakeRose(data)) return true; return false; } function normalizeChartsObject(rawCharts = {}, vo = {}) { const charts = { ...(rawCharts || {}) }; Object.keys(CHART_PAYLOAD_ALIASES).forEach((chartKey) => { if (hasChartPayload(chartKey, charts[chartKey])) return; const aliases = CHART_PAYLOAD_ALIASES[chartKey] || [chartKey]; for (let i = 0; i < aliases.length; i += 1) { const alias = aliases[i]; const sources = [charts[alias], vo[alias]]; for (let j = 0; j < sources.length; j += 1) { const source = sources[j]; if (!source) continue; const normalized = normalizeChartPayload(chartKey, source); if (hasChartPayload(chartKey, normalized)) { charts[chartKey] = normalized; return; } } } }); return charts; } async function loadChartFromPath(chartKey, path) { if (!path) return null; const resolved = resolveResourceUrl(path); if (isRemoteImageUrl(resolved)) { return chartKey === "wakeDistribute" ? { __imageUrl: resolved } : null; } try { const json = await fetchRemoteJson(resolved); return normalizeChartPayload(chartKey, json); } catch (error) { console.warn(`加载尾流图表资源失败 [${chartKey}]:`, path, error); return null; } } async function loadDescriptionText(path) { if (!path) return ""; const resolved = resolveResourceUrl(path); if (isRemoteImageUrl(resolved)) return ""; try { const response = await fetch(resolved, { method: "GET", credentials: "include" }); if (!response.ok) return ""; return response.text(); } catch (error) { console.warn("加载尾流描述文本失败:", path, error); return ""; } } function formatConfidence(confidentLevel) { return formatWakePercent(confidentLevel); } function formatNumber(value) { return formatWakeMetric(value); } export function mapWakeTurbineList(list) { if (!Array.isArray(list)) return []; return list.map((item) => ({ name: item.engineId || item.engineName || item.name || "—", engineId: item.engineId || "", affected: item.isWake === 1 || item.isWake === true ? "yes" : "no", turbulence: formatNumber(item.turbulenceIntensity), loss: formatNumber(item.velocityDeficit), confidence: formatConfidence(item.confidentLevel), image: resolveResourceUrl(item.wakeTurbinePath || item.image || ""), })); } function extractTurbineNames(charts, turbineList) { const names = []; const pushName = (id) => { if (id && !names.includes(id)) names.push(id); }; charts.windSpeedDistribution?.turbines?.forEach((item) => { pushName(item.displayName || item.engineId); }); charts.wakeRose?.turbines?.forEach((item) => { pushName(item.engineId); }); if (Array.isArray(turbineList)) { turbineList.forEach((item) => pushName(item.engineId)); } return names; } function formatDataDate(dataDate) { if (!dataDate) return ""; return String(dataDate).slice(0, 10); } function isEmptyWakeRose(rose) { return !rose || !Array.isArray(rose.turbines) || !rose.turbines.length; } function hasRoseBands(turbine) { return ( turbine && Array.isArray(turbine.bands) && turbine.bands.some((band) => Array.isArray(band.values) && band.values.length) ); } function normalizeTurbineRoseData(engineId, payload) { const normalized = unwrapWindRosePayload(payload); if (!normalized || typeof normalized !== "object") return null; const meta = { speedBands: normalized.speedBands, directions: normalized.directions, }; if (Array.isArray(normalized.turbines) && normalized.turbines.length) { const matched = normalized.turbines.find((item) => item.engineId === engineId) || normalized.turbines[0]; if (!hasRoseBands(matched)) return null; return { engineId: matched.engineId || engineId, bands: matched.bands, speedBands: meta.speedBands, directions: meta.directions, }; } if (hasRoseBands(normalized)) { return { engineId: normalized.engineId || engineId, bands: normalized.bands, speedBands: meta.speedBands, directions: meta.directions, }; } return null; } function getTurbineRosePath(item) { const path = item?.wakeRoseDiagramPath || item?.windRoseDiagramPath || item?.wakeRosePath || ""; return typeof path === "string" ? path.trim() : ""; } function getInlineTurbineRoseSource(item) { if (!item || typeof item !== "object") return null; const pathField = item.wakeRoseDiagramPath || item.windRoseDiagramPath || item.wakeRosePath; if (pathField && typeof pathField === "object") { return normalizeTurbineRoseData(item.engineId, pathField); } if (item.windRose || item.wakeRose) { return normalizeTurbineRoseData( item.engineId, item.windRose || item.wakeRose, ); } return null; } async function loadTurbineRoseFromItem(item) { const engineId = item?.engineId || ""; const inlineRose = getInlineTurbineRoseSource(item); if (inlineRose) return inlineRose; const rosePath = getTurbineRosePath(item); if (!rosePath || isRemoteImageUrl(resolveResourceUrl(rosePath))) { return null; } try { const json = await fetchRemoteJson(rosePath); return normalizeTurbineRoseData(engineId, json); } catch (error) { console.warn(`加载机组玫瑰图失败 [${engineId}]:`, rosePath, error); return null; } } async function loadTurbineRoseDiagrams(turbineList) { if (!Array.isArray(turbineList) || !turbineList.length) { return []; } const results = await Promise.all( turbineList.map((item) => loadTurbineRoseFromItem(item)), ); return results.filter(Boolean); } function mergeWakeRoseData(farmRose, turbineList, perTurbineRoses) { const roseMap = new Map(); (farmRose?.turbines || []).forEach((item) => { if (item?.engineId) roseMap.set(item.engineId, item); }); perTurbineRoses.forEach((item) => { if (item?.engineId) roseMap.set(item.engineId, item); }); if (!roseMap.size) return farmRose || null; const turbines = []; if (Array.isArray(turbineList)) { turbineList.forEach((item) => { const id = item?.engineId; if (id && roseMap.has(id)) { turbines.push(roseMap.get(id)); roseMap.delete(id); } }); } roseMap.forEach((item) => turbines.push(item)); const metaSource = perTurbineRoses.find((item) => item.speedBands || item.directions) || farmRose || {}; return { speedBands: metaSource.speedBands || farmRose?.speedBands, directions: metaSource.directions || farmRose?.directions, turbines: turbines.map(({ speedBands, directions, ...item }) => item), }; } export async function loadWakePageData(vo) { if (!vo || typeof vo !== "object") { return emptyPageData(); } const turbineList = Array.isArray(vo.getWakeTurbineList) ? vo.getWakeTurbineList : []; let charts = normalizeChartsObject( vo.charts && typeof vo.charts === "object" ? vo.charts : {}, vo, ); const [wakeDescription, perTurbineRoses] = await Promise.all([ loadDescriptionText(vo.wakeDiscriptionPath), loadTurbineRoseDiagrams(turbineList), ...CHART_PATH_MAP.filter(({ chartKey }) => chartKey !== "wakeRose").map( async ({ chartKey, pathKey }) => { if (charts[chartKey]) return; const loaded = await loadChartFromPath(chartKey, vo[pathKey]); if (loaded) { charts[chartKey] = loaded; } }, ), ]); const mergedWakeRose = mergeWakeRoseData(null, turbineList, perTurbineRoses); if (mergedWakeRose?.turbines?.length) { charts.wakeRose = mergedWakeRose; } else if (isEmptyWakeRose(charts.wakeRose)) { const farmRose = await loadChartFromPath("wakeRose", vo.wakeRoseDiagramPath); if (farmRose && !isEmptyWakeRose(farmRose)) { charts.wakeRose = farmRose; } } const tableData = mapWakeTurbineList(turbineList); const turbineNames = extractTurbineNames(charts, turbineList); if ( !hasChartPayload("velocityDeficitBar", charts.velocityDeficitBar) && turbineList.length ) { charts.velocityDeficitBar = { items: turbineList.map((item) => ({ engineId: item.engineId, lossPercent: item.lossPercent ?? item.velocityDeficit, turbulenceIntensity: item.turbulenceIntensity, velocityDeficit: item.velocityDeficit, })), }; } if ( !hasChartPayload("wakeLossConfident", charts.wakeLossConfident) && turbineList.length ) { charts.wakeLossConfident = { points: turbineList.map((item) => ({ engineId: item.engineId, velocityDeficit: item.velocityDeficit, confidentLevel: item.confidentLevel, })), }; } return { charts, tableData, turbineNames, wakeDescription, turbineList, meta: { fieldId: vo.fieldId || "", dataDate: formatDataDate(vo.dataDate), }, }; }