1(function () { 2 "use strict"; 3 4 const CARDINALS = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"]; 5 const SEGMENTS = { 6 hover: { color: "#2f80ed", label: "Stazionario" }, 7 straightFlight: { color: "#27ae60", label: "Volo rettilineo" }, 8 turn: { color: "#f2994a", label: "Virata" }, 9 takeoff: { color: "#9b51e0", label: "Decollo" }, 10 landing: { color: "#eb5757", label: "Atterraggio" }, 11 unknown: { color: "#7f8fa6", label: "Non classificato" } 12 }; 13 14 function toNumber(value) { 15 if (value === null || value === undefined || value === "") return null; 16 const number = Number(value); 17 return Number.isFinite(number) ? number : null; 18 } 19 20 function normalizeDirection(value) { 21 const direction = toNumber(value); 22 if (direction === null) return null; 23 return ((direction % 360) + 360) % 360; 24 } 25 26 function getDirectionToDeg(directionFromDeg) { 27 const direction = normalizeDirection(directionFromDeg); 28 return direction === null ? null : normalizeDirection(direction + 180); 29 } 30 31 function getCardinal(value) { 32 const direction = normalizeDirection(value); 33 if (direction === null) return null; 34 return CARDINALS[Math.round(direction / 22.5) % 16]; 35 } 36 37 function getCircularDistanceDeg(left, right) { 38 const start = normalizeDirection(left); 39 const end = normalizeDirection(right); 40 if (start === null || end === null) return null; 41 return Math.abs(((((end - start) + 540) % 360) - 180)); 42 } 43 44 function clamp(value, min, max) { 45 const number = toNumber(value); 46 if (number === null) return null; 47 return Math.max(min, Math.min(max, number)); 48 } 49 50 function getAgreementPct(directionDifferenceDeg, speedDifferenceKmh) { 51 const directionDifference = toNumber(directionDifferenceDeg); 52 const speedDifference = toNumber(speedDifferenceKmh); 53 if (directionDifference === null || speedDifference === null) return null; 54 const directionScore = clamp(1 - (directionDifference / 90), 0, 1); 55 const speedScore = clamp(1 - (speedDifference / 20), 0, 1); 56 if (directionScore === null || speedScore === null) return null; 57 return Math.round(((directionScore * 0.62) + (speedScore * 0.38)) * 100); 58 } 59 60 function normalizeEstimate(wind = {}) { 61 if (!wind || typeof wind !== "object") return null; 62 const speed = toNumber(wind?.speed ?? wind?.speedKmh); 63 const rawDirectionToDeg = normalizeDirection(wind?.directionToDeg); 64 const finalFrom = normalizeDirection(wind?.directionFromDeg) ?? 65 (rawDirectionToDeg === null ? null : normalizeDirection(rawDirectionToDeg + 180)); 66 const directionToDeg = finalFrom === null ? rawDirectionToDeg : getDirectionToDeg(finalFrom); 67 if (speed === null || finalFrom === null || directionToDeg === null) return null; 68 return { 69 cardinalFrom: wind.cardinalFrom || getCardinal(finalFrom), 70 cardinalTo: wind.cardinalTo || getCardinal(directionToDeg), 71 confidence: toNumber(wind.confidence), 72 debug: wind.debug || null, 73 directionFromDeg: finalFrom, 74 directionToDeg, 75 speedKmh: speed 76 }; 77 } 78 79 function normalizeHistoricalWind(wind = {}) { 80 return normalizeEstimate({ 81 cardinalFrom: wind.cardinalFrom10m, 82 cardinalTo: wind.cardinalTo10m, 83 directionFromDeg: wind.directionFromDeg10m, 84 directionToDeg: wind.directionToDeg10m, 85 speed: wind.speedKmh10m 86 }); 87 } 88 89 function getSegmentType(point = {}) { 90 return point.wind?.instant?.debug?.segmentType || 91 point.wind?.debug?.segmentType || 92 "unknown"; 93 } 94 95 function getSegmentInfo(segmentType) { 96 return SEGMENTS[segmentType] || SEGMENTS.unknown; 97 } 98 99 function getWindowDebug(windowed) { 100 return windowed?.debug || {}; 101 } 102 103 function getMedian(values = []) { 104 const numbers = values.map(toNumber).filter(value => value !== null).sort((left, right) => left - right); 105 if (!numbers.length) return null; 106 const middle = Math.floor(numbers.length / 2); 107 return numbers.length % 2 ? numbers[middle] : (numbers[middle - 1] + numbers[middle]) / 2; 108 } 109
110 function getLocalRows(rows = [], index = 0, radius = 2) { 111 return rows.slice(Math.max(0, index - radius), Math.min(rows.length, index + radius + 1)); 112 } 113 114 function markOutliers(rows = []) { 115 rows.forEach((row, index) => { 116 const reasons = []; 117 const localRows = getLocalRows(rows, index).filter(candidate => candidate !== row); 118 const localSpeedMedian = getMedian(localRows.map(candidate => candidate.active?.speedKmh)); 119 const speed = toNumber(row.active?.speedKmh); 120 121 if (speed !== null && localSpeedMedian !== null && Math.abs(speed - localSpeedMedian) >= 18) { 122 reasons.push("velocita incompatibile con i campioni vicini"); 123 } 124 125 const previous = rows[index - 1]; 126 const next = rows[index + 1]; 127 const direction = toNumber(row.active?.directionFromDeg); 128 const previousJump = previous && direction !== null ? getCircularDistanceDeg(previous.active?.directionFromDeg, direction) : null; 129 const nextJump = next && direction !== null ? getCircularDistanceDeg(direction, next.active?.directionFromDeg) : null; 130 if ((previousJump !== null && previousJump >= 95) || (nextJump !== null && nextJump >= 95)) { 131 reasons.push("salto improvviso della direzione"); 132 } 133 134 if (row.confidencePct !== null && row.confidencePct < 28) { 135 reasons.push("confidence molto bassa"); 136 } 137 138 if (row.directionalDispersionDeg !== null && row.directionalDispersionDeg >= 75) { 139 reasons.push("dispersione elevata"); 140 } 141 142 row.isOutlier = reasons.length > 0; 143 row.outlierReasons = reasons; 144 row.outlierSeverity = reasons.length >= 3 ? "high" : reasons.length >= 2 ? "medium" : reasons.length ? "low" : "none"; 145 }); 146 return rows; 147 } 148 149 function create(options = {}) { 150 const weatherService = options.weatherService || window.DSC_FlightWeatherService?.create?.(); 151 152 async function load(points = []) { 153 let dataset = null; 154 try { 155 dataset = await weatherService?.loadFlightWeather?.(points); 156 } catch (error) { 157 dataset = { error, status: "failed" }; 158 } 159 return build(points, dataset); 160 } 161 162 function build(points = [], dataset = null) { 163 const safePoints = Array.isArray(points) ? points : []; 164 const durationSec = Math.max(...safePoints.map(point => toNumber(point.elapsedSec) || 0), 1); 165 const rows = safePoints.map((point, index) => { 166 const date = weatherService?.getReplayDate?.(point, safePoints); 167 const weather = date && dataset ? weatherService?.getWeatherAt?.(dataset, date) : null; 168 const historical = weather?.wind ? normalizeHistoricalWind(weather.wind) : null; 169 const instant = normalizeEstimate(point.wind?.instant || point.wind); 170 const windowed = normalizeEstimate(point.wind?.windowed); 171 const solver = normalizeEstimate(point.wind?.solver); 172 const active = solver || windowed || instant; 173 const segmentType = getSegmentType(point); 174 const solverDebug = solver?.debug || {}; 175 const windowDebug = getWindowDebug(windowed); 176 const solverConfidence = clamp(solver?.confidence, 0, 1); 177 const solverConfidencePct = solverConfidence === null 178 ? null 179 : Math.round(solverConfidence * 100); 180 const solverResidualError = toNumber(solverDebug.residualError); 181 const solverFit = solverResidualError === null 182 ? null 183 : clamp(1 - (solverResidualError / 1.8), 0, 1); 184 const solverFitPct = solverResidualError === null 185 ? null 186 : Math.round(solverFit * 100); 187 const speedDifferenceKmh = historical && active 188 ? Math.abs((historical.speedKmh || 0) - (active.speedKmh || 0)) 189 : null; 190 const solverHistoricalDirectionDiffDeg = historical && solver 191 ? getCircularDistanceDeg(historical.directionFromDeg, solver.directionFromDeg) 192 : null; 193 const solverHistoricalSpeedDiffKmh = historical && solver 194 ? Math.abs((historical.speedKmh || 0) - (solver.speedKmh || 0)) 195 : null; 196 const solverHistoricalAgreementPct = getAgreementPct( 197 solverHistoricalDirectionDiffDeg, 198 solverHistoricalSpeedDiffKmh 199 ); 200 201 return { 202 active, 203 altitudeM: toNumber(point.altitude), 204 confidencePct: active?.confidence === null || active?.confidence === undefined ? null : Math.round(Math.max(0, Math.min(1, active.confidence)) * 100), 205 date, 206 directionalDispersionDeg: toNumber(windowDebug.directionalDispersionDeg), 207 durationSec, 208 elapsedSec: toNumber(point.elapsedSec) || 0, 209 historical, 210 index, 211 instant, 212 minimumDirectionDifferenceDeg: historical && active ? getCircularDistanceDeg(historical.directionFromDeg, active.directionFromDeg) : null, 213 point, 214 sampleCount: safePoints.length, 215 samplesUsed: toNumber(windowDebug.window?.samplesUsed), 216 segmentColor: getSegmentInfo(segmentType).color, 217 segmentLabel: getSegmentInfo(segmentType).label, 218 segmentType, 219 solver, 220 solverConfidencePct, 221 solverCost: toNumber(solverDebug.solverCost), 222 solverFitPct, 223 solverHistoricalAgreementPct, 224 solverHistoricalDirectionDiffDeg, 225 solverHistoricalSpeedDiffKmh, 226 solverResidualError, 227 solverStateId: solverDebug.stateId || null, 228 speedDifferenceKmh, 229 windowDurationSec: toNumber(windowDebug.window?.durationSec), 230 windowed 231 }; 232 }); 233 markOutliers(rows); 234 235 return { 236 dataset, 237 durationSec, 238 rows, 239 segments: SEGMENTS 240 }; 241 } 242 243 return { 244 build, 245 getCardinal, 246 getCircularDistanceDeg, 247 load, 248 normalizeDirection, 249 normalizeEstimate 250 }; 251 } 252 253 window.DSC_WindDiagnosticsDataAdapter = { 254 create, 255 segments: SEGMENTS 256 }; 257})();
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.