From d0fe2b6febee0ca2981dd80ab93d2bf90be1cf04 Mon Sep 17 00:00:00 2001 From: John Wiseman Date: Sat, 8 Aug 2020 22:35:49 -0700 Subject: [PATCH] Added KML export. Merged Mon 20 Sep 08:43:25 CEST 2021 --- html/index.html | 2 + html/script.js | 166 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+) diff --git a/html/index.html b/html/index.html index 53dafed..4e82e05 100644 --- a/html/index.html +++ b/html/index.html @@ -669,6 +669,8 @@ + + diff --git a/html/script.js b/html/script.js index 29a6ca7..e958cab 100644 --- a/html/script.js +++ b/html/script.js @@ -6814,6 +6814,172 @@ jQuery.fn.updateText = function (text) { this.text() !== String(text) && this.text(text); } +function zeroPad(num, size) { + var s = num + ""; + while (s.length < size) s = "0" + s; + return s; +} + +// Converts "hiccup"-style structures (https://github.com/weavejester/hiccup) +// to XML. +function hiccup(node) { + if (Array.isArray(node)) { + const [tag, attribs, ...children] = node; + let attribStrings = []; + for (const prop in attribs) { + if (!attribs.hasOwnProperty(prop) || attribs[prop] === undefined) { + continue; + } + attribStrings.push(`${prop}="${attribs[prop]}"`); + } + let xml = `<${tag} ${attribStrings.join(' ')}>`; + for (const child of children) { + xml += hiccup(child); + } + xml += `\n`; + return xml; + } else { + return '' + node; + } +} + +// Prompts a browser to download a data: URL. +function download(name, contentType, data) { + var link = document.createElement("a"); + link.download = name; + link.href = 'data:' + contentType + ',' + encodeURIComponent(data); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +} + +function baseExportFilenameForAircrafts(aircrafts) { + return aircrafts.map((a) => (a.registration || a.icao).toUpperCase()).join('-'); +} + +// Returns an array of {pos, alt, ts} for an aircraft. +function coordsForExport(plane) { + var coords = []; + var numSegs = plane.track_linesegs.length; + for (let i = 0; i < numSegs; i++) { + const proj = plane.track_linesegs[i].fixed.getCoordinates()[1]; + if (proj) { + const pos = ol.proj.toLonLat(proj); + const alt = plane.track_linesegs[i].alt_real; + const ts = new Date(plane.track_linesegs[i].ts * 1000.0); + if (!alt) { + throw new Error(`No altitude: ${i} ${pos} ${ts}`); + } + // Attempt to correct altitude. This could be better? + // + // 950 feet is the correction factor for an altimeter of 30.15. + // 25 feet is the quantum of transponder reporting. 0 altitude + // could be reported as -25, so just add 25. + coords.push({ pos, alt: (alt + 950 + 25) * 0.3048, ts }); + } else { + console.log(`Skipping ${i}`); + } + } + return coords; +} + +// We use this to give each aircraft a different color track in a +// multi-select export scenario. From colorbrewer, but I moved the red +// to be first. +const EXPORT_RGB_COLORS = [ + 'e31a1c', + 'a6cee3', + '1f78b4', + 'b2df8a', + '33a02c', + 'fb9a99', + 'fdbf6f', + 'ff7f00', + 'cab2d6', + '6a3d9a', + 'ffff99', + 'b15928' +]; + +// Converts "rrggbb" colors to KML format, "aabbggrr". +function RGBColorToKMLColor(c) { + return 'ff' + c.substring(4, 6) + c.substring(2, 4) + c.substring(0, 2); +} + +// Returns an array of selected planes, ordered by registration-or-ICAO. +function selectedPlanes() { + const planes = []; + for (let key in Planes) { + if (Planes[key].selected) { + planes.push(Planes[key]); + } + } + planes.sort((a, b) => { + const keyA = (a.registration || a.icao).toUpperCase(); + const keyB = (b.registration || b.icao).toUpperCase(); + if (keyA < keyB) return -1; + if (keyA > keyB) return 1; + return 0; + }); + return planes; +} + +// Exports currently selected aircraft as KML. +function exportKML() { + const planes = selectedPlanes(); + const folders = []; + for (let i = 0; i < planes.length; i++) { + const plane = planes[i]; + const coords = coordsForExport(plane); + const whenObjs = coords.map((c) => { + const date = `${c.ts.getUTCFullYear()}-${zeroPad(c.ts.getUTCMonth() + 1, 2)}-${zeroPad(c.ts.getUTCDate(), 2)}`; + const time = `T${zeroPad(c.ts.getUTCHours(), 2)}:${zeroPad(c.ts.getUTCMinutes(), 2)}:${zeroPad(c.ts.getUTCSeconds(), 2)}.${zeroPad(c.ts.getUTCMilliseconds(), 3)}Z`; + return ["when", {}, date + time]; + }); + const coordObjs = coords.map((c) => ["gx:coord", {}, `${c.pos[0]} ${c.pos[1]} ${c.alt}`]); + var folder = ["Folder", {}, + ["name", {}, `${(plane.registration || plane.icao).toUpperCase()} track`], + ["Placemark", {}, + ["name", {}, (plane.registration || plane.icao).toUpperCase()], + ["Style", {}, + ["LineStyle", {}, + ["color", {}, RGBColorToKMLColor(EXPORT_RGB_COLORS[i % EXPORT_RGB_COLORS.length])], + ["width", {}, 4] + ], + ["IconStyle", {}, + ["Icon", {}, + ["href", {}, "http://maps.google.com/mapfiles/kml/shapes/airports.png"] + ] + ] + ], + ["gx:Track", {}, + ["altitudeMode", {}, "absolute"], + ["extrude", {}, "1"], + ["tessellate", {}, "1"], + ...whenObjs, + ...coordObjs + ] + ] + ]; + folders.push(folder); + } + const filename = baseExportFilenameForAircrafts(planes); + const prologue = '\n'; + const xmlObj = ["kml", { + "xmlns": "http://www.opengis.net/kml/2.2", + "xmlns:gx": "http://www.google.com/kml/ext/2.2" + }, + ["Folder", {}, + ...folders + ] + ]; + const xml = prologue + hiccup(xmlObj); + download( + filename + '-track.kml', + 'application/vnd.google-earth.kml+xml', + xml); +} + parseURLIcaos(); initialize();