diff --git a/FLARM.md b/FLARM.md new file mode 100644 index 000000000..c4d397f2f --- /dev/null +++ b/FLARM.md @@ -0,0 +1,208 @@ +# FLARM Display in tar1090 + +## Overview + +FLARM aircraft are displayed alongside ADS-B traffic in the OpenSky tar1090 +frontend. They are sourced from a separate JSON endpoint, normalised into +tar1090-compatible fields, and clearly distinguished from ADS-B aircraft by +source colour, filter category, marker icon, and a dedicated FLARM detail +section in the sidebar. + +--- + +## Configuration + +Two variables in `html/defaults.js` control the FLARM data source: + +| Variable | Default | Description | +|---|---|---| +| `flarm_server` | `"data/flarm_aircraft.json"` | URL or path to a JSON endpoint returning an array of Avro-deserialized FLARM aircraft objects (or `{"aircraft": [...]}`) | +| `flarm_refresh` | `5` | Polling interval in seconds | + +Set or override them in `html/config.js`. + +--- + +## Data Format + +The endpoint must return Avro-deserialised JSON conforming to the +`org.opensky.avro.v2.FLARMDecodedMessage` schema. A mock file is at +`html/data/flarm_aircraft.json`. The top-level object may be either a bare +array or `{"aircraft": [...]}`. + +### Field mapping (Avro -> tar1090) + +| Avro field | tar1090 field | Conversion | +|---|---|---| +| `latitude` / `longitude` | `lat` / `lon` | direct | +| `altitude` (m) | `alt_geom` (ft) | `* 3.28084` | +| `speed` (m/s) | `gs` (kt) | `* 1.94384` | +| `climb_rate` (m/s) | `geom_rate` (ft/min) | `* 196.85` | +| `track` | `track` | direct | +| `turn_rate` | `track_rate` | direct | +| `aircraft_type` | `category` | direct (enum string) | + +The remaining Avro fields are preserved in a `flarm` sub-object on the +PlaneObject and rendered in the FLARM detail section. + +--- + +## Synthetic Hex Keys + +All FLARM records receive a synthetic hex key prefixed with `~F`: + + ~F + +The `~` prefix prevents namespace collision with ADS-B records in +`g.planes` (keyed by `hex`). The real ICAO24 is preserved in +`plane.flarm.icao24`. + +When `MergeNonIcao` is enabled (default: `false`), aircraft with `~`-prefixed +hexes have the prefix stripped. Because the prefix is `~F`, stripping produces +`F`, which is not a valid ICAO hex and will not accidentally merge +with ADS-B records. FLARM aircraft will still appear, but their hex keys will +lack the `~` prefix. + +--- + +## Source Classification + +In `PlaneObject.updateData()` (`planeObject.js:1670`): + +``` +const flarm = !isArray && (data.source == "flarm" || data.type == "flarm" || data.flarm != null); +``` + +When the check passes, the aircraft is classified: + +- `dataSource = "flarm"` +- `source = "flarm"` +- `flarm = data.flarm` (preserved Avro fields) + +This happens after the ADS-B source-classification cascade, so `dataSource` +is always `"flarm"` for FLARM aircraft. + +--- + +## Filtering + +The source filter list in `html/script.js` (line 89) includes `'flarm'`: + +```js +let sources = ['adsb', ['uat', 'adsr'], 'mlat', 'tisb', 'modeS', 'other', 'adsc', 'ais', 'flarm']; +``` + +The filter button appears in the **Filters** section with label **FLARM** and +a light orange background (`#ffd27f` unselected, `#f69d00` selected). When +selected, only aircraft with `dataSource == "flarm"` are shown. + +--- + +## Marker + +In `getBaseMarker()` (`markers.js:1259`), FLARM aircraft use the glider SVG +shape: + +```js +if (addrtype == 'flarm') { + return ['glider', 1]; +} +``` + +The colour is determined by the marker colour system (defaulting to the +FLARM source colour in `tableColors`). + +--- + +## Sidebar Details + +When a FLARM aircraft is selected, a **FLARM** section appears in the sidebar +between the Accuracy section and Export KML. It contains a table of +FLARM-native fields rendered by `refreshSelectedFlarm()` (`script.js:3931`): + +| Label | Avro field | +|---|---| +| ICAO24 | `icao24` | +| Radio ID Type | `radio_identifier_type` | +| Aircraft Type | `aircraft_type` | +| Urgency | `urgency` | +| Stealth | `is_stealth` | +| No Track | `is_no_track` | +| Movement Mode | `movement_mode` | +| Turn Rate | `turn_rate` | +| Version | `version` | +| H-ACC | `acc_pos_hor` | +| V-ACC | `acc_pos_ver` | +| Vel. ACC | `acc_vel` | +| SIL | `sil` | +| SDA | `sda` | +| NIC | `nic` | + +The existing **Signal > Source** line shows "FLARM" via +`format_data_source()` (`formatter.js:328`). + +--- + +## Legend + +The map legend includes a **FLARM** entry with the same orange background, +added in `initLegend()` (`script.js:1865`). + +--- + +## Data Fetch Flow + +1. `setIntervalTimers()` (`script.js:2177`) starts a polling interval via + `timers.flarm = setInterval(processFlarmUpdate, flarm_refresh * 1000)` + and calls `processFlarmUpdate()` immediately. + +2. `processFlarmUpdate()` (`script.js:2204`) fetches the URL at + `flarm_server` via jQuery AJAX. + +3. On success, the callback iterates the aircraft array, calls + `normalizeFlarmAircraft()` on each element, and passes the result to + `processAircraft(ac, false, false)`. + +4. `normalizeFlarmAircraft()` (`script.js:189`) maps Avro fields to + tar1090-compatible fields and nests remaining Avro fields under `flarm`. + +5. `processAircraft()` creates or updates a `PlaneObject`, which calls + `updateData()` to set all fields including the FLARM classification. + +If `now` has not been set by the ADS-B data stream yet (race condition), +the AJAX callback initialises `now` to the current wall-clock time +(`script.js:2213-2215`). + +--- + +## Reaping / Timeout + +FLARM aircraft use the standard `seenTimeout` (default ~58 s) via +`checkVisible()`. The `seen` / `seen_pos` fields are set to `0` on every +update, so the aircraft appears "just received" as long as the poller runs. + +--- + +## File Change Summary + +| File | Change | +|---|---| +| `html/defaults.js:407-408` | Added `flarm_server`, `flarm_refresh` | +| `html/config.js:377-378` | Added commented example overrides | +| `html/script.js:89` | Added `'flarm'` to `sources[]` | +| `html/script.js:189-231` | Added `normalizeFlarmAircraft()` | +| `html/script.js:170` | Added `flarm_data` variable | +| `html/script.js:2177-2180` | Wired FLARM timer in `setIntervalTimers()` | +| `html/script.js:2204-2229` | Added `processFlarmUpdate()` | +| `html/script.js:1865` | Added FLARM to legend | +| `html/script.js:1844` | FLARM filter button label | +| `html/script.js:3591` | Guarded Non-ICAO info block for FLARM | +| `html/script.js:3930` | Calls `refreshSelectedFlarm()` from `refreshSelected()` | +| `html/script.js:3931-3971` | Added `refreshSelectedFlarm()` | +| `html/planeObject.js:56` | `this.flarm = null` in `setNull()` | +| `html/planeObject.js:204` | `target.flarm = source.flarm` in `planeCloneState()` | +| `html/planeObject.js:1670-1674` | FLARM classification in `updateData()` | +| `html/formatter.js:328-329` | `case 'flarm': return "FLARM"` | +| `html/markers.js:1259-1261` | FLARM marker returns glider icon | +| `html/index.html:719-731` | FLARM detail block inside `infoblock-container` | +| `html/data/flarm_aircraft.json` | New mock data file | diff --git a/html/config.js b/html/config.js index 2f4f4880e..851fab1c9 100644 --- a/html/config.js +++ b/html/config.js @@ -374,6 +374,9 @@ routeApiUrl = "https://flightroutes.opensky-network.org/api/routeset"; // aiscatcher_refresh = 15; // refresh interval in seconds // aisTimeout = 1200; +// flarm_server = "data/flarm_aircraft.json"; // URL or path to FLARM Avro JSON +// flarm_refresh = 5; // refresh interval in seconds + // droneJson = ""; // droneRefresh = 1; diff --git a/html/data/flarm_aircraft.json b/html/data/flarm_aircraft.json new file mode 100644 index 000000000..7748cc638 --- /dev/null +++ b/html/data/flarm_aircraft.json @@ -0,0 +1,35 @@ +{ + "aircraft": [ + { + "timestamp": 1717000000.0, + "icao24": "abcd01", + "icao24_num": 11259393, + "message_type": 0, + "radio_identifier_type": "FLARM", + "urgency": "Normal", + "icaoExt": "0000", + "message_type_ext": 0, + "is_stealth": false, + "is_no_track": false, + "version": 3, + "version_max": 3, + "time": 0, + "aircraft_type": "Helicopter", + "latitude": 47.5, + "longitude": 8.5, + "altitude": 1200.0, + "turn_rate": 0.0, + "speed": 30.0, + "climb_rate": 1.5, + "track": 180.0, + "movement_mode": "Flying", + "acc_pos_hor": 15.0, + "acc_pos_ver": 30.0, + "acc_vel": 2.0, + "sil": "Le1eMinus3", + "sda": "MajorDalC", + "nic": "RcLt1Nm", + "payload": "AAAA..." + } + ] +} diff --git a/html/defaults.js b/html/defaults.js index e0f6f09d4..b8f6bb238 100644 --- a/html/defaults.js +++ b/html/defaults.js @@ -31,15 +31,15 @@ let DisplayUnits = "nautical"; // degrees. // The google maps zoom level, 0 - 16, lower is further out -let DefaultZoomLvl = 9; +let DefaultZoomLvl = 9; let autoselectCoords = null; let showGrid = false; -let SiteShow = true; // true to show a center marker -let SiteName = "My Radar Site"; // tooltip of the marker +let SiteShow = true; // true to show a center marker +let SiteName = "My Radar Site"; // tooltip of the marker // Update GPS location (keep map centered on GPS location) let updateLocation = false; @@ -121,12 +121,12 @@ let altitudeChartDefaultState = true; // All color values are given as Hue (0-359) / Saturation (0-100) / Lightness (0-100) let ColorByAlt = { // HSL for planes with unknown altitude: - unknown : { h: 0, s: 0, l: 75 }, + unknown: { h: 0, s: 0, l: 75 }, // HSL for planes that are on the ground: - ground : { h: 220, s: 0, l: 30 }, + ground: { h: 220, s: 0, l: 30 }, - air : { + air: { // These define altitude-to-hue mappings // at particular altitudes; the hue // for intermediate altitudes that lie @@ -140,57 +140,57 @@ let ColorByAlt = { // hue of the first entry; altitudes above // the last entry use the hue of the last // entry. - h: [ { alt: 0, val: 20 }, // orange - { alt: 2000, val: 32.5 }, // yellow - { alt: 4000, val: 43 }, // yellow - { alt: 6000, val: 54 }, // yellow - { alt: 8000, val: 72 }, // yellow - { alt: 9000, val: 85 }, // green yellow - { alt: 11000, val: 140 }, // light green - { alt: 40000, val: 300 } , // magenta - { alt: 51000, val: 360 } , // red + h: [{ alt: 0, val: 20 }, // orange + { alt: 2000, val: 32.5 }, // yellow + { alt: 4000, val: 43 }, // yellow + { alt: 6000, val: 54 }, // yellow + { alt: 8000, val: 72 }, // yellow + { alt: 9000, val: 85 }, // green yellow + { alt: 11000, val: 140 }, // light green + { alt: 40000, val: 300 }, // magenta + { alt: 51000, val: 360 }, // red ], s: 88, l: [ - { h: 0, val: 53}, - { h: 20, val: 50}, - { h: 32, val: 54}, - { h: 40, val: 52}, - { h: 46, val: 51}, - { h: 50, val: 46}, - { h: 60, val: 43}, - { h: 80, val: 41}, - { h: 100, val: 41}, - { h: 120, val: 41}, - { h: 140, val: 41}, - { h: 160, val: 40}, - { h: 180, val: 40}, - { h: 190, val: 44}, - { h: 198, val: 50}, - { h: 200, val: 58}, - { h: 220, val: 58}, - { h: 240, val: 58}, - { h: 255, val: 55}, - { h: 266, val: 55}, - { h: 270, val: 58}, - { h: 280, val: 58}, - { h: 290, val: 47}, - { h: 300, val: 43}, - { h: 310, val: 48}, - { h: 320, val: 48}, - { h: 340, val: 52}, - { h: 360, val: 53}, + { h: 0, val: 53 }, + { h: 20, val: 50 }, + { h: 32, val: 54 }, + { h: 40, val: 52 }, + { h: 46, val: 51 }, + { h: 50, val: 46 }, + { h: 60, val: 43 }, + { h: 80, val: 41 }, + { h: 100, val: 41 }, + { h: 120, val: 41 }, + { h: 140, val: 41 }, + { h: 160, val: 40 }, + { h: 180, val: 40 }, + { h: 190, val: 44 }, + { h: 198, val: 50 }, + { h: 200, val: 58 }, + { h: 220, val: 58 }, + { h: 240, val: 58 }, + { h: 255, val: 55 }, + { h: 266, val: 55 }, + { h: 270, val: 58 }, + { h: 280, val: 58 }, + { h: 290, val: 47 }, + { h: 300, val: 43 }, + { h: 310, val: 48 }, + { h: 320, val: 48 }, + { h: 340, val: 52 }, + { h: 360, val: 53 }, ], }, // Changes added to the color of the currently selected plane - selected : { h: 0, s: 10, l: 5 }, + selected: { h: 0, s: 10, l: 5 }, // Changes added to the color of planes that have stale position info - stale : { h: 0, s: -35, l: 9 }, + stale: { h: 0, s: -35, l: 9 }, // Changes added to the color of planes that have positions from mlat - mlat : { h: 0, s: 0, l: 0 } + mlat: { h: 0, s: 0, l: 0 } }; // For a monochrome display try this: @@ -310,26 +310,26 @@ let squareMania = false; // Columns that have a // in front of them are shown. let HideCols = [ "#icao", -// "#country", -// "#flight", -// "#route", + // "#country", + // "#flight", + // "#route", "#registration", -// "#type", -// "#squawk", -// "#altitude", -// "#speed", + // "#type", + // "#squawk", + // "#altitude", + // "#speed", "#vert_rate", -// "#sitedist", + // "#sitedist", "#track", "#msgs", "#seen", -// "#rssi", + // "#rssi", "#lat", "#lon", "#data_source", "#military", - "#wd", - "#ws", + "#wd", + "#ws", ] @@ -403,6 +403,9 @@ let aiscatcher_refresh = 15; let aiscatcher_test = true; // unused let aisTimeout = 1200; +let flarm_server = "data/flarm_aircraft.json"; +let flarm_refresh = 5; + let droneJson = ""; let droneRefresh = 1; @@ -415,35 +418,37 @@ let OutlineMlatColor = null; let tableColorsDark; let tableColorsLight; let tableColors = { - unselected: { - adsb: "#d8f4ff", - mlat: "#FDF7DD", - uat: "#C4FFDC", - adsr: "#C4FFDC", - adsc: "#9efa9e", - modeS: "#d8d8ff", - tisb: "#ffd8e6", - unknown: "#dcdcdc", - other: "#dcdcdc", - ais: "#dcdcdc", - }, - selected: { - adsb: "#88DDFF", - mlat: "#F1DD83", - uat: "#66FFA6", - adsr: "#66FFA6", - adsc: "#75f075", - modeS: "#BEBEFF", - tisb: "#FFC1D8", - unknown: "#bcbcbc", - other: "#bcbcbc", - ais: "#bcbcbc", - }, - special: { - 7500: "#ff0000", - 7600: "#ff0000", - 7700: "#ff0000", - } + unselected: { + adsb: "#d8f4ff", + mlat: "#FDF7DD", + uat: "#C4FFDC", + adsr: "#C4FFDC", + adsc: "#9efa9e", + modeS: "#d8d8ff", + tisb: "#ffd8e6", + unknown: "#dcdcdc", + other: "#dcdcdc", + ais: "#dcdcdc", + flarm: "#ffd27f", + }, + selected: { + adsb: "#88DDFF", + mlat: "#F1DD83", + uat: "#66FFA6", + adsr: "#66FFA6", + adsc: "#75f075", + modeS: "#BEBEFF", + tisb: "#FFC1D8", + unknown: "#bcbcbc", + other: "#bcbcbc", + ais: "#bcbcbc", + flarm: "#f69d00", + }, + special: { + 7500: "#ff0000", + 7600: "#ff0000", + 7700: "#ff0000", + } }; let disableGeoLocation = false; @@ -461,8 +466,8 @@ let inhibitIframe = false; // !!! Please set the latitude / longitude in the decoder rather than // setting it here !!! // (graphs1090 will get the location from the decoder) -let SiteLat = null; // position of the marker -let SiteLon = null; +let SiteLat = null; // position of the marker +let SiteLon = null; // Default center of the map if no Site location is set let DefaultCenterLat = 40.56; diff --git a/html/formatter.js b/html/formatter.js index 39e2a621e..f6cdb0041 100644 --- a/html/formatter.js +++ b/html/formatter.js @@ -1,57 +1,57 @@ // -*- mode: javascript; indent-tabs-mode: t; c-basic-offset: 8 -*- "use strict"; -let NBSP='\u00a0'; -let NNBSP='\u202f'; -let NUMSP='\u2007'; -let DEGREES='\u00b0' -let ENDASH='\u2013'; -let UP_TRIANGLE='\u25b2'; // U+25B2 BLACK UP-POINTING TRIANGLE -let DOWN_TRIANGLE='\u25bc'; // U+25BC BLACK DOWN-POINTING TRIANGLE +let NBSP = '\u00a0'; +let NNBSP = '\u202f'; +let NUMSP = '\u2007'; +let DEGREES = '\u00b0' +let ENDASH = '\u2013'; +let UP_TRIANGLE = '\u25b2'; // U+25B2 BLACK UP-POINTING TRIANGLE +let DOWN_TRIANGLE = '\u25bc'; // U+25BC BLACK DOWN-POINTING TRIANGLE let EM_QUAD = '\u2001'; -let TrackDirections = ["North","NE","East","SE","South","SW","West","NW"]; -let TrackDirectionArrows = ["\u21e7","\u2b00","\u21e8","\u2b02","\u21e9","\u2b03","\u21e6","\u2b01"]; +let TrackDirections = ["North", "NE", "East", "SE", "South", "SW", "West", "NW"]; +let TrackDirectionArrows = ["\u21e7", "\u2b00", "\u21e8", "\u2b02", "\u21e9", "\u2b03", "\u21e6", "\u2b01"]; let UnitLabels = { - 'altitude': { metric: "m", imperial: "ft", nautical: "ft"}, - 'speed': { metric: "km/h", imperial: "mph", nautical: "kt" }, - 'distance': { metric: "km", imperial: "mi", nautical: "nmi" }, - 'verticalRate': { metric: "m/s", imperial: "ft/min", nautical: "ft/min" }, - 'distanceShort': { metric: "m", imperial: "ft", nautical: "m" } + 'altitude': { metric: "m", imperial: "ft", nautical: "ft" }, + 'speed': { metric: "km/h", imperial: "mph", nautical: "kt" }, + 'distance': { metric: "km", imperial: "mi", nautical: "nmi" }, + 'verticalRate': { metric: "m/s", imperial: "ft/min", nautical: "ft/min" }, + 'distanceShort': { metric: "m", imperial: "ft", nautical: "m" } }; let aircraftCategories = { - 'A0': 'Unspecified powered aircraft', - 'A1': `Light (< 15${NNBSP}500${NBSP}lb)`, - 'A2': `Small (15${NNBSP}500 to 75${NNBSP}000${NBSP}lb)`, - 'A3': `Large (75${NNBSP}000 to 300${NNBSP}000${NBSP}lb)`, - 'A4': 'High Vortex Large(aircraft such as B-757)', - 'A5': `Heavy (> 300${NNBSP}000${NBSP}lb)`, - 'A6': `High Performance (> 5${NBSP}g acceleration and > 400${NBSP}kt)`, - 'A7': 'Rotorcraft', - 'B0': 'Unspecified unpowered aircraft or UAV or spacecraft', - 'B1': 'Glider/sailplane', - 'B2': 'Lighter-than-Air', - 'B3': 'Parachutist/Skydiver', - 'B4': 'Ultralight/hang-glider/paraglider', - 'B6': 'Unmanned Aerial Vehicle', - 'B7': 'Space/Trans-atmospheric vehicle', - 'C0': 'Unspecified ground installation or vehicle', - 'C1': `Surface Vehicle ${ENDASH} Emergency Vehicle`, - 'C2': `Surface Vehicle ${ENDASH} Service Vehicle`, - 'C3': 'Fixed Ground or Tethered Obstruction' + 'A0': 'Unspecified powered aircraft', + 'A1': `Light (< 15${NNBSP}500${NBSP}lb)`, + 'A2': `Small (15${NNBSP}500 to 75${NNBSP}000${NBSP}lb)`, + 'A3': `Large (75${NNBSP}000 to 300${NNBSP}000${NBSP}lb)`, + 'A4': 'High Vortex Large(aircraft such as B-757)', + 'A5': `Heavy (> 300${NNBSP}000${NBSP}lb)`, + 'A6': `High Performance (> 5${NBSP}g acceleration and > 400${NBSP}kt)`, + 'A7': 'Rotorcraft', + 'B0': 'Unspecified unpowered aircraft or UAV or spacecraft', + 'B1': 'Glider/sailplane', + 'B2': 'Lighter-than-Air', + 'B3': 'Parachutist/Skydiver', + 'B4': 'Ultralight/hang-glider/paraglider', + 'B6': 'Unmanned Aerial Vehicle', + 'B7': 'Space/Trans-atmospheric vehicle', + 'C0': 'Unspecified ground installation or vehicle', + 'C1': `Surface Vehicle ${ENDASH} Emergency Vehicle`, + 'C2': `Surface Vehicle ${ENDASH} Service Vehicle`, + 'C3': 'Fixed Ground or Tethered Obstruction' }; // formatting helpers function get_category_label(category) { - if (!category) - return ''; - let label = aircraftCategories[category]; - if (!label) - return ''; - return label; + if (!category) + return ''; + let label = aircraftCategories[category]; + if (!label) + return ''; + return label; } function get_unit_label(quantity, systemOfMeasurement) { @@ -64,7 +64,7 @@ function get_unit_label(quantity, systemOfMeasurement) { // track in degrees (0..359) function format_track_brief(track, rounded) { - if (track == null){ + if (track == null) { return "n/a"; } @@ -73,41 +73,41 @@ function format_track_brief(track, rounded) { // track in degrees (0..359) function format_track_long(track, rounded) { - if (track == null){ + if (track == null) { return "n/a"; } let trackDir = Math.floor((360 + track % 360 + 22.5) / 45) % 8; - return TrackDirections[trackDir] + ":" + NNBSP + track.toFixed(rounded ? 0 : 1) + DEGREES; + return TrackDirections[trackDir] + ":" + NNBSP + track.toFixed(rounded ? 0 : 1) + DEGREES; } function format_track_arrow(track) { - if (track == null){ + if (track == null) { return ""; } let trackDir = Math.floor((360 + track % 360 + 22.5) / 45) % 8; - return TrackDirectionArrows[trackDir]; + return TrackDirectionArrows[trackDir]; } // alt in feet function format_altitude_brief(alt, vr, displayUnits, withUnits) { let alt_text; - if (alt == null){ + if (alt == null) { return NBSP + '?' + NBSP; - } else if (alt === "ground"){ + } else if (alt === "ground") { return "ground"; } alt_text = Math.round(convert_altitude(alt, displayUnits)).toString(); - if (withUnits) - alt_text += NNBSP + get_unit_label("altitude", displayUnits); + if (withUnits) + alt_text += NNBSP + get_unit_label("altitude", displayUnits); // Vertical Rate Triangle let verticalRateTriangle = ""; - if (vr > 245){ + if (vr > 245) { verticalRateTriangle = UP_TRIANGLE; - } else if (vr < -245){ + } else if (vr < -245) { verticalRateTriangle = DOWN_TRIANGLE; } else { verticalRateTriangle = '' @@ -149,12 +149,12 @@ function format_altitude(alt, displayUnits) { alt_text = Math.round(convert_altitude(alt, displayUnits)).toString() + NNBSP + get_unit_label("altitude", displayUnits); - return alt_text; + return alt_text; } // alt ground/airborne -function format_onground (alt) { +function format_onground(alt) { if (alt == null) { return "n/a"; } else if (alt === "ground") { @@ -178,9 +178,9 @@ function format_speed_brief(speed, displayUnits, withUnits) { if (speed == null || isNaN(speed)) { return ""; } - let speed_text = Math.round(convert_speed(speed, displayUnits)).toString(); - if (withUnits) - speed_text += NNBSP + get_unit_label("speed", displayUnits); + let speed_text = Math.round(convert_speed(speed, displayUnits)).toString(); + if (withUnits) + speed_text += NNBSP + get_unit_label("speed", displayUnits); return speed_text; } @@ -232,7 +232,7 @@ function format_distance_long(dist, displayUnits, fixed) { return dist_text; } -function format_distance_short (dist, displayUnits) { +function format_distance_short(dist, displayUnits) { if (dist == null) { return "n/a"; } @@ -273,7 +273,7 @@ function format_vert_rate_brief(rate, displayUnits) { // rate in ft/min function format_vert_rate_long(rate, displayUnits) { - if (rate == null){ + if (rate == null) { return "n/a"; } @@ -298,7 +298,7 @@ function format_latlng(p) { function format_data_source(source) { switch (source) { - case 'uat' : + case 'uat': return "UAT"; case 'mlat': return "MLAT"; @@ -323,16 +323,18 @@ function format_data_source(source) { return "AIS"; case 'mode_ac': return "Mode A/C"; - case 'adsc': - return jaeroLabel; - case 'other': - return "Other"; + case 'adsc': + return jaeroLabel; + case 'flarm': + return "FLARM"; + case 'other': + return "Other"; } return "Unknown"; } -function format_nac_p (value) { +function format_nac_p(value) { switch (value) { case 0: return "EPU ≥ 18.5 km"; @@ -364,7 +366,7 @@ function format_nac_p (value) { } } -function format_nac_v (value) { +function format_nac_v(value) { switch (value) { case 0: return "≥ 10 m/s"; @@ -382,339 +384,339 @@ function format_nac_v (value) { } function format_duration(seconds) { - if (seconds == null) - return "n/a"; - if (seconds < 20) - return seconds.toFixed(1) + ' s'; - if (seconds < 5 * 60) - return seconds.toFixed(0) + ' s'; - if (seconds < 3 * 60 * 60) - return (seconds/60).toFixed(0) + ' min'; - return (seconds/60/60).toFixed(0) + ' h'; + if (seconds == null) + return "n/a"; + if (seconds < 20) + return seconds.toFixed(1) + ' s'; + if (seconds < 5 * 60) + return seconds.toFixed(0) + ' s'; + if (seconds < 3 * 60 * 60) + return (seconds / 60).toFixed(0) + ' min'; + return (seconds / 60 / 60).toFixed(0) + ' h'; } function iOSVersion() { - if (/iP(hone|od|ad)/.test(navigator.platform)) { - // supports iOS 2.0 and later: - var v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/); - return [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3] || 0, 10)]; - } + if (/iP(hone|od|ad)/.test(navigator.platform)) { + // supports iOS 2.0 and later: + var v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/); + return [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3] || 0, 10)]; + } } function wqi(data) { - const INT32_MAX = 2147483647; - const buffer = data.buffer; - //console.log(buffer); - let u32 = new Uint32Array(data.buffer, 0, 13); - data.now = u32[0] / 1000 + u32[1] * 4294967.296; - //console.log(data.now); - let stride = u32[2]; - data.global_ac_count_withpos = u32[3]; - data.globeIndex = u32[4]; - - let limits = new Int16Array(buffer, 20, 4); - data.south = limits[0]; - data.west = limits[1]; - data.north = limits[2]; - data.east = limits[3]; - - data.messages = u32[7]; - - let s32 = new Int32Array(data.buffer, 0, stride / 4); - let receiver_lat = s32[8] / 1e6; - let receiver_lon = s32[9] / 1e6; - - - const binCraftVersion = u32[10]; - - data.messageRate = u32[11] / 10; - - const flags = u32[12]; - const useMessageRate = flags & (1 << 0); - - if (receiver_lat != 0 && receiver_lon != 0) { - //console.log("receiver_lat: " + receiver_lat + " receiver_lon: " + receiver_lon); - let position = { - coords: { - latitude: receiver_lat, - longitude: receiver_lon, - }, - }; - if (receiver_lat != SiteLat || receiver_lon != SiteLon) { - onLocationChange(position); - } - } - - data.aircraft = []; - for (let off = stride; off < buffer.byteLength; off += stride) { - let ac = {} - let u32 = new Uint32Array(buffer, off, stride / 4); - let s32 = new Int32Array(buffer, off, stride / 4); - let u16 = new Uint16Array(buffer, off, stride / 2); - let s16 = new Int16Array(buffer, off, stride / 2); - let u8 = new Uint8Array(buffer, off, stride); - let t = s32[0] & (1<<24); - ac.hex = (s32[0] & ((1<<24) - 1)).toString(16).padStart(6, '0'); - ac.hex = t ? ('~' + ac.hex) : ac.hex; - - if (binCraftVersion >= 20240218) { - ac.seen = s32[1] / 10; - ac.seen_pos = s32[27] / 10; - } else { - ac.seen_pos = u16[2] / 10; - ac.seen = u16[3] / 10; - } - - ac.lon = s32[2] / 1e6; - ac.lat = s32[3] / 1e6; - - ac.baro_rate = s16[8] * 8; - ac.geom_rate = s16[9] * 8; - ac.alt_baro = s16[10] * 25; - ac.alt_geom = s16[11] * 25; - - ac.nav_altitude_mcp = u16[12] * 4; - ac.nav_altitude_fms = u16[13] * 4; - ac.nav_qnh = s16[14] / 10; - ac.nav_heading = s16[15] / 90; - - const s = u16[16].toString(16).padStart(4, '0'); - if (s[0] > '9') { - ac.squawk = String(parseInt(s[0], 16)) + s[1] + s[2] + s[3]; - } else { - ac.squawk = s; - } - ac.gs = s16[17] / 10; - ac.mach = s16[18] / 1000; - ac.roll = s16[19] / 100; - - ac.track = s16[20] / 90; - ac.track_rate = s16[21] / 100; - ac.mag_heading = s16[22] / 90; - ac.true_heading = s16[23] / 90; - - ac.wd = s16[24]; - ac.ws = s16[25]; - ac.oat = s16[26]; - ac.tat = s16[27]; - - ac.tas = u16[28]; - ac.ias = u16[29]; - ac.rc = u16[30]; - - if (useMessageRate) { - ac.messageRate = u16[31] / 10; - } else { - ac.messages = u16[31]; - } - - ac.category = u8[64] ? u8[64].toString(16).toUpperCase() : undefined; - ac.nic = u8[65]; - - let nav_modes = u8[66]; - ac.nav_modes = true; - ac.emergency = u8[67] & 15; - ac.type = (u8[67] & 240) >> 4; - - ac.airground = u8[68] & 15; - ac.nav_altitude_src = (u8[68] & 240) >> 4; - - ac.sil_type = u8[69] & 15; - ac.adsb_version = (u8[69] & 240) >> 4; - - ac.adsr_version = u8[70] & 15; - ac.tisb_version = (u8[70] & 240) >> 4; - - ac.nac_p = u8[71] & 15; - ac.nac_v = (u8[71] & 240) >> 4; - - ac.sil = u8[72] & 3; - ac.gva = (u8[72] & 12) >> 2; - ac.sda = (u8[72] & 48) >> 4; - ac.nic_a = (u8[72] & 64) >> 6; - ac.nic_c = (u8[72] & 128) >> 7; - - - ac.flight = ""; - for (let i = 78; u8[i] && i < 86; i++) { - ac.flight += String.fromCharCode(u8[i]); - } - - ac.dbFlags = u16[43]; - - ac.t = ""; - for (let i = 88; u8[i] && i < 92; i++) { - ac.t += String.fromCharCode(u8[i]); - } - ac.r = ""; - for (let i = 92; u8[i] && i < 104; i++) { - ac.r += String.fromCharCode(u8[i]); - } - ac.receiverCount = u8[104]; - - if (binCraftVersion >= 20250403) { - ac.rssi = (u8[105] * (50 / 255)) - 50; - } else { - let level = u8[105]*u8[105]/65025 + 1.125e-5; - ac.rssi = 10 * Math.log(level)/Math.log(10); - } - - ac.extraFlags = u8[106]; - ac.nogps = ac.extraFlags & 1; - if (ac.nogps && nogpsOnly && s32[3] != INT32_MAX) { - u8[73] |= 64; - u8[73] |= 16; - } - - // must come after the stuff above (validity bits) - - ac.nic_baro = (u8[73] & 1); - ac.alert1 = (u8[73] & 2); - ac.spi = (u8[73] & 4); - ac.flight = (u8[73] & 8) ? ac.flight : undefined; - ac.alt_baro = (u8[73] & 16) ? ac.alt_baro : undefined; - ac.alt_geom = (u8[73] & 32) ? ac.alt_geom : undefined; - - ac.lat = (u8[73] & 64) ? ac.lat : undefined; - ac.lon = (u8[73] & 64) ? ac.lon : undefined; - ac.seen_pos = (u8[73] & 64) ? ac.seen_pos : undefined; - - ac.gs = (u8[73] & 128) ? ac.gs : undefined; - - ac.ias = (u8[74] & 1) ? ac.ias : undefined; - ac.tas = (u8[74] & 2) ? ac.tas : undefined; - ac.mach = (u8[74] & 4) ? ac.mach : undefined; - ac.track = (u8[74] & 8) ? ac.track : undefined; - ac.calc_track = !(u8[74] & 8) ? ac.track : undefined; - ac.track_rate = (u8[74] & 16) ? ac.track_rate : undefined; - ac.roll = (u8[74] & 32) ? ac.roll : undefined; - ac.mag_heading = (u8[74] & 64) ? ac.mag_heading : undefined; - ac.true_heading = (u8[74] & 128) ? ac.true_heading : undefined; - - ac.baro_rate = (u8[75] & 1) ? ac.baro_rate : undefined; - ac.geom_rate = (u8[75] & 2) ? ac.geom_rate : undefined; - ac.nic_a = (u8[75] & 4) ? ac.nic_a : undefined; - ac.nic_c = (u8[75] & 8) ? ac.nic_c : undefined; - ac.nic_baro = (u8[75] & 16) ? ac.nic_baro : undefined; - ac.nac_p = (u8[75] & 32) ? ac.nac_p : undefined; - ac.nac_v = (u8[75] & 64) ? ac.nac_v : undefined; - ac.sil = (u8[75] & 128) ? ac.sil : undefined; - - ac.gva = (u8[76] & 1) ? ac.gva : undefined; - ac.sda = (u8[76] & 2) ? ac.sda : undefined; - ac.squawk = (u8[76] & 4) ? ac.squawk : undefined; - ac.emergency = (u8[76] & 8) ? ac.emergency : undefined; - ac.spi = (u8[76] & 16) ? ac.spi : undefined; - ac.nav_qnh = (u8[76] & 32) ? ac.nav_qnh : undefined; - ac.nav_altitude_mcp = (u8[76] & 64) ? ac.nav_altitude_mcp : undefined; - ac.nav_altitude_fms = (u8[76] & 128) ? ac.nav_altitude_fms : undefined; - - ac.nav_altitude_src = (u8[77] & 1) ? ac.nav_altitude_src : undefined; - ac.nav_heading = (u8[77] & 2) ? ac.nav_heading : undefined; - ac.nav_modes = (u8[77] & 4) ? ac.nav_modes : undefined; - ac.alert1 = (u8[77] & 8) ? ac.alert1 : undefined; - ac.ws = (u8[77] & 16) ? ac.ws : undefined; - ac.wd = (u8[77] & 16) ? ac.wd : undefined; - ac.oat = (u8[77] & 32) ? ac.oat : undefined; - ac.tat = (u8[77] & 32) ? ac.tat : undefined; - - if (ac.airground == 1) - ac.alt_baro = "ground"; - - if (ac.nav_modes) { - ac.nav_modes = []; - if (nav_modes & 1) ac.nav_modes.push('autopilot'); - if (nav_modes & 2) ac.nav_modes.push('vnav'); - if (nav_modes & 4) ac.nav_modes.push('alt_hold'); - if (nav_modes & 8) ac.nav_modes.push('approach'); - if (nav_modes & 16) ac.nav_modes.push('lnav'); - if (nav_modes & 32) ac.nav_modes.push('tcas'); - } - switch (ac.type) { - case 0: ac.type = 'adsb_icao'; break; - case 1: ac.type = 'adsb_icao_nt'; break; - case 2: ac.type = 'adsr_icao'; break; - case 3: ac.type = 'tisb_icao'; break; - case 4: ac.type = 'adsc'; break; - case 5: ac.type = 'mlat'; break; - case 6: ac.type = 'other'; break; - case 7: ac.type = 'mode_s'; break; - case 8: ac.type = 'adsb_other'; break; - case 9: ac.type = 'adsr_other'; break; - case 10: ac.type = 'tisb_trackfile'; break; - case 11: ac.type = 'tisb_other'; break; - case 12: ac.type = 'mode_ac'; break; - default: ac.type = 'unknown'; - } - const type4 = ac.type.slice(0, 4); - if (type4 == 'adsb') { - ac.version = ac.adsb_version; - } else if (type4 == 'adsr') { - ac.version = ac.adsr_version; - } else if (type4 == 'tisb') { - ac.version = ac.tisb_version; - } - - if (binCraftVersion >= 20240218) { - if (stride == 116) { - ac.rId = u32[28].toString(16).padStart(8, '0'); - } - } else { - if (stride == 112) { - ac.rId = u32[27].toString(16).padStart(8, '0'); - } - } - - data.aircraft.push(ac); - } + const INT32_MAX = 2147483647; + const buffer = data.buffer; + //console.log(buffer); + let u32 = new Uint32Array(data.buffer, 0, 13); + data.now = u32[0] / 1000 + u32[1] * 4294967.296; + //console.log(data.now); + let stride = u32[2]; + data.global_ac_count_withpos = u32[3]; + data.globeIndex = u32[4]; + + let limits = new Int16Array(buffer, 20, 4); + data.south = limits[0]; + data.west = limits[1]; + data.north = limits[2]; + data.east = limits[3]; + + data.messages = u32[7]; + + let s32 = new Int32Array(data.buffer, 0, stride / 4); + let receiver_lat = s32[8] / 1e6; + let receiver_lon = s32[9] / 1e6; + + + const binCraftVersion = u32[10]; + + data.messageRate = u32[11] / 10; + + const flags = u32[12]; + const useMessageRate = flags & (1 << 0); + + if (receiver_lat != 0 && receiver_lon != 0) { + //console.log("receiver_lat: " + receiver_lat + " receiver_lon: " + receiver_lon); + let position = { + coords: { + latitude: receiver_lat, + longitude: receiver_lon, + }, + }; + if (receiver_lat != SiteLat || receiver_lon != SiteLon) { + onLocationChange(position); + } + } + + data.aircraft = []; + for (let off = stride; off < buffer.byteLength; off += stride) { + let ac = {} + let u32 = new Uint32Array(buffer, off, stride / 4); + let s32 = new Int32Array(buffer, off, stride / 4); + let u16 = new Uint16Array(buffer, off, stride / 2); + let s16 = new Int16Array(buffer, off, stride / 2); + let u8 = new Uint8Array(buffer, off, stride); + let t = s32[0] & (1 << 24); + ac.hex = (s32[0] & ((1 << 24) - 1)).toString(16).padStart(6, '0'); + ac.hex = t ? ('~' + ac.hex) : ac.hex; + + if (binCraftVersion >= 20240218) { + ac.seen = s32[1] / 10; + ac.seen_pos = s32[27] / 10; + } else { + ac.seen_pos = u16[2] / 10; + ac.seen = u16[3] / 10; + } + + ac.lon = s32[2] / 1e6; + ac.lat = s32[3] / 1e6; + + ac.baro_rate = s16[8] * 8; + ac.geom_rate = s16[9] * 8; + ac.alt_baro = s16[10] * 25; + ac.alt_geom = s16[11] * 25; + + ac.nav_altitude_mcp = u16[12] * 4; + ac.nav_altitude_fms = u16[13] * 4; + ac.nav_qnh = s16[14] / 10; + ac.nav_heading = s16[15] / 90; + + const s = u16[16].toString(16).padStart(4, '0'); + if (s[0] > '9') { + ac.squawk = String(parseInt(s[0], 16)) + s[1] + s[2] + s[3]; + } else { + ac.squawk = s; + } + ac.gs = s16[17] / 10; + ac.mach = s16[18] / 1000; + ac.roll = s16[19] / 100; + + ac.track = s16[20] / 90; + ac.track_rate = s16[21] / 100; + ac.mag_heading = s16[22] / 90; + ac.true_heading = s16[23] / 90; + + ac.wd = s16[24]; + ac.ws = s16[25]; + ac.oat = s16[26]; + ac.tat = s16[27]; + + ac.tas = u16[28]; + ac.ias = u16[29]; + ac.rc = u16[30]; + + if (useMessageRate) { + ac.messageRate = u16[31] / 10; + } else { + ac.messages = u16[31]; + } + + ac.category = u8[64] ? u8[64].toString(16).toUpperCase() : undefined; + ac.nic = u8[65]; + + let nav_modes = u8[66]; + ac.nav_modes = true; + ac.emergency = u8[67] & 15; + ac.type = (u8[67] & 240) >> 4; + + ac.airground = u8[68] & 15; + ac.nav_altitude_src = (u8[68] & 240) >> 4; + + ac.sil_type = u8[69] & 15; + ac.adsb_version = (u8[69] & 240) >> 4; + + ac.adsr_version = u8[70] & 15; + ac.tisb_version = (u8[70] & 240) >> 4; + + ac.nac_p = u8[71] & 15; + ac.nac_v = (u8[71] & 240) >> 4; + + ac.sil = u8[72] & 3; + ac.gva = (u8[72] & 12) >> 2; + ac.sda = (u8[72] & 48) >> 4; + ac.nic_a = (u8[72] & 64) >> 6; + ac.nic_c = (u8[72] & 128) >> 7; + + + ac.flight = ""; + for (let i = 78; u8[i] && i < 86; i++) { + ac.flight += String.fromCharCode(u8[i]); + } + + ac.dbFlags = u16[43]; + + ac.t = ""; + for (let i = 88; u8[i] && i < 92; i++) { + ac.t += String.fromCharCode(u8[i]); + } + ac.r = ""; + for (let i = 92; u8[i] && i < 104; i++) { + ac.r += String.fromCharCode(u8[i]); + } + ac.receiverCount = u8[104]; + + if (binCraftVersion >= 20250403) { + ac.rssi = (u8[105] * (50 / 255)) - 50; + } else { + let level = u8[105] * u8[105] / 65025 + 1.125e-5; + ac.rssi = 10 * Math.log(level) / Math.log(10); + } + + ac.extraFlags = u8[106]; + ac.nogps = ac.extraFlags & 1; + if (ac.nogps && nogpsOnly && s32[3] != INT32_MAX) { + u8[73] |= 64; + u8[73] |= 16; + } + + // must come after the stuff above (validity bits) + + ac.nic_baro = (u8[73] & 1); + ac.alert1 = (u8[73] & 2); + ac.spi = (u8[73] & 4); + ac.flight = (u8[73] & 8) ? ac.flight : undefined; + ac.alt_baro = (u8[73] & 16) ? ac.alt_baro : undefined; + ac.alt_geom = (u8[73] & 32) ? ac.alt_geom : undefined; + + ac.lat = (u8[73] & 64) ? ac.lat : undefined; + ac.lon = (u8[73] & 64) ? ac.lon : undefined; + ac.seen_pos = (u8[73] & 64) ? ac.seen_pos : undefined; + + ac.gs = (u8[73] & 128) ? ac.gs : undefined; + + ac.ias = (u8[74] & 1) ? ac.ias : undefined; + ac.tas = (u8[74] & 2) ? ac.tas : undefined; + ac.mach = (u8[74] & 4) ? ac.mach : undefined; + ac.track = (u8[74] & 8) ? ac.track : undefined; + ac.calc_track = !(u8[74] & 8) ? ac.track : undefined; + ac.track_rate = (u8[74] & 16) ? ac.track_rate : undefined; + ac.roll = (u8[74] & 32) ? ac.roll : undefined; + ac.mag_heading = (u8[74] & 64) ? ac.mag_heading : undefined; + ac.true_heading = (u8[74] & 128) ? ac.true_heading : undefined; + + ac.baro_rate = (u8[75] & 1) ? ac.baro_rate : undefined; + ac.geom_rate = (u8[75] & 2) ? ac.geom_rate : undefined; + ac.nic_a = (u8[75] & 4) ? ac.nic_a : undefined; + ac.nic_c = (u8[75] & 8) ? ac.nic_c : undefined; + ac.nic_baro = (u8[75] & 16) ? ac.nic_baro : undefined; + ac.nac_p = (u8[75] & 32) ? ac.nac_p : undefined; + ac.nac_v = (u8[75] & 64) ? ac.nac_v : undefined; + ac.sil = (u8[75] & 128) ? ac.sil : undefined; + + ac.gva = (u8[76] & 1) ? ac.gva : undefined; + ac.sda = (u8[76] & 2) ? ac.sda : undefined; + ac.squawk = (u8[76] & 4) ? ac.squawk : undefined; + ac.emergency = (u8[76] & 8) ? ac.emergency : undefined; + ac.spi = (u8[76] & 16) ? ac.spi : undefined; + ac.nav_qnh = (u8[76] & 32) ? ac.nav_qnh : undefined; + ac.nav_altitude_mcp = (u8[76] & 64) ? ac.nav_altitude_mcp : undefined; + ac.nav_altitude_fms = (u8[76] & 128) ? ac.nav_altitude_fms : undefined; + + ac.nav_altitude_src = (u8[77] & 1) ? ac.nav_altitude_src : undefined; + ac.nav_heading = (u8[77] & 2) ? ac.nav_heading : undefined; + ac.nav_modes = (u8[77] & 4) ? ac.nav_modes : undefined; + ac.alert1 = (u8[77] & 8) ? ac.alert1 : undefined; + ac.ws = (u8[77] & 16) ? ac.ws : undefined; + ac.wd = (u8[77] & 16) ? ac.wd : undefined; + ac.oat = (u8[77] & 32) ? ac.oat : undefined; + ac.tat = (u8[77] & 32) ? ac.tat : undefined; + + if (ac.airground == 1) + ac.alt_baro = "ground"; + + if (ac.nav_modes) { + ac.nav_modes = []; + if (nav_modes & 1) ac.nav_modes.push('autopilot'); + if (nav_modes & 2) ac.nav_modes.push('vnav'); + if (nav_modes & 4) ac.nav_modes.push('alt_hold'); + if (nav_modes & 8) ac.nav_modes.push('approach'); + if (nav_modes & 16) ac.nav_modes.push('lnav'); + if (nav_modes & 32) ac.nav_modes.push('tcas'); + } + switch (ac.type) { + case 0: ac.type = 'adsb_icao'; break; + case 1: ac.type = 'adsb_icao_nt'; break; + case 2: ac.type = 'adsr_icao'; break; + case 3: ac.type = 'tisb_icao'; break; + case 4: ac.type = 'adsc'; break; + case 5: ac.type = 'mlat'; break; + case 6: ac.type = 'other'; break; + case 7: ac.type = 'mode_s'; break; + case 8: ac.type = 'adsb_other'; break; + case 9: ac.type = 'adsr_other'; break; + case 10: ac.type = 'tisb_trackfile'; break; + case 11: ac.type = 'tisb_other'; break; + case 12: ac.type = 'mode_ac'; break; + default: ac.type = 'unknown'; + } + const type4 = ac.type.slice(0, 4); + if (type4 == 'adsb') { + ac.version = ac.adsb_version; + } else if (type4 == 'adsr') { + ac.version = ac.adsr_version; + } else if (type4 == 'tisb') { + ac.version = ac.tisb_version; + } + + if (binCraftVersion >= 20240218) { + if (stride == 116) { + ac.rId = u32[28].toString(16).padStart(8, '0'); + } + } else { + if (stride == 112) { + ac.rId = u32[27].toString(16).padStart(8, '0'); + } + } + + data.aircraft.push(ac); + } } function ItemCache(maxItems) { - this.maxItems = maxItems; - this.items = {}; - this.keys = []; + this.maxItems = maxItems; + this.items = {}; + this.keys = []; } ItemCache.prototype.clear = function() { - this.items = {}; - this.keys = []; + this.items = {}; + this.keys = []; } ItemCache.prototype.get = function(key) { - return this.items[key]; + return this.items[key]; } ItemCache.prototype.add = function(key, value) { - if (!(key in this.items)) { - this.keys.push(key); - } - this.items[key] = value; + if (!(key in this.items)) { + this.keys.push(key); + } + this.items[key] = value; - if (this.maxItems && this.maxItems > 0) { - while (this.keys.length > this.maxItems) { - const key = this.keys.shift(); - delete this.items[key]; - } - } + if (this.maxItems && this.maxItems > 0) { + while (this.keys.length > this.maxItems) { + const key = this.keys.shift(); + delete this.items[key]; + } + } } function itemCacheTest() { - let a = new ItemCache(4); - a.add(8, 4); - a.add(5, 2); - a.add(4, 2); - a.add(3, 2); - a.add(1, 2); - a.add(1, 3); - a.add(1, 5); - let items = JSON.stringify(a.items) - let keys = JSON.stringify(a.keys); - const expectedItems = '{"1":5,"3":2,"4":2,"5":2}'; - const expectedKeys = '[5,4,3,1]'; - if (items != expectedItems || keys != expectedKeys || g.get(1) != 5) { - console.error(`ItemCache broken!`); - console.log(`got: items: ${items} keys: ${keys}`); - console.log(`expected: items: ${expectedItems} keys: ${expectedKeys}`); - } else { - console.log(`ItemCache tested correctly!`); - } + let a = new ItemCache(4); + a.add(8, 4); + a.add(5, 2); + a.add(4, 2); + a.add(3, 2); + a.add(1, 2); + a.add(1, 3); + a.add(1, 5); + let items = JSON.stringify(a.items) + let keys = JSON.stringify(a.keys); + const expectedItems = '{"1":5,"3":2,"4":2,"5":2}'; + const expectedKeys = '[5,4,3,1]'; + if (items != expectedItems || keys != expectedKeys || g.get(1) != 5) { + console.error(`ItemCache broken!`); + console.log(`got: items: ${items} keys: ${keys}`); + console.log(`expected: items: ${expectedItems} keys: ${expectedKeys}`); + } else { + console.log(`ItemCache tested correctly!`); + } } diff --git a/html/index.html b/html/index.html index b5a781820..f41c3fbae 100644 --- a/html/index.html +++ b/html/index.html @@ -43,8 +43,8 @@
n/a
-
Route:
-
+
Route:
+
@@ -354,7 +354,7 @@ - +
@@ -708,14 +708,27 @@
Learn more about Mode S data type by hovering over each data label.
-
- Pos. epoch: -
-
- n/a -
+
+ Pos. epoch: +
+
+ n/a +
+ +
Export KML @@ -729,7 +742,7 @@
- +
@@ -789,35 +802,35 @@
-
-
- -
-
- -
-
-
-
L
-
O
-
K
-
-
- -
-
- -
M
-
-
-
P
-
I
-
-
-
R
-
F
+
+
+ +
+
+
+
+
+
L
+
O
+
K
+
+
+ +
+
+ +
M
+
+
+
P
+
I
+
+
+
R
+
F
+
@@ -918,7 +931,7 @@ - + @@ -938,7 +951,7 @@ - + @@ -1021,21 +1034,21 @@ - -
-
Filter by DB flags:
- - - -
-
    -
    - - -
    -
    - - + +
    +
    Filter by DB flags:
    + + + +
    +
      +
      + + +
      +
      + + diff --git a/html/markers.js b/html/markers.js index 57f7e0307..36e35e109 100644 --- a/html/markers.js +++ b/html/markers.js @@ -151,7 +151,7 @@ let shapes = { }, // Mac Donnell-Doulgas F/A-18B Hornet (ICAO F18H) 'f18': { - viewBox:'-4 -3 32 32', + viewBox: '-4 -3 32 32', w: 25, h: 25, accentMult: 0.8, @@ -242,7 +242,7 @@ let shapes = { path: 'M33.76 6.93l.01.4.2.06.05.07 3.84.45s.18-.36.6-.88c0 0-.26-2.86.6-3.83v-.28l.05-.05s.1-.51.28 0c0 0 .24-.18.3-.15.05.03 1.31.19 1.23 4.3 0 0 .62.84.58.94l3.83-.44.06-.08.23-.08-.01-.4s-.7-.21-4.04-.5l-.12-.13 4.32.01s.43-1.46.88 0l5.11-.07v.06s-4.12.36-4.79.71l-.2-.24.03.52.2.15.06 1.09-.07.14s-.09 1.63-.41 2.37l-.63-.03-.23-1.2-3.91.43s.36.97-.97 3.79l-.1 2.47h1.06s.06-.28.16-.3m0 0s.43-.02-.04 2.41h-4.5s-.46-2.46-.04-2.4c0 0 .12.02.16.29l1.04.01-.08-2.5s-1.2-2.38-.96-3.82l-3.89-.44-.24 1.2-.62.03s-.4-1.1-.43-2.38l-.05-.1.04-1.1.24-.16.01-.5-.2.23s-1.02-.4-4.8-.71v-.07l5.12.05s.42-1.39.86 0h4.34l-.13.14s-3.99.39-4.03.52', }, 't38': { - viewBox:'22.2 -6 36 36', + viewBox: '22.2 -6 36 36', w: 28, h: 28, strokeScale: 1.2, @@ -256,7 +256,7 @@ let shapes = { path: 'm 16.85,2.96 c 0.38,3.4 0.78,5.93 0.78,5.93 l 0.29,0.72 0.77,-0.84 0.38,0.73 0.19,4.5 0.71,1.41 5.85,3.95 v 2.84 l -6.47,1.61 0.5,1.17 2.84,1.92 v 1.6 l -4.85,1.2 -0.63,-4.05 -0.45,0.78 H 15.24 L 14.79,25.65 14.16,29.7 9.31,28.5 v -1.6 l 2.85,-1.92 0.49,-1.17 -6.47,-1.61 v -2.84 l 5.85,-3.95 0.71,-1.4 0.2,-4.51 0.37,-0.73 0.77,0.84 0.3,-0.72 c 0,0 0.4,-2.53 0.77,-5.93 C 15.19,2.27 15.77,1.09 16,1 c 0.209,-0.018 0.78,1.25 0.85,1.96 z', }, 'l159': { - viewBox:'-3.7 0 32 32', + viewBox: '-3.7 0 32 32', w: 28, h: 28, strokeScale: 1, @@ -265,7 +265,7 @@ let shapes = { accent: "M10.81 13.65l.75-.07c.45.92.7 2.13.7 2.13l-.01 10.4v-10.4s.24-1.21.68-2.13l.75.07", }, 'mirage': { - viewBox:'-5.8 -3.8 36 36', + viewBox: '-5.8 -3.8 36 36', w: 28, h: 28, strokeScale: 1.2, @@ -273,7 +273,7 @@ let shapes = { accent: "M 9.90,9.57 C 10.50,12.57 10.80,15.90 10.80,15.90 l 0.20,4.50 0.01,3.21 M 14.59,9.57 C 13.99,12.57 13.69,15.90 13.69,15.90 l -0.20,4.50 -0.01,3.21", }, 'sb39': { // Saab JAS-39 Gripen (ICAO SB39) - viewBox:'-4.1 0 32 32', + viewBox: '-4.1 0 32 32', w: 29, h: 29, strokeScale: 1, @@ -283,7 +283,7 @@ let shapes = { }, // Mac Donnell-Doulgas A4 Skyhawk (ICAO A4) 'md_a4': { - viewBox:'-4.2 -1 32 32', + viewBox: '-4.2 -1 32 32', w: 26, h: 26, accentMult: 0.8, @@ -293,7 +293,7 @@ let shapes = { }, // Panavia Tornado (ICAO TOR) 'tornado': { - viewBox:'-4 -3 32 32', + viewBox: '-4 -3 32 32', w: 25, h: 25, strokeScale: 1.1, @@ -310,7 +310,7 @@ let shapes = { path: 'M16.8 15.57v-1.08s1.8-5.07-.74-5.58c-2.44.59-.67 5.58-.67 5.58h0v1.08l-2.6.15-10.23.54v1.08l10.26.46 2.55.07v3.86h-3.05v1.38h3.25v.34l-1.81.25v.14h1.98c.15.49.35.47.35.47s.2.02.35-.47h1.99v-.14l-1.81-.25v-.34h3.24v-1.38h-3.05v-3.86l2.55-.07 10.26-.46v-1.08l-10.22-.54z', }, 'typhoon': { - viewBox:'-4.8 -3.5 34 34', + viewBox: '-4.8 -3.5 34 34', w: 25, h: 25, strokeScale: 1.2, @@ -326,7 +326,7 @@ let shapes = { }, // Hawker Hunter (ICAO HUNT) 'hunter': { - viewBox:'-5.2 -4 34 34', + viewBox: '-5.2 -4 34 34', w: 26, h: 26, accentMult: 0.8, @@ -397,7 +397,7 @@ let shapes = { 'u2': { w: 32, h: 32, - viewBox:'-3.8 -4 34 34', + viewBox: '-3.8 -4 34 34', path: 'M16.9 21s.2 0 .2.3v.7s0 .3-.2.2l-3.7.5s0 1-.2.8c0 0-.2.2-.2-.8l-3.6-.5s-.2 0-.2-.4v-.6s0-.2.3-.3l3-.5V16l-2.4-.2s-.1.8-.5.8c0 0-.5 0-.5-1l-4.3-.3s-.1.5-.2 0l-7-.6v-1.2L9 12.1s-.2-2.5.4-2.5c0 0 .6-.2.5 2.3l2.3-.3s-.4-1.6-.1-2h.5V6.4s0-2.4.6-2.5c0 0 .5 0 .6 2.5v3.2h.5s.3.3 0 2l2.2.4v-1.5s0-.8.4-1c0 0 .6 0 .5 1v1.6l11.5 1.5v1.2l-7 .6s0 .6-.2 0l-4.4.5s0 1-.4.9c0 0-.3 0-.4-.9l-2.6.3v4.2z', }, 'glider': { @@ -425,7 +425,7 @@ let shapes = { }, // Verhees Engineering Verhees Delta (ICAO DLTA) 'verhees': { - viewBox:'-3.7 0 32 32', + viewBox: '-3.7 0 32 32', w: 28, h: 28, strokeScale: 1.2, @@ -435,7 +435,7 @@ let shapes = { }, // Gyrocopter Generic Icon (ICAO GYRO) 'gyrocopter': { - viewBox:'-3.5 -3 32 32', + viewBox: '-3.5 -3 32 32', w: 27, h: 27, strokeScale: 1.0, @@ -446,27 +446,27 @@ let shapes = { }, // liberally modeled after ec145 'helicopter': { - w:29, - h:29, - viewBox:'-13 -13 90 90', + w: 29, + h: 29, + viewBox: '-13 -13 90 90', strokeScale: 3.0, path: "m 24.698,60.712 c 0,0 -0.450,2.134 -0.861,2.142 -0.561,0.011 -0.480,-3.836 -0.593,-5.761 -0.064,-1.098 1.381,-1.192 1.481,-0.042 l 5.464,0.007 -0.068,-9.482 -0.104,-1.108 c -2.410,-2.131 -3.028,-3.449 -3.152,-7.083 l -12.460,13.179 c -0.773,0.813 -2.977,0.599 -3.483,-0.428 L 26.920,35.416 26.866,29.159 11.471,14.513 c -0.813,-0.773 -0.599,-2.977 0.428,-3.483 l 14.971,14.428 0.150,-5.614 c -0.042,-1.324 1.075,-4.784 3.391,-5.633 0.686,-0.251 2.131,-0.293 3.033,0.008 2.349,0.783 3.433,4.309 3.391,5.633 l 0.073,4.400 12.573,-12.763 c 0.779,-0.807 2.977,-0.599 3.483,0.428 L 37.054,28.325 37.027,35.027 52.411,49.365 c 0.813,0.773 0.599,2.977 -0.428,3.483 L 36.992,38.359 c -0.124,3.634 -0.742,5.987 -3.152,8.118 l -0.104,1.108 -0.068,9.482 5.321,-0.068 c 0.101,-1.150 1.546,-1.057 1.481,0.042 -0.113,1.925 -0.032,5.772 -0.593,5.761 -0.412,-0.008 -0.861,-2.142 -0.861,-2.142 l -5.387,-0.011 0.085,9.377 -1.094,2.059 -1.386,-0.018 -1.093,-2.049 0.085,-9.377 z", }, 'pa24': { - viewBox:'-3 -3 30 30', + viewBox: '-3 -3 30 30', w: 30, h: 30, noAspect: true, path: 'M12.17 17.81l2.77.46.01.98-3.03.14-3.14-.18.02-.97 2.77-.43-.61-4.52-7.18-1.23s-.4-1.27.02-1.88l5.72-.25 1.39-.9.43-2.7-1.41-.01s-.15-.1 0-.16l1.73-.03s.34-1.08.67 0l1.72.04s.15.07 0 .15h-1.42l.36 2.72 1.37.95 5.82.27s.4.16.02 1.9l-7.33 1.14z', }, 'bae_hawk': { - viewBox:'-2 0 29 29', + viewBox: '-2 0 29 29', w: 33, h: 33, path: 'M15.9 21.7l-3-.7h-.7l-2.9.7s-.2-.5.2-1l2.4-1.6-.1-3.4-.1-.2-6.1.7s-.3-1.1.6-1.6l4.3-2.2.8-.5s-.3-1 .2-1.3h.4V7.2s.1-2.1.8-2.4c0 0 .6 0 .7 2.1v3.7h.5s.4.1.1 1.3l.8.6 4.3 2.1s.8.3.5 1.7l-6-.8-.1.2s0 1.8-.2 3.4l2.3 1.7s.4.2.3 1z', }, 'a10': { - viewBox:'0 0 32 32', + viewBox: '0 0 32 32', w: 24, h: 24, strokeScale: 1.2, @@ -475,7 +475,7 @@ let shapes = { "m 17.06817,18.98577 0.0039,1.41211 c 0.0062,2.53506 0.06826,3.269951 0.328125,3.857421 0.207911,0.538692 0.566942,0.639573 0.986328,0.648438 0.419386,-0.0089 0.78037,-0.109746 0.988281,-0.648438 0.25987,-0.58747 0.321925,-1.322361 0.328125,-3.857421 l 0.0039,-1.41211 c -2.638672,0 0,0 -2.638672,0 z"], }, 'chinook': { - viewBox:'-4.5 -3 32 32', + viewBox: '-4.5 -3 32 32', w: 32, h: 32, path: [ @@ -498,15 +498,15 @@ let shapes = { strokeScale: 1.2, path: 'M19.7 27.47s.12.61-.38.56l-2.38.18.34.6-.2.65-.38-.66-.05 1.8-.13.22.04-2.28h-.3s.02 1.3-.25 1.6c0 0-.24-.03-.35-1.22 0 0-.32.12-.32-.69l-2.79-.2s-.4.07-.34-.48l.01-.46s.03-.42.36-.4l3.05-.31-.31-8.77s-.08-.4-.51-.52l-.01-.32-.77-.03v-.85l-.18-.1.2-.88s-.16-.2-.14-1.7l-7.6 5.18-.82.27-.43-.63.83-.26 7.4-5.1h.63l.03-2.26-.1-.11s-.27.42-.5-.02l.02-.9s.22-.43.45 0v.44l.21-.24.02-.44-4.83-7.09-.25-.8.62-.42.27.82 4.19 6.1s-.27-3.85 1.77-3.9c0 0 2.19-.52 2.03 3.65v2.07l.2.19-.01-.44s.18-.34.4 0l.02.48 7.08-4.83.82-.26.42.62-.81.26-7.47 5.13h-.48l-.05 3.8 4.62 6.8.25.83-.62.42-.27-.83-4.06-5.93.01.93-.74.01v.3s-.58.17-.58.86l-.31 8.5h.27v-.38l.15-.3v.71l2.45.24s.52-.05.53.33z', }, - 's61':{ - w:28, - h:28, + 's61': { + w: 28, + h: 28, strokeScale: 1.1, - viewBox:'0 0 32 32', - path:'M11.16.91l-.9.54 4.9 10.19v.24h-.72v-.15c-.02-.53-.82-.74-1 0-.22.8-.16 1.42-.04 2L2.9 14.96 3.14 16l10.48-1.3.02.07c.06.3.15.45.24.62.11.11.24.16.38-.05.04-.14.07-.3.1-.49h.8l-.03 4.42c.04.33.09.6.14.84l-1.5 6.95.92.14 1.05-5.62.18.4.46 8.84c-.01.2.18.18.24.05v-.13l2.75-.24c.2-.01.2-.12.26-.18l-.03-.99-2.84-.2.48-7.09c.33-.73.67-1.64.92-2.69V14.9l10.38 5.95L29 20l-9.34-5.28.01-.04c.18-.9.54-1.76.2-3.05a.8.8 0 00-.1-.24l7.36-6.96-.75-.64-8.24 7.77V8.82l-.05-.36-.19-.63c-.1-.27-.44-.6-.82-.77h-.92c-.4.17-.7.44-.8.71-.18.31-.13.5-.2.74v.95L11.16.91z', + viewBox: '0 0 32 32', + path: 'M11.16.91l-.9.54 4.9 10.19v.24h-.72v-.15c-.02-.53-.82-.74-1 0-.22.8-.16 1.42-.04 2L2.9 14.96 3.14 16l10.48-1.3.02.07c.06.3.15.45.24.62.11.11.24.16.38-.05.04-.14.07-.3.1-.49h.8l-.03 4.42c.04.33.09.6.14.84l-1.5 6.95.92.14 1.05-5.62.18.4.46 8.84c-.01.2.18.18.24.05v-.13l2.75-.24c.2-.01.2-.12.26-.18l-.03-.99-2.84-.2.48-7.09c.33-.73.67-1.64.92-2.69V14.9l10.38 5.95L29 20l-9.34-5.28.01-.04c.18-.9.54-1.76.2-3.05a.8.8 0 00-.1-.24l7.36-6.96-.75-.64-8.24 7.77V8.82l-.05-.36-.19-.63c-.1-.27-.44-.6-.82-.77h-.92c-.4.17-.7.44-.8.71-.18.31-.13.5-.2.74v.95L11.16.91z', }, 'f5_tiger': { - viewBox:'-3.5 -3 32 32', + viewBox: '-3.5 -3 32 32', w: 28, h: 28, path: 'M9.77 14.6s-.03-.78.16-1.74l.59-.98.05-.65.8-.72V7.17s-.05-1.28.15-3c0 0 .03-1.33.7-3.36v-1.2h.1V.83s.48 1.15.7 3.37c0 0 .17 1.15.14 3.02l.01 3.32.77.7.08.67.6 1s.14.69.14 1.7l2.47 1.35 2.24 1.22.03-1.82h-.1v-.14h.1l.01-.3s.01-.13.17-.14v-.4s.1-.28.23 0v.68l.1.16h.16v.14h-.26v3.18l.2.23v.68h-.63v-.75l-5.77.57s.06 1.08-.08 2.6l2.5 1.57-.02.94-2.75.17-.2 1.68h-1.8l-.2-1.68-2.77-.17v-.94l2.5-1.58s-.12-1.1-.09-2.62l-5.74-.55v.76H4.4v-.67l.23-.25v-3.15h-.27v-.17h.13l.13-.14v-.73s.1-.17.22 0v.46h.1s.07.02.07.12v.3h.13v.15h-.1v1.82l2.32-1.27z', @@ -521,7 +521,7 @@ let shapes = { path: 'M1169 1932 l-207 -2 -11 -45 c-12 -49 -13 -108 -2 -175 l7 -45 14 28 c13 27 15 27 74 22 34 -4 68 -9 76 -12 8 -4 39 -8 69 -9 29 -2 56 -5 58 -8 3 -3 0 -42 -6 -87 -6 -45 -11 -173 -11 -284 l0 -202 -77 -6 c-43 -4 -166 -14 -273 -22 -107 -8 -278 -21 -380 -30 -346 -28 -440 -35 -455 -35 -23 0 -48 -58 -41 -95 12 -62 19 -64 379 -116 182 -27 373 -53 422 -59 165 -19 155 -15 155 -74 1 -28 5 -70 9 -93 l8 -43 -46 0 c-25 0 -53 -4 -60 -9 -16 -10 -8 -13 71 -26 24 -4 44 -15 57 -33 l20 -27 23 28 c16 19 36 29 63 33 22 3 51 8 64 10 39 8 5 24 -53 24 l-48 0 6 33 c4 17 10 54 13 81 l6 48 64 -5 c35 -2 66 -7 68 -9 3 -2 11 -101 19 -219 20 -304 49 -412 121 -446 33 -16 37 -16 70 0 72 34 101 142 121 446 8 118 16 217 19 219 2 2 33 7 68 9 l64 5 6 -48 c3 -27 9 -64 13 -81 l7 -33 -49 0 c-58 0 -92 -16 -53 -24 13 -2 42 -7 64 -10 27 -4 47 -14 63 -33 l23 -28 20 27 c13 18 33 29 57 33 79 13 87 16 71 26 -7 5 -35 9 -60 9 l-46 0 8 43 c4 23 8 65 9 93 0 59 -10 55 155 74 50 6 240 32 422 59 360 52 367 54 379 116 7 37 -18 95 -41 95 -15 0 -109 7 -455 35 -102 9 -273 22 -380 30 -107 8 -230 18 -272 22 l-78 6 0 128 0 127 34 12 c31 10 46 26 46 51 0 14 -20 11 -36 -6 -31 -30 -44 -18 -44 38 0 30 -5 91 -11 136 -6 45 -9 84 -6 87 2 3 29 6 58 8 30 1 61 5 69 9 8 3 42 8 76 12 59 5 61 5 74 -22 14 -27 14 -26 24 47 6 52 6 91 -2 129 -6 29 -13 55 -14 56 -5 5 -464 10 -669 7z', }, 'b52': { - viewBox:'-7.5 -7 40 40', + viewBox: '-7.5 -7 40 40', w: 40, h: 40, path: 'M12.48 25.75l-.16-.67c-.1-.46-.11-.46-2.2-.1l-1.72.28c-.18 0-.12-1.2.07-1.43.1-.12.95-.86 1.9-1.65a18.3 18.3 0 001.72-1.54l-.15-1.43a79.1 79.1 0 01-.25-4.68l-.09-3.34-4.22 2.25c-1.45.8-2.92 1.55-4.42 2.25-.11 0-1.47.67-3.01 1.49-2.16 1.14-2.84 1.44-2.93 1.3-.18-.29-.15-1.59.05-1.83.1-.12.6-.52 1.13-.9l.95-.68v-.8c0-.9.14-1.38.31-1.42.14-.03.27.03.33.69.04.4 0 1.01.04 1.01.03 0 .69-.51 1.31-.99.95-.73 1.09-.88.9-1.02-.26-.19-.44-1.4-.3-2.1.09-.48.11-.5.7-.5.7 0 .78.13.79 1.28 0 .37.03.67.08.67.12 0 3.16-2.27 3.16-2.35 0-.05-.1-.15-.22-.24-.25-.18-.42-1.4-.3-2.1.1-.48.12-.5.73-.5.6 0 .62.02.71.5.05.26.06.76.02 1.1-.04.45-.02.6.1.53L9.6 7.35l1.93-1.39V4.71c0-2.41.5-4.64 1.06-4.64.55 0 1.05 2.23 1.05 4.64v1.25l1.94 1.39c1.06.76 2 1.43 2.1 1.48.1.07.13-.08.08-.52-.04-.35-.03-.85.02-1.12.09-.47.12-.48.72-.48s.63.01.71.49c.14.7-.03 1.92-.29 2.1-.12.09-.21.2-.21.24 0 .08 3.04 2.35 3.16 2.35.04 0 .08-.3.08-.67 0-1.15.08-1.28.78-1.28.6 0 .62.02.7.5.14.7-.03 1.91-.3 2.1-.18.14-.05.3.9 1.02.63.48 1.3 1.03 1.33 1.03.03 0 0-.64.04-1.03.06-.63.15-.76.28-.76.18.01.35.55.35 1.46v.81l.95.68c.52.38 1.02.78 1.12.9.2.24.24 1.54.06 1.83-.1.14-.78-.16-2.93-1.3a34.2 34.2 0 00-3.01-1.5c-.11 0-2.1-1-4.43-2.24l-4.22-2.25-.1 3.34a79.1 79.1 0 01-.39 6.11c0 .06.78.75 1.72 1.54.95.79 1.8 1.53 1.9 1.65.2.23.25 1.43.07 1.43l-1.72-.28c-2.09-.36-2.09-.36-2.19.1-.04.21-.13.52-.2.67-.1.28-.1.28-.18 0z', @@ -534,14 +534,14 @@ let shapes = { path: 'M21 28.3V30l-5-1.5-5 1.5v-1.5l4.4-3.3s-.8-4.6-.4-8.4c0 0 0-2-2.6-.5L1.3 21v-2l4.3-3-.3-.1v-2.4s0-.4.3-.4h.8s.4 0 .4.4v1.8l3.3-2.4-.3-.2v-2s0-.5.4-.5h.6s.4 0 .4.3v1.7L15 9.4s-.2-7.2 1.4-8.3c0 0 1.4 0 1.3 8.2l4.1 3v-2s0-.3.4-.3h.6s.4 0 .4.4v2.3l-.3.3 3.2 2.2v-1.9s0-.4.2-.4h.7s.4 0 .4.4v2.3l-.2.4 3.8 2.6v2.1L20 15.9s-1.8-.7-2.3 1c0 0 .3 4.9-.7 8.3z', }, 'rutan_veze': { - viewBox:'-2.8 -1 30 30', + viewBox: '-2.8 -1 30 30', w: 27, h: 27, path: 'M14.4 15.66l-1.16 1.13-.63.07-.01.18 1.87.1-1.87.08s-.09.53-.34.62c0 0-.25-.1-.35-.6l-1.85-.03 1.83-.17v-.2l-.58-.07-1.26-1.06-6.55 1.9L3 19s-.1-.61.21-1.9l-.32-.12v-.18l7.12-3.62 1.42-2.88s0-1.9.08-2.5l-4.02.03s-.18-.97.3-1l3.9-.03s.25-.9.5-1c0 0 .12-.13.58.98l3.79-.05s.4.05.26.98l-3.8.04s.1 1.18.03 2.59l1.4 2.74 7.32 3.43v.22l-.22.07s.35 1.69.14 1.8l-.4-1.25z', }, // Flying Pumpkin 'pumpkin': { - viewBox:'-4 -3 32 32', + viewBox: '-4 -3 32 32', w: 32, h: 32, accentMult: 0.6, @@ -568,7 +568,7 @@ let shapes = { }, // EMBRAER EMB-326 Xavante (ICAO M326) and AERMACCHI MB-339 (ICAO M339) 'm326': { - viewBox:'-4 -3 32 32', + viewBox: '-4 -3 32 32', w: 25, h: 25, accentMult: 0.8, @@ -577,7 +577,7 @@ let shapes = { }, // Dassault Mirage F1 (ICAO MRF1) 'miragef1': { - viewBox:'-4 -2 32 32', + viewBox: '-4 -2 32 32', w: 35, h: 35, accentMult: 0.8, @@ -589,7 +589,7 @@ let shapes = { h: 22, viewBox: "-2.5 -2.5 22 22", path: 'M 4.256,15.496 C 3.979,14.340 7.280,13.606 7.280,13.606 V 8.650 l -6,2 c -0.680,0 -1,-0.350 -1,-0.660 C 0.242,9.595 0.496,9.231 0.880,9.130 1.140,9 4.800,7 7.280,5.630 V 3 C 7.280,1.890 7.720,0.290 8.510,0.290 9.300,0.290 9.770,1.840 9.770,3 v 2.630 c 2.450,1.370 6.100,3.370 6.370,3.500 0.390,0.093 0.651,0.461 0.610,0.860 -0.050,0.310 -0.360,0.670 -1.050,0.670 l -5.930,-2 v 4.946 c 0,0 3.300,0.734 3.024,1.890 -0.331,1.384 -2.830,0.378 -4.254,0.378 -1.434,4.520e-4 -3.950,1.016 -4.284,-0.378 z', - size: [22,22] + size: [22, 22] }, 'ground_square': { w: 11, @@ -671,7 +671,7 @@ let shapes = { }, // Mac Donnell-Doulgas F-15 Eagle (ICAO F15) 'md_f15': { - viewBox:'-4 -3 32 32', + viewBox: '-4 -3 32 32', w: 28, h: 28, accentMult: 0.8, @@ -1211,41 +1211,41 @@ let TypeDescriptionIcons = { 'L4T-H': ['c130', 1.07], 'L4T': ['c130', 0.96], - 'L4J-H': ['b707' , 1], - 'L4J-M': ['b707' , 0.8], - 'L4J': ['b707' , 0.8], + 'L4J-H': ['b707', 1], + 'L4J-M': ['b707', 0.8], + 'L4J': ['b707', 0.8], }; let CategoryIcons = { - "A1" : ['cessna', 1],// < 7t + "A1": ['cessna', 1],// < 7t - "A2" : ['jet_swept', 0.94], // < 34t + "A2": ['jet_swept', 0.94], // < 34t - "A3" : ['airliner', 0.96], // < 136t + "A3": ['airliner', 0.96], // < 136t - "A4" : ['airliner', 1], // < 136t + "A4": ['airliner', 1], // < 136t - "A5" : ['heavy_2e', 0.92], // > 136t + "A5": ['heavy_2e', 0.92], // > 136t - "A6" : ['hi_perf', 0.94], + "A6": ['hi_perf', 0.94], - "A7" : ['helicopter', 1], + "A7": ['helicopter', 1], 'B1': ['glider', 1], - "B2" : ['balloon', 1], + "B2": ['balloon', 1], 'B4': _ulac, 'B6': ['uav', 1], - 'C0' : ['ground_unknown', 1], + 'C0': ['ground_unknown', 1], - 'C1' : ['ground_emergency', 1], + 'C1': ['ground_emergency', 1], - 'C2' : ['ground_service', 1], + 'C2': ['ground_service', 1], - 'C3' : ['ground_tower', 1], + 'C3': ['ground_tower', 1], }; function getBaseMarker(category, typeDesignator, typeDescription, wtc, addrtype, altitude, eastbound) { @@ -1256,6 +1256,10 @@ function getBaseMarker(category, typeDesignator, typeDescription, wtc, addrtype, return ['ground_square', 0.001]; } + if (addrtype == 'flarm') { + return ['glider', 1]; + } + if (halloween) { if ((typeDescription && typeDescription[0] == 'H') || typeDesignator == 'C172') return ['pumpkin', 1]; @@ -1352,7 +1356,7 @@ function svgShapeToSVG(shape, fillColor, strokeColor, strokeWidth, scale) { if (shape.path) { let path = shape.path; - if (! Array.isArray(path)) + if (!Array.isArray(path)) path = [path]; for (let i = 0; i < path.length; i++) { svg += ' " + (filterSpeed*666).toFixed(0) + " / " + derivedMach.toFixed(2) + " > " + filterSpeed.toFixed(2) + ") (" + (this.position_time - this.prev_time + 0.2).toFixed(1) + "s)"); + console.log(this.icao + " / " + this.name + " (" + this.dataSource + "): Implausible position filtered: " + this.bad_position[0] + ", " + this.bad_position[1] + " (kt/Mach " + (derivedMach * 666).toFixed(0) + " > " + (filterSpeed * 666).toFixed(0) + " / " + derivedMach.toFixed(2) + " > " + filterSpeed.toFixed(2) + ") (" + (this.position_time - this.prev_time + 0.2).toFixed(1) + "s)"); } this.position = this.prev_position; this.position_time = this.prev_time; @@ -459,7 +465,7 @@ PlaneObject.prototype.updateTrack = function(now, last, serverTrack, stale) { } return false; } else { - this.too_fast = Math.max(-5, this.too_fast-0.8); + this.too_fast = Math.max(-5, this.too_fast - 0.8); } if (this.request_rotation_from_track && this.prev_position) { @@ -568,7 +574,7 @@ PlaneObject.prototype.updateTrack = function(now, last, serverTrack, stale) { // and we then get a new position. if (verboseUpdateTrack) { - this.logSel("sec_elapsed: " + since_update.toFixed(1) + " alt_change: "+ alt_change.toFixed(0) + " derived_speed(kt/Mach): " + (distance_traveled/since_update*1.94384).toFixed(0) + " / " + (distance_traveled/since_update/343).toFixed(1) + " dist:" + distance_traveled.toFixed(0)); + this.logSel("sec_elapsed: " + since_update.toFixed(1) + " alt_change: " + alt_change.toFixed(0) + " derived_speed(kt/Mach): " + (distance_traveled / since_update * 1.94384).toFixed(0) + " / " + (distance_traveled / since_update / 343).toFixed(1) + " dist:" + distance_traveled.toFixed(0)); } let segments = [[projPrev]]; @@ -613,7 +619,8 @@ PlaneObject.prototype.updateTrack = function(now, last, serverTrack, stale) { for (let i in segments) { let points = segments[i]; - this.track_linesegs.push({ fixed: new ol.geom.LineString(points), + this.track_linesegs.push({ + fixed: new ol.geom.LineString(points), feature: null, estimated: estimated, estimatedFill: estimatedFill, @@ -647,17 +654,17 @@ PlaneObject.prototype.updateTrack = function(now, last, serverTrack, stale) { if (pTracks && pTracksInterval > 5) turn_density = 3; if ( since_update > 86 + !!pTracks * 90 || - (!on_ground && since_update > (100/turn_density)/track_change) || + (!on_ground && since_update > (100 / turn_density) / track_change) || (!on_ground && isNaN(track_change) && since_update > 8 + !!pTracks * 22) || - (on_ground && since_update > (120/turn_density)/track_change && distance_traveled > 20) || + (on_ground && since_update > (120 / turn_density) / track_change && distance_traveled > 20) || (on_ground && distance_traveled > 50 && since_update > 5) || debugAll ) { lastseg.fixed.appendCoordinate(projPrev); - this.history_size ++; + this.history_size++; - this.logSel("sec_elapsed: " + since_update.toFixed(1) + " " + (on_ground ? "ground" : "air") + " dist:" + distance_traveled.toFixed(0) + " track_change: "+ track_change.toFixed(1) + " derived_speed(kt/Mach): " + (distance_traveled/since_update*1.94384).toFixed(0) + " / " + (distance_traveled/since_update/343).toFixed(1)); + this.logSel("sec_elapsed: " + since_update.toFixed(1) + " " + (on_ground ? "ground" : "air") + " dist:" + distance_traveled.toFixed(0) + " track_change: " + track_change.toFixed(1) + " derived_speed(kt/Mach): " + (distance_traveled / since_update * 1.94384).toFixed(0) + " / " + (distance_traveled / since_update / 343).toFixed(1)); return this.updateTail(); } @@ -673,7 +680,7 @@ PlaneObject.prototype.getDataSourceNumber = function() { if (this.dataSource == "mlat") return 3; - if (this.dataSource == "uat" || (this.addrtype && this.addrtype.substring(0,4) == "adsr")) + if (this.dataSource == "uat" || (this.addrtype && this.addrtype.substring(0, 4) == "adsr")) return 2; // UAT if (this.dataSource == "tisb") @@ -711,7 +718,7 @@ PlaneObject.prototype.getMarkerColor = function(options) { // If we have not seen a recent position update, change color if ((this.dataSource == 'adsc' && this.seen_pos > 20 * 60) - || (!globeIndex && this.dataSource != 'adsc' && this.seen_pos > 15)) { + || (!globeIndex && this.dataSource != 'adsc' && this.seen_pos > 15)) { h += ColorByAlt.stale.h; s += ColorByAlt.stale.s; l += ColorByAlt.stale.l; @@ -721,7 +728,7 @@ PlaneObject.prototype.getMarkerColor = function(options) { } // If this marker is selected, change color - if (this.selected && !SelectedAllPlanes && !onlySelected){ + if (this.selected && !SelectedAllPlanes && !onlySelected) { h += ColorByAlt.selected.h; s += ColorByAlt.selected.s; l += ColorByAlt.selected.l; @@ -778,25 +785,25 @@ function altitudeColor(altitude) { // and interpolate the hue between those points let hpoints = ColorByAlt.air.h; h = hpoints[0].val; - for (let i = hpoints.length-1; i >= 0; --i) { + for (let i = hpoints.length - 1; i >= 0; --i) { if (altitude > hpoints[i].alt) { - if (i == hpoints.length-1) { + if (i == hpoints.length - 1) { h = hpoints[i].val; } else { - h = hpoints[i].val + (hpoints[i+1].val - hpoints[i].val) * (altitude - hpoints[i].alt) / (hpoints[i+1].alt - hpoints[i].alt) + h = hpoints[i].val + (hpoints[i + 1].val - hpoints[i].val) * (altitude - hpoints[i].alt) / (hpoints[i + 1].alt - hpoints[i].alt) } break; } } let lpoints = ColorByAlt.air.l; - lpoints = lpoints.length ? lpoints : [{h:0, val:lpoints}]; + lpoints = lpoints.length ? lpoints : [{ h: 0, val: lpoints }]; l = lpoints[0].val; - for (let i = lpoints.length-1; i >= 0; --i) { + for (let i = lpoints.length - 1; i >= 0; --i) { if (h > lpoints[i].h) { - if (i == lpoints.length-1) { + if (i == lpoints.length - 1) { l = lpoints[i].val; } else { - l = lpoints[i].val + (lpoints[i+1].val - lpoints[i].val) * (h - lpoints[i].h) / (lpoints[i+1].h - lpoints[i].h) + l = lpoints[i].val + (lpoints[i + 1].val - lpoints[i].val) * (h - lpoints[i].h) / (lpoints[i + 1].h - lpoints[i].h) } break; } @@ -823,7 +830,7 @@ function altitudeColor(altitude) { } PlaneObject.prototype.setMarkerRgb = function() { - let hsl = this.getMarkerColor({noRound: true}); + let hsl = this.getMarkerColor({ noRound: true }); let rgb = hslToRgb(hsl, 'array'); if (this.shape && this.shape.svg) rgb = [255, 255, 255]; @@ -835,10 +842,10 @@ PlaneObject.prototype.setMarkerRgb = function() { PlaneObject.prototype.updateIcon = function() { let fillColor = hslToRgb(this.getMarkerColor()); - let svgKey = fillColor + '!' + this.shape.name + '!' + this.strokeWidth; + let svgKey = fillColor + '!' + this.shape.name + '!' + this.strokeWidth; let labelText = null; - if ( g.enableLabels && (!multiSelect || (multiSelect && this.selected)) && + if (g.enableLabels && (!multiSelect || (multiSelect && this.selected)) && ( (g.zoomLvl >= labelZoom && this.altitude != "ground" && this.dataSource != "ais") || (g.zoomLvl >= labelZoomGround - 2 && this.speed > 5 && !this.fakeHex) @@ -849,11 +856,11 @@ PlaneObject.prototype.updateIcon = function() { ) { let callsign = ""; if (this.flight && this.flight.trim() && !(this.dataSource == "ais" && !g.extendedLabels)) - callsign = this.flight.trim(); + callsign = this.flight.trim(); else if (this.registration) - callsign = 'reg: ' + this.registration; + callsign = 'reg: ' + this.registration; else - callsign = 'hex: ' + this.icao; + callsign = 'hex: ' + this.icao; if ((useRouteAPI || this.dataSource == "ais") && this.routeString) { if (0 && g.extendedLabels) { callsign += ' - ' + this.routeString; @@ -862,7 +869,7 @@ PlaneObject.prototype.updateIcon = function() { } } - const unknown = NBSP+NBSP+"?"+NBSP+NBSP; + const unknown = NBSP + NBSP + "?" + NBSP + NBSP; let alt; if (labelsGeom) { @@ -871,7 +878,7 @@ PlaneObject.prototype.updateIcon = function() { alt = adjust_baro_alt(this.altitude); } let altString = (alt == null) ? unknown : format_altitude_brief(alt, this.vert_rate, DisplayUnits, showLabelUnits); - let speedString = (this.speed == null) ? (NBSP+'?'+NBSP) : format_speed_brief(this.speed, DisplayUnits, showLabelUnits).padStart(3, NBSP); + let speedString = (this.speed == null) ? (NBSP + '?' + NBSP) : format_speed_brief(this.speed, DisplayUnits, showLabelUnits).padStart(3, NBSP); labelText = ""; if (atcStyle) { @@ -979,8 +986,8 @@ PlaneObject.prototype.updateIcon = function() { textAlign: 'left', textBaseline: labels_top ? 'bottom' : 'top', font: labelFont, - offsetX: (this.shape.w *0.5*0.74*this.scale), - offsetY: labels_top ? (this.shape.w *-0.3*0.74*this.scale) : (this.shape.w *0.5*0.74*this.scale), + offsetX: (this.shape.w * 0.5 * 0.74 * this.scale), + offsetY: labels_top ? (this.shape.w * -0.3 * 0.74 * this.scale) : (this.shape.w * 0.5 * 0.74 * this.scale), padding: [1, 0, -1, 2], }), zIndex: this.zIndex, @@ -1037,7 +1044,7 @@ PlaneObject.prototype.processTrace = function() { } if (!now) - now = new Date().getTime()/1000; + now = new Date().getTime() / 1000; if (showTrace || replay) this.setNull(); @@ -1065,7 +1072,7 @@ PlaneObject.prototype.processTrace = function() { if (this.fullTrace && this.recentTrace && this.fullTrace.length > 0 && this.recentTrace.length > 0) { let t1 = this.fullTrace.trace; let t2 = this.recentTrace.trace; - let end1 = t1[t1.length-1][0]; + let end1 = t1[t1.length - 1][0]; let start2 = t2[0][0]; if (end1 < start2) console.log("Insufficient recent trace overlap!"); @@ -1275,7 +1282,8 @@ PlaneObject.prototype.processTrace = function() { if (this.track_linesegs.length > 0 && this.position) { const proj = ol.proj.fromLonLat(this.position); this.track_linesegs[this.track_linesegs.length - 1].fixed.appendCoordinate(proj); - this.track_linesegs.push({ fixed: new ol.geom.LineString([proj]), + this.track_linesegs.push({ + fixed: new ol.geom.LineString([proj]), feature: null, estimated: false, ground: (this.altitude == "ground"), @@ -1290,7 +1298,7 @@ PlaneObject.prototype.processTrace = function() { dataSource: this.dataSource, }); } - now = new Date().getTime()/1000; + now = new Date().getTime() / 1000; } if (showTrace) { this.seen = 0; @@ -1315,8 +1323,7 @@ PlaneObject.prototype.processTrace = function() { && this.position && !(traceOpts.noFollow && traceOpts.noFollow + 3 > new Date().getTime() / 1000) && !inView(this.position, myExtent(OLMap.getView().calculateExtent(size))) - && !inView(firstPos, myExtent(OLMap.getView().calculateExtent(size)))) - { + && !inView(firstPos, myExtent(OLMap.getView().calculateExtent(size)))) { OLMap.getView().setCenter(ol.proj.fromLonLat(this.position)); } @@ -1389,27 +1396,27 @@ PlaneObject.prototype.updateData = function(now, last, data, init) { // [.hex, .alt_baro, .gs, .track, .lat, .lon, .seen_pos, "mlat"/"tisb"/.type , .flight, .messages] // 0 1 2 3 4 5 6 7 8 9 // this format is only valid for chunk loading the history - const alt_baro = isArray? data[1] : data.alt_baro; - let gs = isArray? data[2] : data.gs; - const track = isArray? data[3] : data.track; - const lat = isArray? data[4] : data.lat; - const lon = isArray? data[5] : data.lon; - let seen = isArray? data[6] : data.seen; - let seen_pos = isArray? data[6] : data.seen_pos; + const alt_baro = isArray ? data[1] : data.alt_baro; + let gs = isArray ? data[2] : data.gs; + const track = isArray ? data[3] : data.track; + const lat = isArray ? data[4] : data.lat; + const lon = isArray ? data[5] : data.lon; + let seen = isArray ? data[6] : data.seen; + let seen_pos = isArray ? data[6] : data.seen_pos; seen = (seen == null) ? 5 : seen; seen_pos = (seen_pos == null && lat) ? 5 : seen_pos; - let type = isArray? data[7] : data.type; + let type = isArray ? data[7] : data.type; if (!isArray && data.mlat != null && data.mlat.indexOf("lat") >= 0) type = 'mlat'; const mlat = (type == 'mlat'); - const tisb = (type && type.substring(0,4) == "tisb"); - const flight = isArray? data[8] : data.flight; + const tisb = (type && type.substring(0, 4) == "tisb"); + const flight = isArray ? data[8] : data.flight; this.last_message_time = now - seen; // remember last known position even if stale // do not show or process mlat positions when filtered out. if (lat != null && lon != null && !(noMLAT && mlat)) { - this.position = [lon, lat]; + this.position = [lon, lat]; this.position_time = now - seen_pos; } @@ -1450,10 +1457,10 @@ PlaneObject.prototype.updateData = function(now, last, data, init) { this.bad_alt = newAlt; this.bad_altTime = now; if (debugPosFilter) { - console.log((now%1000).toFixed(0) + ': AltFilter: ' + this.icao + console.log((now % 1000).toFixed(0) + ': AltFilter: ' + this.icao + ' oldAlt: ' + this.altitude + ' newAlt: ' + newAlt - + ' elapsed: ' + (now-this.altitudeTime).toFixed(0) ); + + ' elapsed: ' + (now - this.altitudeTime).toFixed(0)); jumpTo = this.icao; } } else { @@ -1500,9 +1507,9 @@ PlaneObject.prototype.updateData = function(now, last, data, init) { this.dataSource = "uat"; } else if (tisb) { this.dataSource = "tisb"; - } else if (type && type.substring(0,4) == "adsr") { + } else if (type && type.substring(0, 4) == "adsr") { this.dataSource = "adsr"; - } else if ((lat != null && type == null) || (type && (type.substring(0,4) == "adsb"))) { + } else if ((lat != null && type == null) || (type && (type.substring(0, 4) == "adsb"))) { this.dataSource = "adsb"; } else if (type == 'adsc') { this.dataSource = "adsc"; @@ -1529,14 +1536,14 @@ PlaneObject.prototype.updateData = function(now, last, data, init) { if (elapsed > 0) { let messageRate = 0; if (!this.uat) { - messageRate = (data.messages - this.msgs1090)/(now - this.last); + messageRate = (data.messages - this.msgs1090) / (now - this.last); this.msgs1090 = data.messages; } else { - messageRate = (data.messages - this.msgs978)/(uat_now - uat_last); + messageRate = (data.messages - this.msgs978) / (uat_now - uat_last); this.msgs978 = data.messages; } if (elapsed > 60) messageRate = 0; - this.messageRate = (messageRate + this.messageRateOld)/2; + this.messageRate = (messageRate + this.messageRateOld) / 2; this.messageRateOld = messageRate; } this.messages = data.messages; @@ -1607,7 +1614,7 @@ PlaneObject.prototype.updateData = function(now, last, data, init) { // Pick a selected altitude if (data.nav_altitude_fms != null) { this.nav_altitude = data.nav_altitude_fms; - } else if (data.nav_altitude_mcp != null){ + } else if (data.nav_altitude_mcp != null) { this.nav_altitude = data.nav_altitude_mcp; } else { this.nav_altitude = null; @@ -1616,7 +1623,7 @@ PlaneObject.prototype.updateData = function(now, last, data, init) { // Pick vertical rate from either baro or geom rate if (data.baro_rate != null) { this.vert_rate = data.baro_rate; - } else if (data.geom_rate != null ) { + } else if (data.geom_rate != null) { this.vert_rate = data.geom_rate; } else if (data.vert_rate != null) { // legacy from mut v 1.15 @@ -1660,6 +1667,14 @@ PlaneObject.prototype.updateData = function(now, last, data, init) { this.last = now; this.updatePositionData(now, last, data, init); + const flarm = !isArray && (data.source == "flarm" || data.type == "flarm" || data.flarm != null); + if (flarm) { + this.dataSource = "flarm"; + this.source = "flarm"; + this.flarm = data.flarm || null; + } else if (mlat && noMLAT) { + this.dataSource = "modeS"; + } return; }; @@ -1727,7 +1742,7 @@ PlaneObject.prototype.updateMarker = function(moved) { if (icaoType == null && this.squawk == 7777) icaoType = 'TWR'; let baseMarkerKey = this.category + "_" - + this.typeDescription + "_" + this.wtc + "_" + icaoType + '_' + (this.altitude == "ground") + eastbound; + + this.typeDescription + "_" + this.wtc + "_" + icaoType + '_' + (this.altitude == "ground") + eastbound; if (!this.shape || this.baseMarkerKey != baseMarkerKey) { this.baseMarkerKey = baseMarkerKey; @@ -1791,7 +1806,7 @@ PlaneObject.prototype.updateMarker = function(moved) { // return the styling of the lines based on altitude -function altitudeLines (segment) { +function altitudeLines(segment) { let colorArr = altitudeColor(segment.altitude); if (segment.estimated) colorArr = [colorArr[0], colorArr[1], colorArr[2] * 0.8]; @@ -1817,7 +1832,7 @@ function altitudeLines (segment) { let cap = 'square'; if (!debugTracks) { if (modeS) { - lineStyleCache[lineKey] = [ + lineStyleCache[lineKey] = [ new ol.style.Style({}), new ol.style.Style({ image: new ol.style.Circle({ @@ -1832,7 +1847,7 @@ function altitudeLines (segment) { }) ]; } else if (segment.estimated && !noVanish) { - lineStyleCache[lineKey] = [ + lineStyleCache[lineKey] = [ new ol.style.Style({ stroke: new ol.style.Stroke({ color: 'black', @@ -1853,9 +1868,9 @@ function altitudeLines (segment) { }) ]; } else if (segment.estimated && pTracks) { - lineStyleCache[lineKey] = new ol.style.Style({}); + lineStyleCache[lineKey] = new ol.style.Style({}); } else { - lineStyleCache[lineKey] = new ol.style.Style({ + lineStyleCache[lineKey] = new ol.style.Style({ stroke: new ol.style.Stroke({ color: color, width: 2 * newWidth * multiplier, @@ -1929,7 +1944,7 @@ PlaneObject.prototype.updateLines = function() { // create any missing fixed line features - for (let i = this.track_linesegs.length-1; i >= 0; i--) { + for (let i = this.track_linesegs.length - 1; i >= 0; i--) { let seg = this.track_linesegs[i]; if (seg.feature && (!trackLabels || seg.label)) break; @@ -1951,11 +1966,11 @@ PlaneObject.prototype.updateLines = function() { seg.label = true; } else if ( trackLabels || - ((i == 0 || i == this.track_linesegs.length-1 ||seg.leg) && showTrace && g.enableLabels) + ((i == 0 || i == this.track_linesegs.length - 1 || seg.leg) && showTrace && g.enableLabels) ) { // 0 vertical rate to avoid arrow let altString; - if(seg.alt_real == "ground") { + if (seg.alt_real == "ground") { altString = "Ground"; } else { let alt; @@ -1966,12 +1981,12 @@ PlaneObject.prototype.updateLines = function() { } if (alt == null) { - altString = (NBSP+'?'+NBSP); + altString = (NBSP + '?' + NBSP); } else { altString = format_altitude_brief(alt, 0, DisplayUnits, showLabelUnits); } } - const speedString = (seg.speed == null) ? (NBSP+'?'+NBSP) : format_speed_brief(seg.speed, DisplayUnits, showLabelUnits).padStart(3, NBSP); + const speedString = (seg.speed == null) ? (NBSP + '?' + NBSP) : format_speed_brief(seg.speed, DisplayUnits, showLabelUnits).padStart(3, NBSP); seg.label = new ol.Feature(new ol.geom.Point(seg.fixed.getFirstCoordinate())); let timestamp1; @@ -2005,7 +2020,7 @@ PlaneObject.prototype.updateLines = function() { } if (traces_high_res || debugTracks) { - timestamp2 += '.' + (Math.floor((seg.ts*10)) % 10); + timestamp2 += '.' + (Math.floor((seg.ts * 10)) % 10); } if (showTrace && !utcTimesHistoric) { @@ -2021,7 +2036,7 @@ PlaneObject.prototype.updateLines = function() { //+ NBSP + format_track_arrow(seg.track) + timestamp1 + timestamp2; if (seg.rId && show_rId) { - text += "\n" + seg.rId.substring(0,9); //+ "\n" + seg.rId.substring(9,18); + text += "\n" + seg.rId.substring(0, 9); //+ "\n" + seg.rId.substring(9,18); } if (showTrace && !trackLabels) @@ -2030,11 +2045,11 @@ PlaneObject.prototype.updateLines = function() { let fill = labelFill; let zIndex = -i - 50 * (seg.alt_real == null); if (seg.leg == 'start') { - fill = new ol.style.Fill({color: '#88CC88' }); + fill = new ol.style.Fill({ color: '#88CC88' }); zIndex += 123499; } if (seg.leg == 'end') { - fill = new ol.style.Fill({color: '#8888CC' }); + fill = new ol.style.Fill({ color: '#8888CC' }); zIndex += 123455; } const otherDiag = seg.track != null && ((seg.track > 270 && seg.track < 360) || (seg.track > 90 && seg.track < 180)); @@ -2145,7 +2160,7 @@ PlaneObject.prototype.destroyTrace = function() { } } -PlaneObject.prototype.makeTR = function (trTemplate) { +PlaneObject.prototype.makeTR = function(trTemplate) { this.trCache = []; this.bgColorCache = undefined; @@ -2157,10 +2172,10 @@ PlaneObject.prototype.makeTR = function (trTemplate) { return; } - if(!mapIsVisible) { - selectPlaneByHex(this.icao, {follow: true}); + if (!mapIsVisible) { + selectPlaneByHex(this.icao, { follow: true }); } else { - selectPlaneByHex(this.icao, {follow: false}); + selectPlaneByHex(this.icao, { follow: false }); } evt.preventDefault(); }; @@ -2169,17 +2184,17 @@ PlaneObject.prototype.makeTR = function (trTemplate) { if (!globeIndex) { this.dblclickListener = (evt) => { - if(!mapIsVisible) { + if (!mapIsVisible) { showMap(); } - selectPlaneByHex(this.icao, {follow: true}); + selectPlaneByHex(this.icao, { follow: true }); evt.preventDefault(); }; this.tr.addEventListener('dblclick', this.dblclickListener); } }; -PlaneObject.prototype.destroyTR = function (trTemplate) { +PlaneObject.prototype.destroyTR = function(trTemplate) { if (this.tr == null) return; @@ -2216,9 +2231,9 @@ function calcAltitudeRounded(altitude) { } else if (altitude == "ground") { return altitude; } else if (altitude > 8000 || heatmap) { - return (altitude/500).toFixed(0)*500; + return (altitude / 500).toFixed(0) * 500; } else { - return (altitude/125).toFixed(0)*125; + return (altitude / 125).toFixed(0) * 125; } }; @@ -2229,7 +2244,7 @@ PlaneObject.prototype.drawRedDot = function(bad_position) { selectPlaneByHex(this.icao, false); } let badFeat = new ol.Feature(new ol.geom.Point(ol.proj.fromLonLat(bad_position))); - badFeat.setStyle(this.dataSource == "mlat" ? badDotMlat : badDot); + badFeat.setStyle(this.dataSource == "mlat" ? badDotMlat : badDot); this.trail_features.addFeature(badFeat); let geom = new ol.geom.LineString([ol.proj.fromLonLat(this.prev_position), ol.proj.fromLonLat(bad_position)]); let lineFeat = new ol.Feature(geom); @@ -2238,11 +2253,11 @@ PlaneObject.prototype.drawRedDot = function(bad_position) { }; function hexToHSL(hex) { - let r = +('0x'+ hex[1] + hex[2]) / 255; - let g = +('0x'+ hex[3] + hex[4]) / 255; - let b = +('0x'+ hex[5] + hex[6]) / 255; - let cmin = Math.min(r,g,b); - let cmax = Math.max(r,g,b); + let r = +('0x' + hex[1] + hex[2]) / 255; + let g = +('0x' + hex[3] + hex[4]) / 255; + let b = +('0x' + hex[5] + hex[6]) / 255; + let cmin = Math.min(r, g, b); + let cmax = Math.max(r, g, b); let delta = cmax - cmin; let h = 0, s = 0, l = 0; if (delta == 0) @@ -2279,7 +2294,7 @@ function hexToHSL(hex) { * @param {number} l The lightness * @return {Array} The RGB representation */ -function hslToRgb(arr, opacity){ +function hslToRgb(arr, opacity) { let h = arr[0]; let s = arr[1]; let l = arr[2]; @@ -2289,40 +2304,40 @@ function hslToRgb(arr, opacity){ s *= 0.01; l *= 0.01; - if(s == 0){ + if (s == 0) { r = g = b = l; // achromatic - }else{ - let hue2rgb = function hue2rgb(p, q, t){ - if(t < 0) t += 1; - if(t > 1) t -= 1; - if(t < 1/6) return p + (q - p) * 6 * t; - if(t < 1/2) return q; - if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; + } else { + let hue2rgb = function hue2rgb(p, q, t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; return p; } let q = l < 0.5 ? l * (1 + s) : l + s - l * s; let p = 2 * l - q; - r = hue2rgb(p, q, h + 1/3); + r = hue2rgb(p, q, h + 1 / 3); g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1/3); + b = hue2rgb(p, q, h - 1 / 3); } if (opacity == 'array') - return [ Math.round(r * 255), Math.round(g * 255), Math.round(b * 255) ]; + return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; if (opacity != null) - return 'rgba(' + Math.round(r * 255) + ', ' + Math.round(g * 255) + ', ' + Math.round(b * 255) + ', ' + opacity + ')'; + return 'rgba(' + Math.round(r * 255) + ', ' + Math.round(g * 255) + ', ' + Math.round(b * 255) + ', ' + opacity + ')'; else - return 'rgb(' + Math.round(r * 255) + ', ' + Math.round(g * 255) + ', ' + Math.round(b * 255) + ')'; + return 'rgb(' + Math.round(r * 255) + ', ' + Math.round(g * 255) + ', ' + Math.round(b * 255) + ')'; } PlaneObject.prototype.altBad = function(newAlt, oldAlt, oldTime, data) { let max_fpm = 12000; if (data.geom_rate != null) - max_fpm = 1.3*Math.abs(data.goem_rate) + 5000; + max_fpm = 1.3 * Math.abs(data.goem_rate) + 5000; else if (data.baro_rate != null) - max_fpm = 1.3*Math.abs(data.baro_rate) + 5000; + max_fpm = 1.3 * Math.abs(data.baro_rate) + 5000; const delta = Math.abs(newAlt - oldAlt); const fpm = (delta < 800) ? 0 : (60 * delta / (now - oldTime + 2)); @@ -2553,7 +2568,7 @@ PlaneObject.prototype.updateTraceData = function(state, _now) { if (data.nav_altitude_fms != null) { this.nav_altitude = data.nav_altitude_fms; - } else if (data.nav_altitude_mcp != null){ + } else if (data.nav_altitude_mcp != null) { this.nav_altitude = data.nav_altitude_mcp; } else { this.nav_altitude = null; @@ -2561,9 +2576,9 @@ PlaneObject.prototype.updateTraceData = function(state, _now) { } if (!this.addrtype) { this.dataSource = "unknown"; - } else if (this.addrtype.substring(0,4) == "adsb") { + } else if (this.addrtype.substring(0, 4) == "adsb") { this.dataSource = "adsb"; - } else if (this.addrtype.substring(0,4) == "adsr") { + } else if (this.addrtype.substring(0, 4) == "adsr") { this.dataSource = "adsr"; } else if (this.addrtype == "mlat") { this.dataSource = "mlat"; @@ -2571,7 +2586,7 @@ PlaneObject.prototype.updateTraceData = function(state, _now) { this.dataSource = "modeS"; } else if (this.addrtype == 'mode_s') { this.dataSource = "modeS"; - } else if (this.addrtype.substring(0,4) == "tisb") { + } else if (this.addrtype.substring(0, 4) == "tisb") { this.dataSource = "tisb"; } else if (this.addrtype == 'adsc') { this.dataSource = "adsc"; @@ -2602,17 +2617,17 @@ function makeCircle(points, greyskull) { // adapted from https://github.com/seangrogan/great_circle_calculator function midpoint(from, to) { - let lon1 = from[0] * (Math.PI/180); - let lat1 = from[1] * (Math.PI/180); - let lon2 = to[0] * (Math.PI/180); - let lat2 = to[1] * (Math.PI/180); + let lon1 = from[0] * (Math.PI / 180); + let lat1 = from[1] * (Math.PI / 180); + let lon2 = to[0] * (Math.PI / 180); + let lat2 = to[1] * (Math.PI / 180); let b_x = Math.cos(lat2) * Math.cos(lon2 - lon1); let b_y = Math.cos(lat2) * Math.sin(lon2 - lon1); let lat3 = Math.atan2(Math.sin(lat1) + Math.sin(lat2), Math.sqrt((Math.cos(lat1) + b_x) * (Math.cos(lat1) + b_x) + b_y * b_y)); let lon3 = lon1 + Math.atan2(b_y, Math.cos(lat1) + b_x); - lat3 /= (Math.PI/180); - lon3 /= (Math.PI/180); + lat3 /= (Math.PI / 180); + lon3 /= (Math.PI / 180); lon3 = (lon3 + 540) % 360 - 180; return [lon3, lat3]; } @@ -2659,7 +2674,8 @@ PlaneObject.prototype.cross180 = function(on_ground, is_leg) { seg1.unshift(seg1[0]); - this.track_linesegs.push({ fixed: new ol.geom.LineString([seg1.shift()]), + this.track_linesegs.push({ + fixed: new ol.geom.LineString([seg1.shift()]), feature: null, estimated: true, ground: (this.prev_alt == "ground"), @@ -2674,7 +2690,8 @@ PlaneObject.prototype.cross180 = function(on_ground, is_leg) { rId: this.prev_rId, }); - this.track_linesegs.push({ fixed: new ol.geom.LineString(seg1), + this.track_linesegs.push({ + fixed: new ol.geom.LineString(seg1), feature: null, estimated: true, ground: (this.prev_alt == "ground"), @@ -2689,7 +2706,8 @@ PlaneObject.prototype.cross180 = function(on_ground, is_leg) { rId: this.prev_rId, }); - this.track_linesegs.push({ fixed: new ol.geom.LineString(seg2), + this.track_linesegs.push({ + fixed: new ol.geom.LineString(seg2), feature: null, estimated: true, ground: (this.prev_alt == "ground"), @@ -2707,7 +2725,7 @@ PlaneObject.prototype.cross180 = function(on_ground, is_leg) { PlaneObject.prototype.dataChanged = function() { this.refreshTR = 0; - if (useRouteAPI){ + if (useRouteAPI) { this.routeCheck(); } } @@ -2759,7 +2777,7 @@ PlaneObject.prototype.checkVisible = function() { }; PlaneObject.prototype.setTypeData = function() { - if (g.type_cache == null || !this.icaoType || this.icaoType == this.icaoTypeCache) + if (g.type_cache == null || !this.icaoType || this.icaoType == this.icaoTypeCache) return; this.updateMarker(); this.icaoTypeCache = this.icaoType; @@ -2890,7 +2908,7 @@ function normalized_callsign(flight) { let alpha = match[1]; let num = match[2]; let alpha2 = match[3]; - while(num[0] == '0' && num.length > 1) { + while (num[0] == '0' && num.length > 1) { num = num.slice(1); } return alpha + num + alpha2; @@ -2916,7 +2934,7 @@ PlaneObject.prototype.routeCheck = function() { // already checking return; } - let currentTime = Date.now()/1000; + let currentTime = Date.now() / 1000; // if we don't have a route cached or if the cache is older than 6h, do a lookup const route = g.route_cache[currentName]; if (!route || currentTime > route.tarNextUpdate) { @@ -2924,7 +2942,7 @@ PlaneObject.prototype.routeCheck = function() { console.log("routeAPI: updating ", currentName, localTime(new Date(currentTime)), localTime(new Date(route.tarNextUpdate))); } // we have all the pieces that allow us to lookup a route - let route_check = { 'callsign': currentName, icao: this.icao}; + let route_check = { 'callsign': currentName, icao: this.icao }; if (!this.position) { // no lookup (for now) return; @@ -2995,14 +3013,14 @@ PlaneObject.prototype.routeCheck = function() { } function routeDoLookup() { - const currentTime = Date.now()/1000; + const currentTime = Date.now() / 1000; // JavaScript doesn't interrupt running functions - so this should be safe to do if (g.route_check_in_flight) { return; } if (!g.route_check_checking) { // grab up to the first 100 callsigns and leave the rest for later - g.route_check_checking = Object.values(g.route_check_todo).slice(0,100); + g.route_check_checking = Object.values(g.route_check_todo).slice(0, 100); } if (g.route_check_checking.length < 1) { g.route_check_checking = null; @@ -3046,7 +3064,7 @@ function routeDoLookup() { data: requestBody, }) .done((routes) => { - const currentTime = Date.now()/1000; + const currentTime = Date.now() / 1000; g.route_check_in_flight = false; if (debugRoute) { console.log(`${currentTime}: got routes:`, routes); @@ -3080,7 +3098,7 @@ function routeDoLookup() { }) .fail((jqxhr, status, error) => { g.route_check_in_flight = false; - g.route_next_lookup = Date.now()/1000 + 15; + g.route_next_lookup = Date.now() / 1000 + 15; console.log('route API request error, delaying next request by 15 seconds.'); }); } @@ -3089,11 +3107,11 @@ PlaneObject.prototype.setFlight = function(flight) { if (flight == null) { if (now - this.flightTs > 10 * 60) { this.flight = null; - this.name ='no callsign'; + this.name = 'no callsign'; } } else if (flight == "@@@@@@@@") { this.flight = null; - this.name ='no callsign'; + this.name = 'no callsign'; } else { this.flight = `${flight}`; this.name = this.flight.trim() || 'empty callsign'; diff --git a/html/script.js b/html/script.js index e8a63b01a..7e0497be4 100644 --- a/html/script.js +++ b/html/script.js @@ -5,7 +5,7 @@ "use strict"; -g.planes = {}; +g.planes = {}; g.planesOrdered = []; g.route_cache = []; g.route_check_todo = {}; @@ -40,7 +40,7 @@ let iconCache = {}; let addToIconCache = []; let lineStyleCache = {}; let replayPlanes = {}; -let PlaneFilter = {}; +let PlaneFilter = {}; let SelectedPlane = null; let sp = null; let SelPlanes = []; @@ -86,7 +86,7 @@ let firstFetch = true; let debugCounter = 0; let pathName = window.location.pathname.replace(/\/+/, '/') || "/"; let sourcesFilter = null; -let sources = ['adsb', ['uat', 'adsr'], 'mlat', 'tisb', 'modeS', 'other', 'adsc', 'ais']; +let sources = ['adsb', ['uat', 'adsr'], 'mlat', 'tisb', 'modeS', 'other', 'adsc', 'ais', 'flarm']; let flagFilter = null; let flagFilterValues = ['military', 'pia', 'ladd']; let showTrace = false; @@ -166,6 +166,8 @@ let uat_now = 0; let uat_last = 0; let FetchPending = []; let FetchPendingUAT = null; +let FetchPendingFLARM = null; +let flarm_data = null; let MessageCountHistory = []; let MessageRate = 0; @@ -184,6 +186,50 @@ let badDotMlat; let showingReplayBar = false; +function normalizeFlarmAircraft(ac) { + var hex = '~F' + ac.icao24; + var normalized = { + hex: hex, + type: 'flarm', + source: 'flarm', + lat: ac.latitude, + lon: ac.longitude, + alt_geom: ac.altitude != null ? Math.round(ac.altitude * 3.28084) : null, + gs: ac.speed != null ? ac.speed * 1.94384 : null, + geom_rate: ac.climb_rate != null ? Math.round(ac.climb_rate * 196.85) : null, + track: ac.track, + track_rate: ac.turn_rate, + seen: 0, + seen_pos: 0, + category: ac.aircraft_type, + flarm: { + icao24: ac.icao24, + icao24_num: ac.icao24_num, + message_type: ac.message_type, + radio_identifier_type: ac.radio_identifier_type, + urgency: ac.urgency, + icaoExt: ac.icaoExt, + message_type_ext: ac.message_type_ext, + is_stealth: ac.is_stealth, + is_no_track: ac.is_no_track, + version: ac.version, + version_max: ac.version_max, + time: ac.time, + aircraft_type: ac.aircraft_type, + turn_rate: ac.turn_rate, + movement_mode: ac.movement_mode, + acc_pos_hor: ac.acc_pos_hor, + acc_pos_ver: ac.acc_pos_ver, + acc_vel: ac.acc_vel, + sil: ac.sil, + sda: ac.sda, + nic: ac.nic, + } + }; + + return normalized; +} + function processAircraft(ac, init, uat) { const isArray = Array.isArray(ac); let hex = isArray ? ac[0] : ac.hex; @@ -203,7 +249,7 @@ function processAircraft(ac, init, uat) { return; } - if (uat && uatNoTISB && ac.type && ac.type.substring(0,4) == "tisb") { + if (uat && uatNoTISB && ac.type && ac.type.substring(0, 4) == "tisb") { // drop non ADS-B planes from UAT (TIS-B) return; } @@ -339,7 +385,7 @@ function processReceiverUpdate(data, init) { } // Loop through all the planes in the data packet - for (let j=0; j < data.aircraft.length; j++) { + for (let j = 0; j < data.aircraft.length; j++) { processAircraft(data.aircraft[j], init, uat); } } @@ -365,7 +411,7 @@ function fetchFail(jqxhr, status, error) { console.log(error); if (status != 429 && status != '429') { jQuery("#update_error_detail").text(errText); - jQuery("#update_error").css('display','block'); + jQuery("#update_error").css('display', 'block'); StaleReceiverCount++; } else { if (C429++ > 16) { @@ -389,12 +435,12 @@ function fetchDone(data) { lastRequestSize = arr.byteLength; let res; try { - res = zstdDecode( arr, 0 ); + res = zstdDecode(arr, 0); } catch (e) { let errText = e.message; console.log(errText); jQuery("#update_error_detail").text(errText); - jQuery("#update_error").css('display','block'); + jQuery("#update_error").css('display', 'block'); return; } let arrayBuffer = res.buffer @@ -412,7 +458,7 @@ function fetchDone(data) { let error = data.error; if (error) { jQuery("#update_error_detail").text(error); - jQuery("#update_error").css('display','block'); + jQuery("#update_error").css('display', 'block'); StaleReceiverCount++; } return; @@ -467,11 +513,11 @@ function fetchDone(data) { StaleReceiverCount++; if (StaleReceiverCount > 5) { jQuery("#update_error_detail").text("The data from the server hasn't been updated in a while."); - jQuery("#update_error").css('display','block'); + jQuery("#update_error").css('display', 'block'); } - } else if (StaleReceiverCount > 0){ + } else if (StaleReceiverCount > 0) { StaleReceiverCount = 0; - jQuery("#update_error").css('display','none'); + jQuery("#update_error").css('display', 'none'); } } catch (e) { console.error(e); @@ -583,8 +629,10 @@ function fetchData(options) { if (fetchCalls == 1) { console.time("first fetch()"); }; if (enable_uat) { - FetchPendingUAT = jQuery.ajax({ url: 'chunks/978.json', - dataType: 'json' }); + FetchPendingUAT = jQuery.ajax({ + url: 'chunks/978.json', + dataType: 'json' + }); FetchPendingUAT.done(function(data) { uat_data = data; @@ -649,7 +697,7 @@ function fetchData(options) { url += SelPlanes[k].icao + ',' } url = url.slice(0, -1); // remove trailing comma - } else { + } else { url += '&box=' + lastRequestBox; if (SelPlanes.length > 0) { @@ -673,7 +721,7 @@ function fetchData(options) { globeIndexNow[k] = null; } } - indexes.sort(function(x,y) { + indexes.sort(function(x, y) { if (!globeIndexNow[x] && !globeIndexNow[y]) { return globeIndexDist[x] - globeIndexDist[y]; } @@ -699,6 +747,7 @@ function fetchData(options) { ac_url.push('data/aircraft' + suffix); } + pendingFetches += ac_url.length; fetchCounter += ac_url.length; @@ -901,7 +950,7 @@ function earlyInitPage() { // things that can run without receiver json being known if (audio_url) { if (!Array.isArray(audio_url)) { - audio_url = [ audio_url ]; + audio_url = [audio_url]; } let html = ""; for (const entry of audio_url) { @@ -944,8 +993,8 @@ function earlyInitPage() { SiteCirclesLineDash = [5, 5]; SiteCirclesColors = ['#2b3436', '#2b3436', '#2b3436']; MapType_tar1090 = 'carto_light_all'; - lineWidth=4; - g.enableLabels=true; + lineWidth = 4; + g.enableLabels = true; } if (usp.has('debugFetch')) { @@ -1130,7 +1179,7 @@ function earlyInitPage() { if (value = usp.getFloat('mapOrientation')) { g.mapOrientation = value; } - g.mapOrientation *= (Math.PI/180); // adjust to radians + g.mapOrientation *= (Math.PI / 180); // adjust to radians if (usp.has('r') || usp.has('replay')) { let numbers = (usp.get('r') || usp.get('replay') || "").split(/(?:-|:)/); @@ -1152,7 +1201,7 @@ function earlyInitPage() { //Pulling filters from params if (usp.has('filterAltMin')) { const minAlt = usp.getInt('filterAltMin'); - if (minAlt !== null) { + if (minAlt !== null) { PlaneFilter.minAltitude = minAlt; PlaneFilter.enabled = true; PlaneFilter.maxAltitude = 1000000; @@ -1160,7 +1209,7 @@ function earlyInitPage() { } if (usp.has('filterAltMax')) { const maxAlt = usp.getInt('filterAltMax'); - if (maxAlt !== null) { + if (maxAlt !== null) { PlaneFilter.maxAltitude = maxAlt; PlaneFilter.enabled = true; if (PlaneFilter.minAltitude === undefined) { @@ -1181,9 +1230,9 @@ function earlyInitPage() { if (false && iOSVersion() <= 12 && !('PointerEvent' in window)) { jQuery("#generic_error_detail").text("Enable Settings - Safari - Advanced - Experimental features - Pointer Events"); - jQuery("#generic_error").css('display','block'); + jQuery("#generic_error").css('display', 'block'); setTimeout(function() { - jQuery("#generic_error").css('display','none'); + jQuery("#generic_error").css('display', 'none'); }, 30000); } @@ -1217,7 +1266,7 @@ function earlyInitPage() { jQuery('#tabs').tabs({ active: loStore['active_tab'], - activate: function (event, ui) { + activate: function(event, ui) { loStore['active_tab'] = jQuery("#tabs").tabs("option", "active"); }, collapsible: true @@ -1234,7 +1283,7 @@ function earlyInitPage() { else jQuery('#sidebar_container').width('25%'); - if (jQuery('#sidebar_container').width() > jQuery(window).innerWidth() *0.8) + if (jQuery('#sidebar_container').width() > jQuery(window).innerWidth() * 0.8) jQuery('#sidebar_container').width('30%'); loStore['sidebar_width'] = jQuery('#sidebar_container').width(); @@ -1270,29 +1319,29 @@ function earlyInitPage() { jQuery("#jump_form").submit(onJump); jQuery("#show_trace").click(toggleShowTrace); - jQuery("#trace_back_1d").click(function() {shiftTrace(-1)}); - jQuery("#trace_jump_1d").click(function() {shiftTrace(1)}); + jQuery("#trace_back_1d").click(function() { shiftTrace(-1) }); + jQuery("#trace_jump_1d").click(function() { shiftTrace(1) }); jQuery("#histDatePicker").datepicker({ maxDate: '+1d', dateFormat: "yy-mm-dd", - onSelect: function(date){ - setTraceDate({string: date}); + onSelect: function(date) { + setTraceDate({ string: date }); shiftTrace(); jQuery("#histDatePicker").blur(); }, autoSize: true, - onClose: !onMobile ? null : function(dateText, inst){ + onClose: !onMobile ? null : function(dateText, inst) { jQuery("#histDatePicker").attr("disabled", false); }, - beforeShow: !onMobile ? null : function(input, inst){ + beforeShow: !onMobile ? null : function(input, inst) { jQuery("#histDatePicker").attr("disabled", true); }, }); - jQuery("#replayPlay").click(function(){ + jQuery("#replayPlay").click(function() { - if (replay.playing){ + if (replay.playing) { //if playing, pause. playReplay(false); @@ -1302,8 +1351,8 @@ function earlyInitPage() { } }); - jQuery("#leg_prev").click(function() {legShift(-1)}); - jQuery("#leg_next").click(function() {legShift(1)}); + jQuery("#leg_prev").click(function() { legShift(-1) }); + jQuery("#leg_next").click(function() { legShift(1) }); jQuery('#settingsCog').on('click', function() { jQuery('#settings_infoblock').toggle(); @@ -1370,7 +1419,7 @@ function earlyInitPage() { setState: function(state) { geomUseEGM = state; if (geomUseEGM) { -jQuery('#selected_altitude_geom1') + jQuery('#selected_altitude_geom1') jQuery('#selected_altitude_geom1_title').updateText('EGM96 altitude'); jQuery('#selected_altitude_geom2_title').updateText('Geom. EGM96'); let egm = loadEGM(); @@ -1598,7 +1647,7 @@ jQuery('#selected_altitude_geom1') checkbox: null, button: '#toggle_sidebar_button', init: (onMobile ? false : true), - setState: function (state) { + setState: function(state) { if (state) { jQuery("#sidebar_container").show(); jQuery("#expand_sidebar_button").show(); @@ -1612,7 +1661,7 @@ jQuery('#selected_altitude_geom1') w: '#splitter' }, minWidth: 150, - maxWidth: (jQuery(window).innerWidth() *0.8), + maxWidth: (jQuery(window).innerWidth() * 0.8), }); jQuery("#splitter").dblclick(function() { @@ -1785,7 +1834,7 @@ jQuery('#selected_altitude_geom1') if (imageConfigLink != "") { let host = window.location.hostname; let configLink = imageConfigLink.replace('HOSTNAME', host); - jQuery('#imageConfigLink').attr('href',configLink) + jQuery('#imageConfigLink').attr('href', configLink) jQuery('#imageConfigLink').text(imageConfigText) jQuery('#imageConfigHeader').show(); } @@ -1813,12 +1862,12 @@ function initLegend(colors) { if (aiscatcher_server) html += '
      AIS
      '; html += '
      ${jaeroLabel}
      `; - + html += '
      FLARM
      '; document.getElementById('legend').innerHTML = html; } function initSourceFilter(colors) { - const createFilter = function (color, text, key) { + const createFilter = function(color, text, key) { return '
    1. ' + text + '
    2. '; }; @@ -1836,28 +1885,35 @@ function initSourceFilter(colors) { html += createFilter(colors['ais'], 'AIS', sources[7]); } + html += createFilter(colors['flarm'], 'FLARM', sources[8]); + document.getElementById('sourceFilter').innerHTML = html; jQuery("#sourceFilter").selectable({ - stop: function () { + stop: function() { sourcesFilter = []; - jQuery(".ui-selected", this).each(function () { - const index = jQuery("#sourceFilter li").index(this); - if (Array.isArray(sources[index])) - sources[index].forEach(member => { sourcesFilter.push(member); }); - else - sourcesFilter.push(sources[index]); + jQuery(".ui-selected", this).each(function() { + var key = jQuery(this).attr('id').replace('source-filter-', ''); + for (var i = 0; i < sources.length; i++) { + if (Array.isArray(sources[i]) && sources[i][0] === key) { + sources[i].forEach(function(member) { sourcesFilter.push(member); }); + break; + } else if (sources[i] === key) { + sourcesFilter.push(sources[i]); + break; + } + } }); } }); - jQuery("#sourceFilter").on("selectablestart", function (event, ui) { + jQuery("#sourceFilter").on("selectablestart", function(event, ui) { event.originalEvent.ctrlKey = true; }); } function initFlagFilter(colors) { - const createFilter = function (color, text, key) { + const createFilter = function(color, text, key) { return '
    3. ' + text + '
    4. '; }; @@ -1870,9 +1926,9 @@ function initFlagFilter(colors) { document.getElementById('flagFilter').innerHTML = html; jQuery("#flagFilter").selectable({ - stop: function () { + stop: function() { flagFilter = []; - jQuery(".ui-selected", this).each(function () { + jQuery(".ui-selected", this).each(function() { const index = jQuery("#flagFilter li").index(this); if (Array.isArray(flagFilterValues[index])) flagFilterValues[index].forEach(member => { flagFilter.push(member); }); @@ -1882,7 +1938,7 @@ function initFlagFilter(colors) { } }); - jQuery("#flagFilter").on("selectablestart", function (event, ui) { + jQuery("#flagFilter").on("selectablestart", function(event, ui) { event.originalEvent.ctrlKey = true; }); } @@ -1951,19 +2007,19 @@ function parseHistory() { // Sort history by timestamp console.log(localTime(new Date()) + " Sorting history: " + PositionHistoryBuffer.length); - PositionHistoryBuffer.sort(function(x,y) { return (y.now - x.now); }); + PositionHistoryBuffer.sort(function(x, y) { return (y.now - x.now); }); - let currentTime = new Date().getTime()/1000; + let currentTime = new Date().getTime() / 1000; if (!pTracks && !noVanish) { // get all planes within the reapTimeout g.historyKeep = {}; - for (let i = 0; i < PositionHistoryBuffer.length; i++) { + for (let i = 0; i < PositionHistoryBuffer.length; i++) { let data = PositionHistoryBuffer[i]; if (currentTime - data.now > reapTimeout) { break; } - for (let j=0; j < data.aircraft.length; j++) { + for (let j = 0; j < data.aircraft.length; j++) { const ac = data.aircraft[j]; const isArray = Array.isArray(ac); const hex = isArray ? ac[0] : ac.hex; @@ -2036,7 +2092,7 @@ function parseHistory() { if (plane.position && SitePosition && !pTracks) plane.sitedist = ol.sphere.getDistance(SitePosition, plane.position); - if (uatNoTISB && plane.uat && plane.type && plane.type.substring(0,4) == "tisb") { + if (uatNoTISB && plane.uat && plane.type && plane.type.substring(0, 4) == "tisb") { plane.last_message_time -= 999; } } @@ -2067,7 +2123,7 @@ function clearIntervalTimers(arg) { if (loadFinished && arg != 'silent') { console.log(localTime(new Date()) + ' clear timers'); jQuery("#timers_paused_detail").text('Timers paused (tab hidden).'); - jQuery("#timers_paused").css('display','block'); + jQuery("#timers_paused").css('display', 'block'); } const entries = Object.entries(timers); for (let i in entries) { @@ -2086,7 +2142,7 @@ function setIntervalTimers() { } if (loadFinished) { - jQuery("#timers_paused").css('display','none'); + jQuery("#timers_paused").css('display', 'none'); } console.log(localTime(new Date()) + " set timers "); if (dynGlobeRate && !uuid) { @@ -2106,7 +2162,7 @@ function setIntervalTimers() { } if (enable_pf_data && !pTracks && !globeIndex) { jQuery('#pf_info_container').removeClass('hidden'); - timers.pf_data = window.setInterval(fetchPfData, RefreshInterval*10.314); + timers.pf_data = window.setInterval(fetchPfData, RefreshInterval * 10.314); fetchPfData(); } if (actualOutline.enabled) { @@ -2123,6 +2179,11 @@ function setIntervalTimers() { updateDrones(); } + if (flarm_server) { + timers.flarm = setInterval(processFlarmUpdate, flarm_refresh * 1000); + processFlarmUpdate(); + } + timersActive = true; fetchData(); @@ -2132,7 +2193,7 @@ function setIntervalTimers() { } function updateDrones() { - let jsons = Array.isArray(droneJson) ? droneJson : [ droneJson ]; + let jsons = Array.isArray(droneJson) ? droneJson : [droneJson]; for (let i in jsons) { let req = jQuery.ajax({ url: jsons[i], @@ -2145,6 +2206,33 @@ function updateDrones() { } } +function processFlarmUpdate() { + if (!flarm_server) return; + + var req = jQuery.ajax({ + url: flarm_server, + dataType: 'json', + }); + + req.done(function(data) { + if (now === 0) { + now = new Date().getTime() / 1000; + last = now - 1; + } + var aircraft = data.aircraft || data; + if (!Array.isArray(aircraft)) return; + + for (var i = 0; i < aircraft.length; i++) { + var ac = normalizeFlarmAircraft(aircraft[i]); + processAircraft(ac, false, false); + } + }); + + req.fail(function() { + // silent fail + }); +} + function handleDrones(data) { g.droneLast = g.droneNow || 0; g.droneNow = new Date().getTime() / 1000; @@ -2194,7 +2282,7 @@ function updateAIScatcher() { req.done(function(data) { //console.log(data); - g.aiscatcher_source.setUrl("data:text/plain;base64,"+btoa(data)); + g.aiscatcher_source.setUrl("data:text/plain;base64," + btoa(data)); g.aiscatcher_source.refresh(); if (1 || aiscatcher_test) { @@ -2268,8 +2356,8 @@ function processBoat(feature, now, last) { ac.r = pr.shipname; ac.seen = now - pr.last_signal; - ac.messages = pr.count; - ac.rssi = pr.level; + ac.messages = pr.count; + ac.rssi = pr.level; ac.track = pr.cog; @@ -2277,8 +2365,8 @@ function processBoat(feature, now, last) { if (pr.shiptype !== undefined) { ac.t = shortShiptype(pr.shiptype); } // Identify Non-Ship Types if (pr.mmsi_type == 6) { ac.t = "ANAV"; } // Aids to Navigation - if (pr.mmsi_type == 5) {ac.t = "BASE"; } // Land Base Station - if (pr.mmsi_type == 3) {ac.t = "COAS"; } // Coast Station + if (pr.mmsi_type == 5) { ac.t = "BASE"; } // Land Base Station + if (pr.mmsi_type == 3) { ac.t = "COAS"; } // Coast Station if (feature.geometry && feature.geometry.coordinates) { @@ -2349,14 +2437,14 @@ function startPage() { // // Utils begin // -(function (global, jQuery, TAR) { +(function(global, jQuery, TAR) { let utils = TAR.utils = TAR.utils || {}; // Make a LineString with 'points'-number points // that is a closed circle on the sphere such that the // great circle distance from 'center' to each point is // 'radius' meters - utils.make_geodesic_circle = function (center, radius, points) { + utils.make_geodesic_circle = function(center, radius, points) { const angularDistance = radius / 6378137.0; const lon1 = center[0] * Math.PI / 180.0; const lat1 = center[1] * Math.PI / 180.0; @@ -2397,8 +2485,10 @@ function webglAddLayer() { icaoFilter.push(icao); } - processAircraft({hex: icao, lat: CenterLat, lon: CenterLon, type: 'tisb_other', seen: 0, seen_pos: 0, - alt_baro: 25000, }); + processAircraft({ + hex: icao, lat: CenterLat, lon: CenterLon, type: 'tisb_other', seen: 0, seen_pos: 0, + alt_baro: 25000, + }); let plane = g.planes[icao]; if (spritesDataURL) { @@ -2409,33 +2499,33 @@ function webglAddLayer() { let glStyle = { 'icon-src': spriteSrc, 'icon-color': [ - 'color', - [ 'get', 'r' ], - [ 'get', 'g' ], - [ 'get', 'b' ], - 1 + 'color', + ['get', 'r'], + ['get', 'g'], + ['get', 'b'], + 1 ], - 'icon-size': [ glIconSize, glIconSize ], + 'icon-size': [glIconSize, glIconSize], 'icon-offset': [ 'array', - [ 'get', 'sx' ], - [ 'get', 'sy' ] + ['get', 'sx'], + ['get', 'sy'] ], - 'icon-rotation': [ 'get' , 'rotation' ], + 'icon-rotation': ['get', 'rotation'], 'icon-rotate-with-view': false, //'icon-scale': [ 'array', ['get', 'scale'], ['get', 'scale'] ], - 'icon-scale': [ 'abs', ['get', 'scale']], + 'icon-scale': ['abs', ['get', 'scale']], }; if (heatmap) { glStyle = { 'circle-radius': heatmap.radius * globalScale * 1.25, 'circle-displacement': [0, 0], 'circle-opacity': heatmap.alpha || webglIconOpacity, - 'circle-fill-color' : [ + 'circle-fill-color': [ 'color', - [ 'get', 'r' ], - [ 'get', 'g' ], - [ 'get', 'b' ], + ['get', 'r'], + ['get', 'g'], + ['get', 'b'], 1 ] } @@ -2550,11 +2640,11 @@ function ol_map_init() { zoom: g.zoomLvl, multiWorld: true, }), - controls: [new ol.control.Zoom({delta: 1, duration: 0, target: 'map_canvas',}), - new ol.control.Attribution({collapsed: true}), - new ol.control.ScaleLine({units: DisplayUnits}) + controls: [new ol.control.Zoom({ delta: 1, duration: 0, target: 'map_canvas', }), + new ol.control.Attribution({ collapsed: true }), + new ol.control.ScaleLine({ units: DisplayUnits }) ], - interactions: new ol.interaction.defaults({altShiftDragRotate:false, pinchRotate:false,}), + interactions: new ol.interaction.defaults({ altShiftDragRotate: false, pinchRotate: false, }), maxTilesLoading: 4, }); @@ -2651,7 +2741,7 @@ function ol_map_init() { OLMap.on('moveend', function(event) { checkMovement(); - webgl && webglLayer.setOpacity(webglIconOpacity ); + webgl && webglLayer.setOpacity(webglIconOpacity); }); OLMap.on(['click', 'dblclick'], function(evt) { @@ -2667,7 +2757,7 @@ function ol_map_init() { let a = fPixel[0] - evt.pixel[0]; let b = fPixel[1] - evt.pixel[1]; let c = globalScale * (onMobile ? 30 : 20); - if (a**2 + b**2 < c**2) { + if (a ** 2 + b ** 2 < c ** 2) { planeHex = feature.hex; } else { feature = null; @@ -2716,7 +2806,7 @@ function ol_map_init() { let fPixel = evt.map.getPixelFromCoordinate(coords[k]); let a = fPixel[0] - evt.pixel[0]; let b = fPixel[1] - evt.pixel[1]; - let distance2 = a**2 + b**2; + let distance2 = a ** 2 + b ** 2; if (distance2 < close2) { closest = feature; close2 = distance2; @@ -2740,9 +2830,9 @@ function ol_map_init() { } //console.log(`planeHex: ${planeHex} trailHex: ${trailHex}`); if (planeHex) { - selectPlaneByHex(planeHex, {noDeselect: dblclick, follow: dblclick}); + selectPlaneByHex(planeHex, { noDeselect: dblclick, follow: dblclick }); } else if (trailHex) { - selectPlaneByHex(trailHex, {noDeselect: true}); + selectPlaneByHex(trailHex, { noDeselect: true }); } if (!planeHex && !trailHex && !multiSelect && !showTrace) { @@ -2929,13 +3019,13 @@ function initMap() { // handle the layer settings pane checkboxes //OLMap.once('postrender', function(e) { - //toggleLayer('#nexrad_checkbox', 'nexrad'); - //toggleLayer('#sitepos_checkbox', 'site_pos'); - //toggleLayer('#actrail_checkbox', 'ac_trail'); - //toggleLayer('#acpositions_checkbox', 'webglLayer'); + //toggleLayer('#nexrad_checkbox', 'nexrad'); + //toggleLayer('#sitepos_checkbox', 'site_pos'); + //toggleLayer('#actrail_checkbox', 'ac_trail'); + //toggleLayer('#acpositions_checkbox', 'webglLayer'); //}); - jQuery('#infoblock_close').on('click', function () { + jQuery('#infoblock_close').on('click', function() { if (showTrace) toggleShowTrace(); if (onlySelected) @@ -2987,25 +3077,25 @@ function initMap() { if (state) { root.style.setProperty("--BGCOLOR1", '#313131'); root.style.setProperty("--BGCOLOR2", '#242424'); - root.style.setProperty("--TXTCOLOR1","#BFBFBF"); - root.style.setProperty("--TXTCOLOR2","#D8D8D8"); - root.style.setProperty("--TXTCOLOR3","#a8a8a8"); + root.style.setProperty("--TXTCOLOR1", "#BFBFBF"); + root.style.setProperty("--TXTCOLOR2", "#D8D8D8"); + root.style.setProperty("--TXTCOLOR3", "#a8a8a8"); //invert the "x" images - jQuery(".infoblockCloseBox").css('filter','invert(100%)'); - jQuery(".infoblockCloseBox").css(' -webkit-filter','invert(100%)'); - jQuery(".settingsCloseBox").css('filter','invert(100%)'); - jQuery(".settingsCloseBox").css(' -webkit-filter','invert(100%)'); + jQuery(".infoblockCloseBox").css('filter', 'invert(100%)'); + jQuery(".infoblockCloseBox").css(' -webkit-filter', 'invert(100%)'); + jQuery(".settingsCloseBox").css('filter', 'invert(100%)'); + jQuery(".settingsCloseBox").css(' -webkit-filter', 'invert(100%)'); tableColors = tableColorsDark; } else { root.style.setProperty("--BGCOLOR1", '#F8F8F8'); root.style.setProperty("--BGCOLOR2", '#CCCCCC'); - root.style.setProperty("--TXTCOLOR1","#003f4b"); - root.style.setProperty("--TXTCOLOR2","#050505"); - root.style.setProperty("--TXTCOLOR3","#003f4b"); - jQuery(".infoblockCloseBox").css('filter','invert(0%)'); - jQuery(".infoblockCloseBox").css(' -webkit-filter','invert(0%)'); - jQuery(".settingsCloseBox").css('filter','invert(0%)'); - jQuery(".settingsCloseBox").css(' -webkit-filter','invert(0%)'); + root.style.setProperty("--TXTCOLOR1", "#003f4b"); + root.style.setProperty("--TXTCOLOR2", "#050505"); + root.style.setProperty("--TXTCOLOR3", "#003f4b"); + jQuery(".infoblockCloseBox").css('filter', 'invert(0%)'); + jQuery(".infoblockCloseBox").css(' -webkit-filter', 'invert(0%)'); + jQuery(".settingsCloseBox").css('filter', 'invert(0%)'); + jQuery(".settingsCloseBox").css(' -webkit-filter', 'invert(0%)'); tableColors = tableColorsLight; } @@ -3059,7 +3149,7 @@ function initMap() { window.addEventListener('keydown', function(e) { active(); - if (e.defaultPrevented ) { + if (e.defaultPrevented) { return; // Do nothing if the event was already processed } if (e.target.type == "text") { @@ -3069,7 +3159,7 @@ function initMap() { return; } - if( e.ctrlKey || e.altKey || e.metaKey) { + if (e.ctrlKey || e.altKey || e.metaKey) { return; } let oldCenter, extent, newCenter; @@ -3079,7 +3169,7 @@ function initMap() { case "Escape": deselectAllPlanes(); break; - // zoom and movement + // zoom and movement case "q": case "-": case "Subtract": @@ -3094,7 +3184,7 @@ function initMap() { case "w": oldCenter = OLMap.getView().getCenter(); extent = OLMap.getView().calculateExtent(OLMap.getSize()); - newCenter = [oldCenter[0], (oldCenter[1] + extent[3])/2]; + newCenter = [oldCenter[0], (oldCenter[1] + extent[3]) / 2]; OLMap.getView().setCenter(newCenter); toggleFollow(false); break; @@ -3102,7 +3192,7 @@ function initMap() { case "s": oldCenter = OLMap.getView().getCenter(); extent = OLMap.getView().calculateExtent(OLMap.getSize()); - newCenter = [oldCenter[0], (oldCenter[1] + extent[1])/2]; + newCenter = [oldCenter[0], (oldCenter[1] + extent[1]) / 2]; OLMap.getView().setCenter(newCenter); toggleFollow(false); break; @@ -3110,7 +3200,7 @@ function initMap() { case "a": oldCenter = OLMap.getView().getCenter(); extent = OLMap.getView().calculateExtent(OLMap.getSize()); - newCenter = [(oldCenter[0] + extent[0])/2, oldCenter[1]]; + newCenter = [(oldCenter[0] + extent[0]) / 2, oldCenter[1]]; OLMap.getView().setCenter(newCenter); toggleFollow(false); break; @@ -3118,11 +3208,11 @@ function initMap() { case "d": oldCenter = OLMap.getView().getCenter(); extent = OLMap.getView().calculateExtent(OLMap.getSize()); - newCenter = [(oldCenter[0] + extent[2])/2, oldCenter[1]]; + newCenter = [(oldCenter[0] + extent[2]) / 2, oldCenter[1]]; OLMap.getView().setCenter(newCenter); toggleFollow(false); break; - // misc + // misc case "b": toggles['MapDim'].toggle(); break; @@ -3158,7 +3248,7 @@ function initMap() { case "f": toggleFollow(); break; - // filters + // filters case "T": filterTISB = !filterTISB; refreshFilter(); @@ -3172,11 +3262,11 @@ function initMap() { case "i": toggleIsolation(); break; - // persistence mode + // persistence mode case "p": togglePersistence(); break; - // Labels + // Labels case "l": toggleLabels(); break; @@ -3186,7 +3276,7 @@ function initMap() { case "k": toggleTrackLabels(); break; - // debug stuff + // debug stuff case "L": toggles['lastLeg'].toggle(); break; @@ -3210,7 +3300,7 @@ function initMap() { console.log(SelectedPlane.milRange()); break; case "j": - selectPlaneByHex(jumpTo, {follow: true}); + selectPlaneByHex(jumpTo, { follow: true }); break; case "J": debugJump = !debugJump; @@ -3268,7 +3358,7 @@ function reaper(all) { if (plane == null) continue; plane.seen = now - plane.last_message_time; - if ( all || + if (all || ( (!plane.selected) && plane.seen > reapTimeout @@ -3346,10 +3436,10 @@ function displaySil() { return; } let selected = SelectedPlane; - let new_html=""; + let new_html = ""; let type = selected.icaoType ? selected.icaoType : 'ZZZZ'; let hex = selected.icao.toUpperCase(); - new_html = ""; + new_html = ""; setPhotoHtml(new_html); selected.icao.toUpperCase(); } @@ -3367,13 +3457,13 @@ function displayPhoto() { adjustInfoBlock(); return; } - let new_html=""; + let new_html = ""; let photoToPull = photos[0]["thumbnail"]["src"] || photos[0]["thumbnail"]; let linkToPicture = photos[0]["link"]; //console.log(linkToPicture); - new_html = ''; + new_html = ''; let copyright = photos[0]["photographer"] || photos[0]["user"]; - jQuery('#copyrightInfo').html("Image © " + copyright +""); + jQuery('#copyrightInfo').html("Image © " + copyright + ""); setPhotoHtml(new_html); adjustInfoBlock(); } @@ -3448,7 +3538,7 @@ function refreshPhoto(selected) { } }); req.fail(function() { - this.plane.psAPIresponse = {'photos': []}; + this.plane.psAPIresponse = { 'photos': [] }; if (SelectedPlane == this.plane) { displayPhoto(); } @@ -3461,7 +3551,7 @@ let selIcao = null; let selReg = null; let somethingSelected = false; -// Refresh the detail window about the plane +// Refresh the sidebar detail window about the plane function refreshSelected() { const selected = SelectedPlane; @@ -3503,7 +3593,7 @@ function refreshSelected() { jQuery('#selected_flightaware_link').html(getFlightAwareModeSLink(selected.icao, selected.flight, "Visit Flight Page")); } - if (selected.isNonIcao() && selected.source != 'mlat') { + if (selected.isNonIcao() && selected.source != 'mlat' && selected.dataSource != 'flarm') { jQuery('#anon_mlat_info').addClass('hidden'); jQuery('#reg_info').addClass('hidden'); jQuery('#tisb_info').removeClass('hidden'); @@ -3670,7 +3760,7 @@ function refreshSelected() { if (oat != null) - jQuery('#selected_temp').updateText(Math.round(tat) + ' / ' + Math.round(oat) + ' °C'); + jQuery('#selected_temp').updateText(Math.round(tat) + ' / ' + Math.round(oat) + ' °C'); else jQuery('#selected_temp').updateText('n/a'); @@ -3684,9 +3774,9 @@ function refreshSelected() { setSelectedIcao(); - jQuery('#selected_pf_info').updateText((selected.pfRoute ? selected.pfRoute : "") ); + jQuery('#selected_pf_info').updateText((selected.pfRoute ? selected.pfRoute : "")); //+" "+ (selected.pfFlightno ? selected.pfFlightno : "") - jQuery('#airframes_post_icao').attr('value',selected.icao); + jQuery('#airframes_post_icao').attr('value', selected.icao); jQuery('#selected_track1').updateText(format_track_brief(selected.track)); jQuery('#selected_track2').updateText(format_track_brief(selected.track)); @@ -3842,10 +3932,53 @@ function refreshSelected() { } else { jQuery('#selected_version').updateText('v' + selected.version); } - + refreshSelectedFlarm(); adjustInfoBlock(); } +// for FLARM display +function refreshSelectedFlarm() { + var selected = SelectedPlane; + if (!selected || !selected.flarm) { + jQuery('#selected_flarm_block').addClass('hidden'); + return; + } + + jQuery('#selected_flarm_block').removeClass('hidden'); + + var f = selected.flarm; + + var fields = [ + { label: 'ICAO24', key: 'icao24' }, + { label: 'Radio ID Type', key: 'radio_identifier_type' }, + { label: 'Aircraft Type', key: 'aircraft_type' }, + { label: 'Urgency', key: 'urgency' }, + { label: 'Stealth', key: 'is_stealth', fmt: function(v) { return v ? 'Yes' : 'No'; } }, + { label: 'No Track', key: 'is_no_track', fmt: function(v) { return v ? 'Yes' : 'No'; } }, + { label: 'Movement Mode', key: 'movement_mode' }, + { label: 'Turn Rate', key: 'turn_rate', fmt: function(v) { return v != null ? v.toFixed(1) + ' °/s' : 'n/a'; } }, + { label: 'Version', key: 'version', fmt: function(v) { return 'v' + v; } }, + { label: 'H-ACC', key: 'acc_pos_hor', fmt: function(v) { return v != null ? v.toFixed(0) + ' m' : 'n/a'; } }, + { label: 'V-ACC', key: 'acc_pos_ver', fmt: function(v) { return v != null ? v.toFixed(0) + ' m' : 'n/a'; } }, + { label: 'Vel. ACC', key: 'acc_vel', fmt: function(v) { return v != null ? v.toFixed(1) + ' m/s' : 'n/a'; } }, + { label: 'SIL', key: 'sil' }, + { label: 'SDA', key: 'sda' }, + { label: 'NIC', key: 'nic' }, + ]; + + var html = ''; + for (var i = 0; i < fields.length; i++) { + var field = fields[i]; + var val = f[field.key]; + var display = (val != null && val !== '') ? (field.fmt ? field.fmt(val) : val) : 'n/a'; + html += '
      ' + + '' + + '' + + ''; + } + jQuery('#selected_flarm_fields').html(html); +} + let somethingHighlighted = false; function refreshHighlighted() { // this is following nearly identical logic, etc, as the refreshSelected function, but doing less junk for the highlighted pane @@ -3868,7 +4001,7 @@ function refreshHighlighted() { let marker = highlighted.marker || highlighted.glMarker; let geom; let markerCoordinates; - if (!marker || !(geom = marker.getGeometry()) || !(markerCoordinates = geom.getCoordinates()) ) { + if (!marker || !(geom = marker.getGeometry()) || !(markerCoordinates = geom.getCoordinates())) { jQuery('#highlighted_infoblock').hide(); return; } @@ -3976,10 +4109,10 @@ function refreshFeatures() { // // g.planes table begin // -(function (global, jQuery, TAR) { +(function(global, jQuery, TAR) { let planeMan = TAR.planeMan = TAR.planeMan || {}; - function compareAlpha(xa,ya) { + function compareAlpha(xa, ya) { if (xa === ya) return 0; if (xa < ya) @@ -3997,7 +4130,7 @@ function refreshFeatures() { return 1; } - function compareNumeric(xf,yf) { + function compareNumeric(xf, yf) { if (Math.abs(xf - yf) < 1e-9) return 0; @@ -4008,30 +4141,31 @@ function refreshFeatures() { cols.icao = { text: 'Hex ID', - sort: function () { sortBy('icao', compareAlpha, function(x) { return x.icao; }); }, + sort: function() { sortBy('icao', compareAlpha, function(x) { return x.icao; }); }, value: function(plane) { return plane.icao; }, td: ''; for (let i in activeCols) { let col = activeCols[i]; - table += ''; + table += ''; } table += ' '; table += ''; @@ -4218,7 +4365,7 @@ function refreshFeatures() { } } - planeMan.setColumnVis = function (col, visible) { + planeMan.setColumnVis = function(col, visible) { cols[col].visible = visible; if (!initializing) @@ -4226,8 +4373,8 @@ function refreshFeatures() { } // Refreshes the larger table of all the planes - planeMan.refresh = function () { - if (!loadFinished) { + planeMan.refresh = function() { + if (!loadFinished) { return; } //console.trace(); @@ -4376,7 +4523,7 @@ function refreshFeatures() { planeMan.sortExtract = null; planeMan.sortAscending = true; - function sortFunction(x,y) { + function sortFunction(x, y) { const xv = x._sort_value; const yv = y._sort_value; @@ -4386,7 +4533,7 @@ function refreshFeatures() { if (xv == null) return 1; if (yv == null) return -1; - const c = planeMan.sortAscending ? planeMan.sortCompare(xv,yv) : planeMan.sortCompare(yv,xv); + const c = planeMan.sortAscending ? planeMan.sortCompare(xv, yv) : planeMan.sortCompare(yv, xv); if (c !== 0) return c; return x._sort_pos - y._sort_pos; @@ -4403,24 +4550,24 @@ function refreshFeatures() { for (let i = 0; i < pList.length; ++i) { pList[i]._sort_pos = i; } - pList.sort(function(x,y) { + pList.sort(function(x, y) { const a = x.getDataSourceNumber(); const b = y.getDataSourceNumber(); if (a == b) return (x._sort_pos - y._sort_pos); - return (a-b); + return (a - b); }); } // or distance else if (planeMan.sortId == "data_source") { - pList.sort(function(x,y) { + pList.sort(function(x, y) { return (x.sitedist - y.sitedist); }); } // or longitude else { - pList.sort(function(x,y) { + pList.sort(function(x, y) { return (x.position ? x.position[0] : 500) - (y.position ? y.position[0] : 500); }); } @@ -4447,7 +4594,7 @@ function refreshFeatures() { for (let i = 0; i < pList.length; ++i) { pList[i]._sort_pos = i; } - pList.sort(function(x,y) { + pList.sort(function(x, y) { if (x.selected && y.selected) { return (x._sort_pos - y._sort_pos); } @@ -4485,9 +4632,9 @@ function refreshFeatures() { function createColumnToggles() { const prefix = 'dd_'; const sortableColumns = jQuery('#sortableColumns').sortable({ - update: function (event, ui) { + update: function(event, ui) { const order = []; - jQuery('#sortableColumns li').each(function (e) { + jQuery('#sortableColumns li').each(function(e) { order.push(jQuery(this).attr('id').replace(prefix, '')); }); @@ -4506,7 +4653,7 @@ function refreshFeatures() { display: col.text, container: jQuery(`#${prefix + col.id}`), init: col.visible, - setState: function (state) { + setState: function(state) { planeMan.setColumnVis(col.id, state); } }); @@ -4756,7 +4903,7 @@ function resetMap() { OLMap.getView().setRotation(g.mapOrientation); //selectPlaneByHex(null,false); - jQuery("#update_error").css('display','none'); + jQuery("#update_error").css('display', 'none'); }); } @@ -4801,7 +4948,7 @@ function setPhotoHtml(source) { } function adjustInfoBlock() { - if (wideInfoBlock ) { + if (wideInfoBlock) { infoBlockWidth = baseInfoBlockWidth + 40; } else { infoBlockWidth = baseInfoBlockWidth; @@ -4975,7 +5122,7 @@ function toggleIsolation(state, noRefresh) { if (prevState != onlySelected && noRefresh != "noRefresh") refreshFilter(); - fetchData({force: true}); + fetchData({ force: true }); } function toggleMilitary() { @@ -4984,7 +5131,7 @@ function toggleMilitary() { refreshFilter(); active(); - fetchData({force: true}); + fetchData({ force: true }); } function togglePersistence() { @@ -5018,21 +5165,21 @@ function dim(evt) { const contrast = currentContrastPercentage * (1 + 0.1 * toggles['darkerColors'].state); if (dim > 0.0001) { evt.context.globalCompositeOperation = 'multiply'; - evt.context.fillStyle = 'rgba(0,0,0,'+dim+')'; + evt.context.fillStyle = 'rgba(0,0,0,' + dim + ')'; evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height); } else if (dim < -0.0001) { evt.context.globalCompositeOperation = 'screen'; console.log(evt.context.globalCompositeOperation); - evt.context.fillStyle = 'rgba(255, 255, 255,'+(-dim)+')'; + evt.context.fillStyle = 'rgba(255, 255, 255,' + (-dim) + ')'; evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height); } if (contrast > 0.0001) { evt.context.globalCompositeOperation = 'overlay'; - evt.context.fillStyle = 'rgba(0,0,0,'+contrast+')'; + evt.context.fillStyle = 'rgba(0,0,0,' + contrast + ')'; evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height); } else if (contrast < -0.0001) { evt.context.globalCompositeOperation = 'overlay'; - evt.context.fillStyle = 'rgba(255, 255, 255,'+ (-contrast)+')'; + evt.context.fillStyle = 'rgba(255, 255, 255,' + (-contrast) + ')'; evt.context.fillRect(0, 0, evt.context.canvas.width, evt.context.canvas.height); } evt.context.globalCompositeOperation = 'source-over'; @@ -5040,19 +5187,19 @@ function dim(evt) { console.error(error); } } -function invertMap(evt){ - const ctx=evt.context; - ctx.globalCompositeOperation='difference'; - ctx.fillStyle = "white"; - ctx.globalAlpha = alpha; // alpha 0 = no effect 1 = full effect - ctx.fillRect(0, 0, evt.ctx.canvas.width, ctx.canvas.height); +function invertMap(evt) { + const ctx = evt.context; + ctx.globalCompositeOperation = 'difference'; + ctx.fillStyle = "white"; + ctx.globalAlpha = alpha; // alpha 0 = no effect 1 = full effect + ctx.fillRect(0, 0, evt.ctx.canvas.width, ctx.canvas.height); } // // Altitude Chart begin // -(function (global, jQuery, TAR) { +(function(global, jQuery, TAR) { let altitudeChart = TAR.altitudeChart = TAR.altitudeChart || {}; function createLegendGradientStops() { @@ -5078,13 +5225,13 @@ function invertMap(evt){ function loadLegend() { let baseLegend = (DisplayUnits === 'metric') ? 'images/alt_legend_m.svg' : 'images/alt_legend_ft.svg'; - jQuery.get(baseLegend, function (data) { + jQuery.get(baseLegend, function(data) { jQuery('#altitude_chart_button').css("background-image", createLegendUrl(data)); jQuery('#altitude_chart').show(); }); } - altitudeChart.render = function () { + altitudeChart.render = function() { if (toggles['altitudeChart'].state) { runAfterLoad(loadLegend); } else { @@ -5092,7 +5239,7 @@ function invertMap(evt){ } } - altitudeChart.init = function () { + altitudeChart.init = function() { let chartOn = (onMobile ? false : altitudeChartDefaultState); if (usp.has('altitudeChart')) { chartOn = Boolean(parseInt(usp.get('altitudeChart'))); @@ -5118,13 +5265,13 @@ function followRandomPlane() { let this_one = null; let tired = 0; do { - this_one = g.planesOrdered[Math.floor(Math.random()*g.planesOrdered.length)]; + this_one = g.planesOrdered[Math.floor(Math.random() * g.planesOrdered.length)]; if (!this_one || tired++ > 1000) break; } while ((this_one.isFiltered() && !onlySelected) || !this_one.position || (now - this_one.position_time > 30)); //console.log(this_one.icao); if (this_one) - selectPlaneByHex(this_one.icao, {follow: true}); + selectPlaneByHex(this_one.icao, { follow: true }); } function toggleTableInView(arg) { @@ -5240,7 +5387,7 @@ function onJump(e) { OLMap.getView().setCenter(ol.proj.fromLonLat([coords[1], coords[0]])); if (g.zoomLvl >= 7) { - fetchData({force: true}); + fetchData({ force: true }); } refreshFilter(); @@ -5279,7 +5426,7 @@ function onSearch(e) { if (results.length > 0 && haveTraces) { toggleIsolation("on"); if (results.length < 100) { - getTrace(null, null, {list: results}); + getTrace(null, null, { list: results }); } } return false; @@ -5439,12 +5586,12 @@ Filter.prototype.init = function() { const row = this.tbody.insertRow(); row.innerHTML = `' - ; + ; this.input = jQuery(this.sid + '_input'); this.form = document.getElementById(this.id) this.form.onsubmit = (e) => { return this.update(e); }; @@ -5549,7 +5696,7 @@ function getFlightAwareModeSLink(code, ident, linkText) { linkText = "FlightAware: " + code.toUpperCase(); } - let linkHtml = "Jetphotos"; + return "Jetphotos"; } else if (flightawareLinks) { if (ac.registration == null || ac.registration == "") return ""; - return "FA Photos"; + return "FA Photos"; } else if (showPictures) { return "View on g.planespotters"; } @@ -5607,8 +5754,10 @@ function fetchPfData() { return; fetchingPf = true; for (let i in pf_data) { - const req = jQuery.ajax({ url: pf_data[i], - dataType: 'json' }); + const req = jQuery.ajax({ + url: pf_data[i], + dataType: 'json' + }); jQuery.when(req).done(function(data) { for (let i in g.planesOrdered) { const plane = g.planesOrdered[i]; @@ -5638,38 +5787,38 @@ function solidGoldT(arg) { let plane = g.planesOrdered[i]; //console.log(plane); if (plane.visible) { - list[Math.floor(4*i/g.planesOrdered.length)].push(plane); + list[Math.floor(4 * i / g.planesOrdered.length)].push(plane); } } - getTrace(null, null, {onlyRecent: arg == 2, onlyFull: arg == 1, list: list[0],}); - getTrace(null, null, {onlyRecent: arg == 2, onlyFull: arg == 1, list: list[1],}); - getTrace(null, null, {onlyRecent: arg == 2, onlyFull: arg == 1, list: list[2],}); - getTrace(null, null, {onlyRecent: arg == 2, onlyFull: arg == 1, list: list[3],}); + getTrace(null, null, { onlyRecent: arg == 2, onlyFull: arg == 1, list: list[0], }); + getTrace(null, null, { onlyRecent: arg == 2, onlyFull: arg == 1, list: list[1], }); + getTrace(null, null, { onlyRecent: arg == 2, onlyFull: arg == 1, list: list[2], }); + getTrace(null, null, { onlyRecent: arg == 2, onlyFull: arg == 1, list: list[3], }); } function bearingFromLonLat(position1, position2) { // Positions in format [lon in deg, lat in deg] - const lon1 = position1[0]*Math.PI/180; - const lat1 = position1[1]*Math.PI/180; - const lon2 = position2[0]*Math.PI/180; - const lat2 = position2[1]*Math.PI/180; + const lon1 = position1[0] * Math.PI / 180; + const lat1 = position1[1] * Math.PI / 180; + const lon2 = position2[0] * Math.PI / 180; + const lat2 = position2[1] * Math.PI / 180; - const y = Math.sin(lon2-lon1)*Math.cos(lat2); - const x = Math.cos(lat1)*Math.sin(lat2) - - Math.sin(lat1)*Math.cos(lat2)*Math.cos(lon2-lon1); - return (Math.atan2(y, x)* 180 / Math.PI + 360) % 360; + const y = Math.sin(lon2 - lon1) * Math.cos(lat2); + const x = Math.cos(lat1) * Math.sin(lat2) + - Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1); + return (Math.atan2(y, x) * 180 / Math.PI + 360) % 360; } function zoomIn() { const zoom = OLMap.getView().getZoom(); - OLMap.getView().setZoom((zoom+1).toFixed()); + OLMap.getView().setZoom((zoom + 1).toFixed()); if (FollowSelected) toggleFollow(true); } function zoomOut() { const zoom = OLMap.getView().getZoom(); - OLMap.getView().setZoom((zoom-1).toFixed()); + OLMap.getView().setZoom((zoom - 1).toFixed()); if (FollowSelected) toggleFollow(true); } @@ -5683,7 +5832,7 @@ function changeZoom(init) { checkScale(); // small zoomstep, no need to change aircraft scaling - if (!init && Math.abs(g.zoomLvl-g.zoomLvlCache) < 0.4) + if (!init && Math.abs(g.zoomLvl - g.zoomLvlCache) < 0.4) return; lopaStore['zoomLvl'] = g.zoomLvl; @@ -5778,7 +5927,7 @@ function checkMovement() { return; } - let currentTime = Date.now()/1000; + let currentTime = Date.now() / 1000; if (currentTime > g.route_next_lookup && !g.route_check_in_flight) { // check if it's time to send a batch of request to the API server g.route_next_lookup = currentTime + 1; @@ -5973,7 +6122,7 @@ function highlight(evt) { let a = fPixel[0] - evt.pixel[0]; let b = fPixel[1] - evt.pixel[1]; let c = globalScale * 20; - if (a**2 + b**2 > c**2) { + if (a ** 2 + b ** 2 > c ** 2) { feature = null; } } @@ -6025,11 +6174,11 @@ function parseURLIcaos() { } } } -function processURLParams(){ +function processURLParams() { if (usp.has('showTrace')) { - let date = setTraceDate({string: usp.get('showTrace')}); + let date = setTraceDate({ string: usp.get('showTrace') }); if (date && usp.has('startTime')) { - let numbers = usp.get('startTime').split(':'); + let numbers = usp.get('startTime').split(':'); traceOpts.startHours = numbers[0] ? parseInt(numbers[0]) : 0; traceOpts.startMinutes = numbers[1] ? parseInt(numbers[1]) : 0; traceOpts.startSeconds = numbers[2] ? parseInt(numbers[2]) : 0; @@ -6083,8 +6232,8 @@ function processURLParams(){ } for (let i = 0; i < icaos.length; i++) { const icao = icaos[i]; - console.log('Selected ICAO id: '+ icao + ' traceDate: ' + traceDateString); - let options = {follow: follow, noDeselect: true}; + console.log('Selected ICAO id: ' + icao + ' traceDate: ' + traceDateString); + let options = { follow: follow, noDeselect: true }; if (traceDate != null) { let newPlane = g.planes[icao] || new PlaneObject(icao); newPlane.last_message_time = NaN; @@ -6098,8 +6247,7 @@ function processURLParams(){ selectPlaneByHex(icao, options); } } - if (traceDate != null) - { + if (traceDate != null) { toggleShowTrace(); toggleFollow(follow); } @@ -6164,10 +6312,11 @@ function processURLParams(){ let regIcaoDownloadRunning = false; function regIcaoDownload(opts) { regIcaoDownloadRunning = true; - let req = jQuery.ajax({ url: databaseFolder + "/regIcao.js", + let req = jQuery.ajax({ + url: databaseFolder + "/regIcao.js", cache: true, timeout: 60000, - dataType : 'json', + dataType: 'json', opts: opts, }); req.done(function(data) { @@ -6192,13 +6341,13 @@ function findPlanes(queries, byIcao, byCallsign, byReg, byType, showWarnings) { let upper = query.toUpperCase().replace("-", ""); if (db.regCache) { if (db.regCache[upper]) { - selectPlaneByHex(db.regCache[upper].toLowerCase(), {noDeselect: true, follow: true}); + selectPlaneByHex(db.regCache[upper].toLowerCase(), { noDeselect: true, follow: true }); } } else if (!regIcaoDownloadRunning) { let req = regIcaoDownload({ upper: `${upper}` }); req.done(function() { if (db.regCache[this.opts.upper]) { - selectPlaneByHex(db.regCache[this.opts.upper].toLowerCase(), {noDeselect: true, follow: true}); + selectPlaneByHex(db.regCache[this.opts.upper].toLowerCase(), { noDeselect: true, follow: true }); } }); } @@ -6233,7 +6382,7 @@ function findPlanes(queries, byIcao, byCallsign, byReg, byType, showWarnings) { } showWarnings && hideSearchWarning(); } else if (results.length == 1) { - selectPlaneByHex(results[0].icao, {noDeselect: true, follow: true}); + selectPlaneByHex(results[0].icao, { noDeselect: true, follow: true }); console.log("query selected: " + queries); showWarnings && hideSearchWarning(); } else { @@ -6244,7 +6393,7 @@ function findPlanes(queries, byIcao, byCallsign, byReg, byType, showWarnings) { const query = queries[i]; if (query.toLowerCase().match(/~?[a-f,0-9]{6}/)) { console.log("maybe it's an icao, let's try to fetch the history for it!"); - selectPlaneByHex(query, {noDeselect: true, follow: true}) && foundByHex++ + selectPlaneByHex(query, { noDeselect: true, follow: true }) && foundByHex++ } } } @@ -6346,8 +6495,8 @@ function globe_index(lat, lon) { lat = grid * Math.floor((lat + 90) / grid) - 90; lon = grid * Math.floor((lon + 180) / grid) - 180; - let i = Math.floor((lat+90) / grid); - let j = Math.floor((lon+180) / grid); + let i = Math.floor((lat + 90) / grid); + let j = Math.floor((lon + 180) / grid); let lat_multiplier = Math.floor(360 / grid + 1); let defaultIndex = i * lat_multiplier + j + 1000; @@ -6430,8 +6579,8 @@ function updateAddressBar() { if (replay) { string += '?replay='; string += zDateString(replay.ts); - string += '-' + replay.ts.getUTCHours().toString().padStart(2,'0'); - string += ':' + replay.ts.getUTCMinutes().toString().padStart(2,'0'); + string += '-' + replay.ts.getUTCHours().toString().padStart(2, '0'); + string += ':' + replay.ts.getUTCMinutes().toString().padStart(2, '0'); } if (SelPlanes.length > 0) { @@ -6481,7 +6630,7 @@ function updateAddressBar() { } let shareFilter = ''; - if (shareFiltersParam || (toggles.shareFilters && toggles.shareFilters.state)) { + if (shareFiltersParam || (toggles.shareFilters && toggles.shareFilters.state)) { let filterStrings = []; if (PlaneFilter.minAltitude > -1000000) { @@ -6666,7 +6815,7 @@ function toggleShowTrace() { const plane = SelPlanes[i]; plane.setNull(); } - selectPlaneByHex(hex, {noDeselect: true, follow: true, zoom: g.zoomLvl,}); + selectPlaneByHex(hex, { noDeselect: true, follow: true, zoom: g.zoomLvl, }); if (replay) { replayStep(); } @@ -6677,7 +6826,7 @@ function toggleShowTrace() { } function legShift(offset, plane) { - if(!offset) + if (!offset) offset = 0; if (!plane) { legSel += offset; @@ -6812,7 +6961,7 @@ function shiftTrace(offset) { jQuery("#histDatePicker").datepicker('setDate', traceDateString); for (let i in SelPlanes) { - selectPlaneByHex(SelPlanes[i].icao, {noDeselect: true, zoom: g.zoomLvl}); + selectPlaneByHex(SelPlanes[i].icao, { noDeselect: true, zoom: g.zoomLvl }); } updateAddressBar(); @@ -6835,13 +6984,13 @@ function setLineWidth() { }) }); - badLine = new ol.style.Style({ + badLine = new ol.style.Style({ stroke: new ol.style.Stroke({ color: '#FF0000', width: 2 * newWidth, }) }); - badLineMlat = new ol.style.Style({ + badLineMlat = new ol.style.Style({ stroke: new ol.style.Stroke({ color: '#FFA500', width: 2 * newWidth, @@ -6865,11 +7014,11 @@ function setLineWidth() { }), }); - labelFill = new ol.style.Fill({color: 'white' }); - blackFill = new ol.style.Fill({color: 'black' }); - labelStroke = new ol.style.Stroke({color: 'rgba(0,0,0,0.7', width: 4 * globalScale}); - labelStrokeNarrow = new ol.style.Stroke({color: 'rgba(0,0,0,0.7', width: 2.5 * globalScale}); - bgFill = new ol.style.Stroke({color: 'rgba(0,0,0,0.25'}); + labelFill = new ol.style.Fill({ color: 'white' }); + blackFill = new ol.style.Fill({ color: 'black' }); + labelStroke = new ol.style.Stroke({ color: 'rgba(0,0,0,0.7', width: 4 * globalScale }); + labelStrokeNarrow = new ol.style.Stroke({ color: 'rgba(0,0,0,0.7', width: 2.5 * globalScale }); + bgFill = new ol.style.Stroke({ color: 'rgba(0,0,0,0.25' }); } let lastCallLocationChange = 0; function onLocationChange(position) { @@ -6889,7 +7038,7 @@ function onLocationChange(position) { if (moveMap || lockDotCentered) { OLMap.getView().setCenter(ol.proj.fromLonLat([SiteLon, SiteLat])); } - console.log('Changed Site Location to: '+ SiteLat +', ' + SiteLon); + console.log('Changed Site Location to: ' + SiteLat + ', ' + SiteLon); //followRandomPlane(); //togglePersistence(); } @@ -6919,7 +7068,7 @@ function pollPositionInterval() { const geoposOptions = { enableHighAccuracy: false, timeout: pollPositionSeconds * 1000, - maximumAge: pollPositionSeconds * 1000 , + maximumAge: pollPositionSeconds * 1000, }; navigator.geolocation.getCurrentPosition(function(position) { onLocationChange(position); @@ -6961,7 +7110,7 @@ function geoFindMe() { } initSitePos(); - console.log('Location from browser: '+ SiteLat +', ' + SiteLon); + console.log('Location from browser: ' + SiteLat + ', ' + SiteLon); g.geoFindDefer.resolve(); @@ -6975,7 +7124,7 @@ function geoFindMe() { const geoposOptions = { enableHighAccuracy: false, timeout: 15 * 60 * 1000, - maximumAge: 5 * 60 * 1000 , + maximumAge: 5 * 60 * 1000, }; console.log('geoFindInterval: querying position'); navigator.geolocation.getCurrentPosition(onLocationChange, logArg, geoposOptions); @@ -7031,7 +7180,7 @@ function initSitePos() { if (initSitePosFirstRun) { initSitePosFirstRun = false; const sortBy = usp.get('sortBy'); - if (sortBy == "nosort" ) { + if (sortBy == "nosort") { // no sorting } else if (sortBy) { TAR.planeMan.ascending = true; @@ -7082,7 +7231,7 @@ function createLocationDot() { image: new ol.style.Circle({ radius: 7, snapToPixel: false, - fill: new ol.style.Fill({color: 'black'}), + fill: new ol.style.Fill({ color: 'black' }), stroke: new ol.style.Stroke({ color: 'white', width: 2 }) @@ -7176,11 +7325,11 @@ function drawUpintheair() { }) }); if (points.length > 0) { - geom = new ol.geom.LineString([[ points[0][1], points[0][0] ]]); + geom = new ol.geom.LineString([[points[0][1], points[0][0]]]); for (let j = 0; j < points.length; ++j) { - geom.appendCoordinate([ points[j][1], points[j][0] ]); + geom.appendCoordinate([points[j][1], points[j][0]]); } - geom.appendCoordinate([ points[0][1], points[0][0] ]); + geom.appendCoordinate([points[0][1], points[0][0]]); geom.transform('EPSG:4326', 'EPSG:3857'); let feature = new ol.Feature(geom); @@ -7191,10 +7340,12 @@ function drawUpintheair() { } function drawOutlineJson() { - let request = jQuery.ajax({ url: actualOutline.url, + let request = jQuery.ajax({ + url: actualOutline.url, cache: false, timeout: actualOutline.refresh, - dataType: 'json' }); + dataType: 'json' + }); request.done(function(data) { actualOutline.features.clear(); let points = []; @@ -7276,8 +7427,7 @@ function checkFollow() { } if (Math.abs(center[0] - proj[0]) > 1 || - Math.abs(center[1] - proj[1]) > 1) - { + Math.abs(center[1] - proj[1]) > 1) { toggleFollow(false); return false; } @@ -7286,7 +7436,7 @@ function checkFollow() { function everySecond() { if (traceRate > 0) - traceRate = traceRate * 0.985 - 1; + traceRate = traceRate * 0.985 - 1; updateIconCache(); } @@ -7332,8 +7482,8 @@ function getTrace(newPlane, hex, options) { URL2 = 'globe_history/' + zDateString(refDate).replace(/-/g, '/') + '/traces/' + hex.slice(-2) + '/trace_full_' + hex + '.json'; traceRate += 3; } else { - URL1 = 'data/traces/'+ hex.slice(-2) + '/trace_recent_' + hex + '.json'; - URL2 = 'data/traces/'+ hex.slice(-2) + '/trace_full_' + hex + '.json'; + URL1 = 'data/traces/' + hex.slice(-2) + '/trace_recent_' + hex + '.json'; + URL2 = 'data/traces/' + hex.slice(-2) + '/trace_full_' + hex + '.json'; traceRate += 2; } if (showTrace && trace_hist_only) { @@ -7369,7 +7519,8 @@ function getTrace(newPlane, hex, options) { options.defer = jQuery.Deferred(); if (URL1 && !options.onlyFull) { - jQuery.ajax({ url: `${URL1}`, + jQuery.ajax({ + url: `${URL1}`, dataType: 'json', options: options, }) @@ -7396,50 +7547,51 @@ function getTrace(newPlane, hex, options) { if (options.onlyRecent) return newPlane; - jQuery.ajax({ url: `${URL2}`, + jQuery.ajax({ + url: `${URL2}`, dataType: 'json', options: options, }) .done(function(data) { - const options = this.options; - const plane = g.planes[options.plane]; - plane.fullTrace = normalizeTraceStamps(data); - options.defer.done(function(options) { + const options = this.options; const plane = g.planes[options.plane]; - if (showTrace) { - legShift(0, plane); - if (!multiSelect && showTraceTimestamp) { - gotoTime(showTraceTimestamp); + plane.fullTrace = normalizeTraceStamps(data); + options.defer.done(function(options) { + const plane = g.planes[options.plane]; + if (showTrace) { + legShift(0, plane); + if (!multiSelect && showTraceTimestamp) { + gotoTime(showTraceTimestamp); + } + } else { + plane.processTrace(); + if (options.follow) + toggleFollow(true); } - } else { - plane.processTrace(); - if (options.follow) - toggleFollow(true); + }); + if (options.list) { + newPlane.updateLines(); + getTrace(null, null, options); } - }); - if (options.list) { - newPlane.updateLines(); - getTrace(null, null, options); - } - options.defer = null; - this.options = null; - }) + options.defer = null; + this.options = null; + }) .fail(function() { - const options = this.options; - const plane = g.planes[options.plane]; - if (showTrace) - legShift(0, plane); - else - plane.processTrace(); + const options = this.options; + const plane = g.planes[options.plane]; + if (showTrace) + legShift(0, plane); + else + plane.processTrace(); - if (options.list) { - getTrace(null, null, options); - } else { - plane.getAircraftData(); - refreshSelected(); - } - this.options = null; - }); + if (options.list) { + getTrace(null, null, options); + } else { + plane.getAircraftData(); + refreshSelected(); + } + this.options = null; + }); return newPlane; } @@ -7566,7 +7718,7 @@ function drawHeatmap() { let i = 0; if (!points) continue; - while(points[i] != 0xe7f7c9d && i < points.length) { + while (points[i] != 0xe7f7c9d && i < points.length) { index.push(points[i]); //console.log(points[i]); i += 4; @@ -7605,7 +7757,7 @@ function drawHeatmap() { for (; i < points.length; i += 4) { if (points[i] == 0xe7f7c9d) break; - let lat = points[i+1]; + let lat = points[i + 1]; if (lat > maxLat || lat < minLat) continue; @@ -7639,18 +7791,18 @@ function drawHeatmap() { let type = (pointsU[i] >> 27) & 0x1F; let dataSource; switch (type) { - case 0: dataSource = 'adsb'; break; - case 1: dataSource = 'modeS'; break; - case 2: dataSource = 'adsr'; break; - case 3: dataSource = 'tisb'; break; - case 4: dataSource = 'adsc'; break; - case 5: dataSource = 'mlat'; break; - case 6: dataSource = 'other'; break; - case 7: dataSource = 'modeS'; break; - case 8: dataSource = 'adsb'; break; - case 9: dataSource = 'adsr'; break; - case 10: dataSource = 'tisb'; break; - case 11: dataSource = 'tisb'; break; + case 0: dataSource = 'adsb'; break; + case 1: dataSource = 'modeS'; break; + case 2: dataSource = 'adsr'; break; + case 3: dataSource = 'tisb'; break; + case 4: dataSource = 'adsc'; break; + case 5: dataSource = 'mlat'; break; + case 6: dataSource = 'other'; break; + case 7: dataSource = 'modeS'; break; + case 8: dataSource = 'adsb'; break; + case 9: dataSource = 'adsr'; break; + case 10: dataSource = 'tisb'; break; + case 11: dataSource = 'tisb'; break; default: dataSource = 'unknown'; } let hex = (pointsU[i] & 0xFFFFFF).toString(16).padStart(6, '0'); @@ -7820,7 +7972,7 @@ function loadReplay(ts) { replay.abortController = new AbortController(); - jQuery("#update_error").css('display','none'); + jQuery("#update_error").css('display', 'none'); clearTimeout(replay.errorTimeout); const ff = () => { @@ -7836,8 +7988,8 @@ function loadReplay(ts) { return; } jQuery("#update_error_detail").text(error.message + ' --> No data for this timestamp!'); - jQuery("#update_error").css('display','block'); - replay.errorTimeout = setTimeout(() => { jQuery("#update_error").css('display','none'); }, 5000); + jQuery("#update_error").css('display', 'block'); + replay.errorTimeout = setTimeout(() => { jQuery("#update_error").css('display', 'none'); }, 5000); }; fetch(chunk.url, { signal: replay.abortController.signal }) @@ -7855,12 +8007,12 @@ function loadReplay(ts) { ff(); }) .catch(errorFunc) - ; + ; }, errorFunc ) - .catch( errorFunc ) - ; + .catch(errorFunc) + ; } } @@ -7898,7 +8050,7 @@ function initReplay(chunk, data) { replay.ival = (replay.pointsU[replay.slices[0] + 3] & 65535) / 1000; replay.halfHour = (replay.ts.getUTCMinutes() >= 30) ? 1 : 0; - let index = Math.round (((replay.ts.getUTCMinutes() % 30) * 60 + replay.ts.getUTCSeconds()) / replay.ival); + let index = Math.round(((replay.ts.getUTCMinutes() % 30) * 60 + replay.ts.getUTCSeconds()) / replay.ival); //console.log("init with index" + replay.index); if (index > 0) { if (false && index > 1) { @@ -8067,9 +8219,9 @@ function replayStep(arg) { let lon = points[i + 2]; let pos = [lon, lat]; if (lat >= 1073741824) { - let hex = (points[i] & ((1<<24) - 1)).toString(16).padStart(6, '0'); - hex = (points[i] & (1<<24)) ? ('~' + hex) : hex; - let ac = {hex:hex, seen:0, seen_pos:0,}; + let hex = (points[i] & ((1 << 24) - 1)).toString(16).padStart(6, '0'); + hex = (points[i] & (1 << 24)) ? ('~' + hex) : hex; + let ac = { hex: hex, seen: 0, seen_pos: 0, }; if (replay.pointsU8[4 * (i + 2)] != 0) { ac.flight = ""; for (let j = 0; j < 8; j++) { @@ -8094,19 +8246,19 @@ function replayStep(arg) { let type = (pointsU[i] >> 27) & 0x1F; switch (type) { - case 0: type = 'adsb_icao'; break; - case 1: type = 'adsb_icao_nt'; break; - case 2: type = 'adsr_icao'; break; - case 3: type = 'tisb_icao'; break; - case 4: type = 'adsc'; break; - case 5: type = 'mlat'; break; - case 6: type = 'other'; break; - case 7: type = 'mode_s'; break; - case 8: type = 'adsb_other'; break; - case 9: type = 'adsr_other'; break; - case 10: type = 'tisb_trackfile'; break; - case 11: type = 'tisb_other'; break; - case 12: type = 'mode_ac'; break; + case 0: type = 'adsb_icao'; break; + case 1: type = 'adsb_icao_nt'; break; + case 2: type = 'adsr_icao'; break; + case 3: type = 'tisb_icao'; break; + case 4: type = 'adsc'; break; + case 5: type = 'mlat'; break; + case 6: type = 'other'; break; + case 7: type = 'mode_s'; break; + case 8: type = 'adsb_other'; break; + case 9: type = 'adsr_other'; break; + case 10: type = 'tisb_trackfile'; break; + case 11: type = 'tisb_other'; break; + case 12: type = 'mode_ac'; break; default: type = 'unknown'; } let hex = (pointsU[i] & 0xFFFFFF).toString(16).padStart(6, '0'); @@ -8165,7 +8317,7 @@ function replayStep(arg) { function updateIconCache() { let item; let tryAgain = []; - while(item = addToIconCache.pop()) { + while (item = addToIconCache.pop()) { let svgKey = item[0]; let element = item[1]; if (iconCache[svgKey] != undefined) { @@ -8234,20 +8386,20 @@ function updateMessageRate(data) { MessageRate = data.messageRate; } else if (data.messages && data.messages > 1) { // Detect stats reset - if (MessageCountHistory.length > 0 && MessageCountHistory[MessageCountHistory.length-1].messages > data.messages) { + if (MessageCountHistory.length > 0 && MessageCountHistory[MessageCountHistory.length - 1].messages > data.messages) { MessageCountHistory = []; } // Note the message count in the history - MessageCountHistory.push({ 'time' : data.now, 'messages' : data.messages}); + MessageCountHistory.push({ 'time': data.now, 'messages': data.messages }); if (MessageCountHistory.length > 1) { // .. and clean up any old values while ((now - MessageCountHistory[0].time) > 10.5) { MessageCountHistory.shift(); } - let message_time_delta = MessageCountHistory[MessageCountHistory.length-1].time - MessageCountHistory[0].time; - let message_count_delta = MessageCountHistory[MessageCountHistory.length-1].messages - MessageCountHistory[0].messages; + let message_time_delta = MessageCountHistory[MessageCountHistory.length - 1].time - MessageCountHistory[0].time; + let message_count_delta = MessageCountHistory[MessageCountHistory.length - 1].messages - MessageCountHistory[0].messages; if (message_time_delta > 0) { MessageRate = message_count_delta / message_time_delta; } @@ -8261,7 +8413,7 @@ function updateMessageRate(data) { let newCache = uuidCache[data.urlIndex] = { now: now }; let message_delta = 0; let acs = data.aircraft; - for (let j=0; j < acs.length; j++) { + for (let j = 0; j < acs.length; j++) { const hex = acs[j].hex; const messages = acs[j].messages let cachedMessages = cache[hex]; @@ -8281,8 +8433,8 @@ function updateMessageRate(data) { MessageRate = null; } } -function playReplay(state){ - if (!replay){ +function playReplay(state) { + if (!replay) { return; } if (state) { @@ -8300,17 +8452,17 @@ function playReplay(state){ } }; -function showReplayBar(){ +function showReplayBar() { console.log('showReplayBar()'); showingReplayBar = !showingReplayBar; - if (!showingReplayBar){ + if (!showingReplayBar) { jQuery("#replayBar").hide(); clearTimeout(refreshId); replay = null; jQuery('#map_canvas').height('100%'); jQuery('#sidebar_canvas').height('100%'); jQuery("#selected_showTrace_hide").show(); - fetchData({force: true}); + fetchData({ force: true }); } else { jQuery("#replayBar").show(); jQuery("#replayBar").css('display', 'grid'); @@ -8331,10 +8483,10 @@ function showReplayBar(){ }, }; if (onMobile) { - datepickerOptions.onClose = function(dateText, inst){ + datepickerOptions.onClose = function(dateText, inst) { jQuery("replayDatepicker").attr("disabled", false); }; - datepickerOptions.beforeShow = function(input, inst){ + datepickerOptions.beforeShow = function(input, inst) { jQuery("replayDatepicker").attr("disabled", true); }; } else { @@ -8430,7 +8582,7 @@ function refreshHistory() { chunkNames = []; jQuery("#loader_progress").attr('value', 1); try { - for (let i = data.chunks.length-1; i >= 0; i--) { + for (let i = data.chunks.length - 1; i >= 0; i--) { let f = data.chunks[i]; chunkNames.push(f); @@ -8441,7 +8593,7 @@ function refreshHistory() { let parts = f.split(".")[0].split("_"); if (parts[0] == "chunk") { - if (now > parts[1]/1e3) { + if (now > parts[1] / 1e3) { break; } } @@ -8482,7 +8634,7 @@ function handleVisibilityChange() { // tab is no longer hidden if (!tabHidden && !timersActive) { - loadFinished && jQuery("#timers_paused").css('display','none'); + loadFinished && jQuery("#timers_paused").css('display', 'none'); globeRateUpdate(); if (heatmap || replay || globeIndex || pTracks) { noLongerHidden(); @@ -8552,13 +8704,13 @@ function initVisibilityChange() { } // for debugging visibilitychange: function testHide() { - Object.defineProperty(window.document,'hidden',{get:function(){return true;},configurable:true}); - Object.defineProperty(window.document,'visibilityState',{get:function(){return 'hidden';},configurable:true}); + Object.defineProperty(window.document, 'hidden', { get: function() { return true; }, configurable: true }); + Object.defineProperty(window.document, 'visibilityState', { get: function() { return 'hidden'; }, configurable: true }); window.document.dispatchEvent(new Event('visibilitychange')); } function testUnhide() { - Object.defineProperty(window.document,'hidden',{get:function(){return false;},configurable:true}); - Object.defineProperty(window.document,'visibilityState',{get:function(){return 'visible';},configurable:true}); + Object.defineProperty(window.document, 'hidden', { get: function() { return false; }, configurable: true }); + Object.defineProperty(window.document, 'visibilityState', { get: function() { return 'visible'; }, configurable: true }); window.document.dispatchEvent(new Event('visibilitychange')); } @@ -8578,7 +8730,7 @@ function autoSelectClosest() { continue; let refLoc = [CenterLon, CenterLat]; if (autoselectCoords && autoselectCoords.length == 2) { - refLoc = [ autoselectCoords[1], autoselectCoords[0] ]; + refLoc = [autoselectCoords[1], autoselectCoords[0]]; } const dist = ol.sphere.getDistance(refLoc, plane.position); if (dist == null || isNaN(dist)) @@ -8590,7 +8742,7 @@ function autoSelectClosest() { } if (!closest) return; - selectPlaneByHex(closest.icao, {noDeselect: true, follow: FollowSelected,}); + selectPlaneByHex(closest.icao, { noDeselect: true, follow: FollowSelected, }); } function setAutoselect() { clearInterval(timers.autoselect); @@ -8600,7 +8752,7 @@ function setAutoselect() { autoSelectClosest(); } function registrationLink(plane) { - + const countryLinks = { Brazil: (reg) => `https://sistemas.anac.gov.br/aeronaves/cons_rab_resposta_en.asp?textMarca=${reg}`, Australia: (reg) => `https://www.casa.gov.au/search-centre/aircraft-register?reg=${reg.replace(/^VH-/, '')}`, @@ -8617,7 +8769,7 @@ function registrationLink(plane) { //simple jquery plugin to only update the text when it changes -jQuery.fn.updateText = function (text) { +jQuery.fn.updateText = function(text) { this.text() !== String(text) && this.text(text); } @@ -8743,7 +8895,7 @@ function coordsForExport(plane) { continue; } //console.log(`exporting coord: ${i} ${pos} ${alt} ${ts}`); - coords.push({ pos: pos, alt: alt, ts: ts}); + coords.push({ pos: pos, alt: alt, ts: ts }); } else { console.log(`Skipping ${i}`); } @@ -8977,7 +9129,7 @@ function _printTrace(trace) { const timestamp = state[0]; let stale = state[6] & 1; const leg_marker = state[6] & 2; - console.log(zuluTime(new Date(timestamp * 1000)) + ' ' + (state[1] + ',' + state[2]).padStart(26, ' ') + ' ' + String(state[3]).padStart(6, ' ') + ' ' + state[6]); + console.log(zuluTime(new Date(timestamp * 1000)) + ' ' + (state[1] + ',' + state[2]).padStart(26, ' ') + ' ' + String(state[3]).padStart(6, ' ') + ' ' + state[6]); } } @@ -9017,7 +9169,7 @@ function setSelectedIcao() { } jQuery('#selected_icao').html(hex_html); - jQuery('a.identSmall').prop('href',shareLink); + jQuery('a.identSmall').prop('href', shareLink); } function mapTypeSettings() { @@ -9086,7 +9238,7 @@ Please add a disclaimer to any screenshots of this website or better yet just re } function getn(n) { - limitUpdates=n; RefreshInterval=0; fetchCalls=0; + limitUpdates = n; RefreshInterval = 0; fetchCalls = 0; } function onAltimeterSetStandard(e) { @@ -9133,7 +9285,7 @@ function adjust_baro_alt(alt) { } let station_pressure = Math.pow(1 - alt / 145366.45, 5.2553026) * 1013.25; - let res = ( 1 - Math.pow(station_pressure / baroCorrectQNH, 0.190284) ) * 145366.45; + let res = (1 - Math.pow(station_pressure / baroCorrectQNH, 0.190284)) * 145366.45; return res; } @@ -9143,12 +9295,12 @@ function globeRateUpdate() { if (0) { const cookieExp = getCookie('asdf_id').split('_')[0]; const ts = new Date().getTime(); - if (!cookieExp || cookieExp < ts + 3600*1000) - setCookie('adsbx_sid', ((ts + 2*86400*1000) + '_' + Math.random().toString(36).substring(2, 15)), 2); + if (!cookieExp || cookieExp < ts + 3600 * 1000) + setCookie('adsbx_sid', ((ts + 2 * 86400 * 1000) + '_' + Math.random().toString(36).substring(2, 15)), 2); } } if (dynGlobeRate) { - return jQuery.ajax({url:'/globeRates.json', cache: false, dataType: 'json', }).done(function(data) { + return jQuery.ajax({ url: '/globeRates.json', cache: false, dataType: 'json', }).done(function(data) { if (data.simload != null) globeSimLoad = data.simload; if (data.refresh != null && globeIndex)
      ' + field.label + ':' + display + '
      ', }; cols.country = { text: 'Flag', header: function() { return ""; }, - sort: function () { sortBy('country', compareAlpha, function(x) { return x.country; }); }, + sort: function() { sortBy('country', compareAlpha, function(x) { return x.country; }); }, value: function(plane) { return (plane.country_code ? ('') : ''); }, hStyle: 'style="width: 18px; padding: 3px;"', html: true, }; cols.flight = { - sort: function () { sortBy('flight', compareAlpha, function(x) { return x.flight }); }, + sort: function() { sortBy('flight', compareAlpha, function(x) { return x.flight }); }, value: function(plane) { if (flightawareLinks) return getFlightAwareModeSLink(plane.icao, plane.flight, plane.name); return (plane.flight || ''); }, html: flightawareLinks, - text: 'Callsign' }; + text: 'Callsign' + }; if (routeApiUrl) { cols.route = { - sort: function () { sortBy('route', compareAlpha, function(x) { return x.routeColumn }); }, + sort: function() { sortBy('route', compareAlpha, function(x) { return x.routeColumn }); }, value: function(plane) { if (!useRouteAPI) return ''; if (plane.routeString) { @@ -4041,101 +4175,114 @@ function refreshFeatures() { } }, html: true, - text: 'Route' }; + text: 'Route' + }; } cols.registration = { - sort: function () { sortBy('registration', compareAlpha, function(x) { return x.registration; }); }, + sort: function() { sortBy('registration', compareAlpha, function(x) { return x.registration; }); }, value: function(plane) { return (flightawareLinks ? getFlightAwareIdentLink(plane.registration, plane.registration) : (plane.registration ? plane.registration : "")); }, html: flightawareLinks, - text: 'Registration' }; + text: 'Registration' + }; cols.type = { - sort: function () { sortBy('type', compareAlpha, function(x) { return x.icaoType; }); }, + sort: function() { sortBy('type', compareAlpha, function(x) { return x.icaoType; }); }, value: function(plane) { return (plane.icaoType != null ? plane.icaoType : ""); }, - text: 'Type' }; + text: 'Type' + }; cols.squawk = { text: 'Squawk', - sort: function () { sortBy('squawk', compareAlpha, function(x) { return x.squawk; }); }, + sort: function() { sortBy('squawk', compareAlpha, function(x) { return x.squawk; }); }, value: function(plane) { return (plane.squawk != null ? plane.squawk : ""); }, - align: 'right' }; + align: 'right' + }; cols.altitude = { text: 'Altitude', - sort: function () { sortBy('altitude',compareNumeric, function(x) { return (x.altitude == "ground" ? -100000 : x.altitude); }); }, + sort: function() { sortBy('altitude', compareNumeric, function(x) { return (x.altitude == "ground" ? -100000 : x.altitude); }); }, value: function(plane) { return format_altitude_brief(adjust_baro_alt(plane.altitude), plane.vert_rate, DisplayUnits); }, align: 'right', - header: function () { return 'Alt.' + NBSP + '(' + get_unit_label("altitude", DisplayUnits) + ')';}, + header: function() { return 'Alt.' + NBSP + '(' + get_unit_label("altitude", DisplayUnits) + ')'; }, }; cols.speed = { text: pTracks ? 'Max. Speed' : 'Speed', - sort: function () { sortBy('speed', compareNumeric, function(x) { return x.speed; }); }, + sort: function() { sortBy('speed', compareNumeric, function(x) { return x.speed; }); }, value: function(plane) { return format_speed_brief(plane.speed, DisplayUnits); }, align: 'right', - header: function () { return (pTracks ? 'Max. ' : '') + 'Spd.' + NBSP + '(' + get_unit_label("speed", DisplayUnits) + ')';}, + header: function() { return (pTracks ? 'Max. ' : '') + 'Spd.' + NBSP + '(' + get_unit_label("speed", DisplayUnits) + ')'; }, }; cols.vert_rate = { text: 'Vertical Rate', - sort: function () { sortBy('vert_rate', compareNumeric, function(x) { return x.vert_rate; }); }, + sort: function() { sortBy('vert_rate', compareNumeric, function(x) { return x.vert_rate; }); }, value: function(plane) { return format_vert_rate_brief(plane.vert_rate, DisplayUnits); }, align: 'right', - header: function () { return 'V. Rate(' + get_unit_label("verticalRate", DisplayUnits) + ')';}, + header: function() { return 'V. Rate(' + get_unit_label("verticalRate", DisplayUnits) + ')'; }, }; cols.sitedist = { text: pTracks ? 'Max. Distance' : 'Distance', - sort: function () { sortBy('sitedist',compareNumeric, function(x) { return x.sitedist; }); }, + sort: function() { sortBy('sitedist', compareNumeric, function(x) { return x.sitedist; }); }, value: function(plane) { return format_distance_brief(plane.sitedist, DisplayUnits); }, align: 'right', - header: function () { return (pTracks ? 'Max. ' : '') + 'Dist.' + NBSP + '(' + get_unit_label("distance", DisplayUnits) + ')';}, + header: function() { return (pTracks ? 'Max. ' : '') + 'Dist.' + NBSP + '(' + get_unit_label("distance", DisplayUnits) + ')'; }, }; cols.track = { text: 'Track', - sort: function () { sortBy('track', compareNumeric, function(x) { return x.track; }); }, + sort: function() { sortBy('track', compareNumeric, function(x) { return x.track; }); }, value: function(plane) { return format_track_brief(plane.track); }, - align: 'right' }; + align: 'right' + }; cols.msgs = { text: 'Messages', - sort: function () { sortBy('msgs', compareNumeric, function(x) { return x.messages; }); }, + sort: function() { sortBy('msgs', compareNumeric, function(x) { return x.messages; }); }, value: function(plane) { return plane.messages; }, - align: 'right' }; + align: 'right' + }; cols.seen = { text: 'Seen', - sort: function () { sortBy('seen', compareNumeric, function(x) { return x.seen; }); }, + sort: function() { sortBy('seen', compareNumeric, function(x) { return x.seen; }); }, value: function(plane) { return plane.seen.toFixed(0); }, - align: 'right' }; + align: 'right' + }; cols.rssi = { text: 'RSSI', - sort: function () { sortBy('rssi', compareNumeric, function(x) { return x.rssi; }); }, + sort: function() { sortBy('rssi', compareNumeric, function(x) { return x.rssi; }); }, value: function(plane) { return (plane.rssi != null ? plane.rssi.toFixed(1) : ""); }, - align: 'right' }; + align: 'right' + }; cols.lat = { text: 'Latitude', - sort: function () { sortBy('lat', compareNumeric, function(x) { return (x.position !== null ? x.position[1] : null); }); }, + sort: function() { sortBy('lat', compareNumeric, function(x) { return (x.position !== null ? x.position[1] : null); }); }, value: function(plane) { return (plane.position != null ? plane.position[1].toFixed(4) : ""); }, - align: 'right' }; + align: 'right' + }; cols.lon = { text: 'Longitude', - sort: function () { sortBy('lon', compareNumeric, function(x) { return (x.position !== null ? x.position[0] : null); }); }, + sort: function() { sortBy('lon', compareNumeric, function(x) { return (x.position !== null ? x.position[0] : null); }); }, value: function(plane) { return (plane.position != null ? plane.position[0].toFixed(4) : ""); }, - align: 'right' }; + align: 'right' + }; cols.data_source = { text: 'Source', - sort: function () { sortBy('data_source', compareNumeric, function(x) { return x.getDataSourceNumber(); } ); }, + sort: function() { sortBy('data_source', compareNumeric, function(x) { return x.getDataSourceNumber(); }); }, value: function(plane) { return format_data_source(plane.getDataSource()); }, - align: 'right' }; + align: 'right' + }; cols.military = { text: 'Mil.', - sort: function () { sortBy('military', compareAlpha, function(x) { return (x.military ? 'yes' : 'no'); } ); }, + sort: function() { sortBy('military', compareAlpha, function(x) { return (x.military ? 'yes' : 'no'); }); }, value: function(plane) { return (plane.military ? 'yes' : 'no'); }, - align: 'right' }; + align: 'right' + }; cols.wd = { text: 'Wind D.', - sort: function () { sortBy('wd', compareNumeric, function(x) { return x.wd; }); }, + sort: function() { sortBy('wd', compareNumeric, function(x) { return x.wd; }); }, value: function(plane) { return plane.wd != null ? (plane.wd + '°') : ''; }, - align: 'right' }; + align: 'right' + }; cols.ws = { text: 'Wind S.', - sort: function () { sortBy('ws', compareNumeric, function(x) { return x.ws; }); }, + sort: function() { sortBy('ws', compareNumeric, function(x) { return x.ws; }); }, value: function(plane) { return format_speed_brief(plane.ws, DisplayUnits); }, align: 'right', - header: function () { return 'Wind' + NBSP + '(' + get_unit_label("speed", DisplayUnits) + ')'; }, + header: function() { return 'Wind' + NBSP + '(' + get_unit_label("speed", DisplayUnits) + ')'; }, }; const colsEntries = Object.entries(cols); @@ -4159,7 +4306,7 @@ function refreshFeatures() { let htmlTable = null; let tbody = null; - planeMan.init = function () { + planeMan.init = function() { // initialize columns htmlTable = document.getElementById('planesTable'); for (let i in columns) { @@ -4179,7 +4326,7 @@ function refreshFeatures() { } } - planeMan.redraw = function () { + planeMan.redraw = function() { activeCols = []; for (let i in columns) { let col = columns[i]; @@ -4195,7 +4342,7 @@ function refreshFeatures() { table += '
      '+ col.header() +'' + col.header() + '
      ` - + '
      Filter by '+ this.name +':
      ' + + '
      Filter by ' + this.name + ':
      ' + `` + '' + `` + '