Merge remote-tracking branch 'csfa/map_enhancements' into dev

This commit is contained in:
Oliver Jowett 2016-09-14 17:47:44 +01:00
commit 7dee192157
15 changed files with 1139 additions and 334 deletions

View file

@ -11,14 +11,11 @@ PlaneCountInTitle = true;
MessageRateInTitle = false; MessageRateInTitle = false;
// -- Output Settings ------------------------------------- // -- Output Settings -------------------------------------
// Show metric values // The DisplayUnits setting controls whether nautical (ft, NM, knots),
// The Metric setting controls whether metric (m, km, km/h) or // metric (m, km, km/h) or imperial (ft, mi, mph) units are used in the
// imperial (ft, NM, knots) units are used in the plane table // plane table and in the detailed plane info. Valid values are
// and in the detailed plane info. If ShowOtherUnits is true, // "nautical", "metric", or "imperial".
// then the other unit will also be shown in the detailed plane DisplayUnits = "nautical";
// info.
Metric = false;
ShowOtherUnits = true;
// -- Map settings ---------------------------------------- // -- Map settings ----------------------------------------
// These settings are overridden by any position information // These settings are overridden by any position information
@ -98,7 +95,7 @@ OutlineADSBColor = '#000000';
OutlineMlatColor = '#4040FF'; OutlineMlatColor = '#4040FF';
SiteCircles = true; // true to show circles (only shown if the center marker is shown) SiteCircles = true; // true to show circles (only shown if the center marker is shown)
// In nautical miles or km (depending settings value 'Metric') // In miles, nautical miles, or km (depending settings value 'DisplayUnits')
SiteCirclesDistances = new Array(100,150,200); SiteCirclesDistances = new Array(100,150,200);
// Show the clocks at the top of the righthand pane? You can disable the clocks if you want here // Show the clocks at the top of the righthand pane? You can disable the clocks if you want here

View file

@ -0,0 +1,3 @@
Aircraft type data was retrieved from http://www.icao.int/publications/DOC8643/ and converted to JSON format.
Data was last updated on 2016-03-23 and retrieved on 2016-08-25.

File diff suppressed because one or more lines are too long

View file

@ -22,6 +22,7 @@
"use strict"; "use strict";
var _aircraft_cache = {}; var _aircraft_cache = {};
var _aircraft_type_cache = null;
function getAircraftData(icao) { function getAircraftData(icao) {
var defer; var defer;
@ -48,7 +49,7 @@ function request_from_db(icao, level, defer) {
var subkey; var subkey;
if (dkey in data) { if (dkey in data) {
defer.resolve(data[dkey]); getIcaoAircraftTypeData(data[dkey], defer);
return; return;
} }
@ -67,6 +68,36 @@ function request_from_db(icao, level, defer) {
}); });
} }
function getIcaoAircraftTypeData(aircraftData, defer) {
if (_aircraft_type_cache === null) {
$.getJSON("db/aircraft_types/icao_aircraft_types.json")
.done(function(typeLookupData) {
_aircraft_type_cache = typeLookupData;
})
.always(function() {
lookupIcaoAircraftType(aircraftData, defer);
});
}
else {
lookupIcaoAircraftType(aircraftData, defer);
}
}
function lookupIcaoAircraftType(aircraftData, defer) {
if (_aircraft_type_cache !== null && "t" in aircraftData) {
var typeDesignator = aircraftData.t.toUpperCase();
if (typeDesignator in _aircraft_type_cache) {
var typeData = _aircraft_type_cache[typeDesignator];
if (typeData.desc != undefined && typeData.desc != null && typeData.desc.length == 3) {
aircraftData.desc = typeData.desc;
}
aircraftData.wtc = typeData.wtc;
}
}
defer.resolve(aircraftData);
}
var _request_count = 0; var _request_count = 0;
var _request_queue = []; var _request_queue = [];
var _request_cache = {}; var _request_cache = {};

View file

@ -8,8 +8,23 @@ var DOWN_TRIANGLE='\u25bc'; // U+25BC BLACK DOWN-POINTING TRIANGLE
var TrackDirections = ["North","Northeast","East","Southeast","South","Southwest","West","Northwest"]; var TrackDirections = ["North","Northeast","East","Southeast","South","Southwest","West","Northwest"];
var UnitLabels = {
'altitude': { metric: "m", imperial: "ft", nautical: "ft"},
'speed': { metric: "km/h", imperial: "mph", nautical: "kt" },
'distance': { metric: "km", imperial: "mi", nautical: "NM" },
'verticalRate': { metric: "m/s", imperial: "ft/min", nautical: "ft/min" }
};
// formatting helpers // formatting helpers
function get_unit_label(quantity, systemOfMeasurement) {
var labels = UnitLabels[quantity];
if (labels !== undefined && labels[systemOfMeasurement] !== undefined) {
return labels[systemOfMeasurement];
}
return "";
}
// track in degrees (0..359) // track in degrees (0..359)
function format_track_brief(track) { function format_track_brief(track) {
if (track === null){ if (track === null){
@ -29,9 +44,8 @@ function format_track_long(track) {
return Math.round(track) + DEGREES + NBSP + "(" + TrackDirections[trackDir] + ")"; return Math.round(track) + DEGREES + NBSP + "(" + TrackDirections[trackDir] + ")";
} }
// altitude (input: alt in feet) // alt in feet
// brief will always show either Metric or Imperial function format_altitude_brief(alt, vr, displayUnits) {
function format_altitude_brief(alt, vr) {
var alt_text; var alt_text;
if (alt === null){ if (alt === null){
@ -40,31 +54,24 @@ function format_altitude_brief(alt, vr) {
return "ground"; return "ground";
} }
if (Metric) { alt_text = Math.round(convert_altitude(alt, displayUnits)) + NBSP;
alt_text = Math.round(alt / 3.2828) + NBSP; // Altitude to meters
} else {
alt_text = Math.round(alt) + NBSP;
}
// Vertical Rate Triangle // Vertical Rate Triangle
var verticalRateTriangle = "<span class=\"verticalRateTriangle\">";
if (vr > 128){ if (vr > 128){
return alt_text + UP_TRIANGLE; verticalRateTriangle += UP_TRIANGLE;
} else if (vr < -128){ } else if (vr < -128){
return alt_text + DOWN_TRIANGLE; verticalRateTriangle += DOWN_TRIANGLE;
} else { } else {
return alt_text + NBSP; verticalRateTriangle += NBSP;
} }
verticalRateTriangle += "</span>"
return alt_text + verticalRateTriangle;
} }
// alt in ft // alt in feet
function _alt_to_unit(alt, m) { function format_altitude_long(alt, vr, displayUnits) {
if (m)
return Math.round(alt / 3.2828) + NBSP + "m";
else
return Math.round(alt) + NBSP + "ft";
}
function format_altitude_long(alt, vr) {
var alt_text = ""; var alt_text = "";
if (alt === null) { if (alt === null) {
@ -73,13 +80,7 @@ function format_altitude_long(alt, vr) {
return "on ground"; return "on ground";
} }
// Primary unit alt_text = Math.round(convert_altitude(alt, displayUnits)) + NBSP + get_unit_label("altitude", displayUnits);
alt_text = _alt_to_unit(alt, Metric);
// Secondary unit
if (ShowOtherUnits) {
alt_text = alt_text + ' | ' + _alt_to_unit(alt, !Metric);
}
if (vr > 128) { if (vr > 128) {
return UP_TRIANGLE + NBSP + alt_text; return UP_TRIANGLE + NBSP + alt_text;
@ -90,83 +91,123 @@ function format_altitude_long(alt, vr) {
} }
} }
//input: speed in kts // alt in feet
function format_speed_brief(speed) { function convert_altitude(alt, displayUnits) {
if (displayUnits === "metric") {
return alt / 3.2808; // feet to meters
}
return alt;
}
// speed in knots
function format_speed_brief(speed, displayUnits) {
if (speed === null) { if (speed === null) {
return ""; return "";
} }
if (Metric) { return Math.round(convert_speed(speed, displayUnits));
return Math.round(speed * 1.852); // knots to kilometers per hour
} else {
return Math.round(speed); // knots
}
} }
// speed in kts // speed in knots
function format_speed_long(speed, displayUnits) {
function _speed_to_unit(speed, m) {
if (m)
return Math.round(speed * 1.852) + NBSP + "km/h";
else
return Math.round(speed) + NBSP + "kt";
}
function format_speed_long(speed) {
if (speed === null) { if (speed === null) {
return "n/a"; return "n/a";
} }
// Primary unit var speed_text = Math.round(convert_speed(speed, displayUnits)) + NBSP + get_unit_label("speed", displayUnits);
var speed_text = _speed_to_unit(speed, Metric);
// Secondary unit
if (ShowOtherUnits) {
speed_text = speed_text + ' | ' + _speed_to_unit(speed, !Metric);
}
return speed_text; return speed_text;
} }
// speed in knots
function convert_speed(speed, displayUnits) {
if (displayUnits === "metric") {
return speed * 1.852; // knots to kilometers per hour
}
else if (displayUnits === "imperial") {
return speed * 1.151; // knots to miles per hour
}
return speed;
}
// dist in meters // dist in meters
function format_distance_brief(dist) { function format_distance_brief(dist, displayUnits) {
if (dist === null) { if (dist === null) {
return ""; return "";
} }
if (Metric) { return convert_distance(dist, displayUnits).toFixed(1);
return (dist/1000).toFixed(1); // meters to kilometers
} else {
return (dist/1852).toFixed(1); // meters to nautocal miles
}
} }
// dist in metres // dist in meters
function format_distance_long(dist, displayUnits) {
function _dist_to_unit(dist, m) {
if (m)
return (dist/1000).toFixed(1) + NBSP + "km";
else
return (dist/1852).toFixed(1) + NBSP + "NM";
}
function format_distance_long(dist) {
if (dist === null) { if (dist === null) {
return "n/a"; return "n/a";
} }
// Primary unit var dist_text = convert_distance(dist, displayUnits).toFixed(1) + NBSP + get_unit_label("distance", displayUnits);
var dist_text = _dist_to_unit(dist, Metric);
// Secondary unit
if (ShowOtherUnits) {
dist_text = dist_text + ' | ' + _dist_to_unit(dist, !Metric);
}
return dist_text; return dist_text;
} }
// dist in meters
function convert_distance(dist, displayUnits) {
if (displayUnits === "metric") {
return (dist / 1000); // meters to kilometers
}
else if (displayUnits === "imperial") {
return (dist / 1609); // meters to miles
}
return (dist / 1852); // meters to nautical miles
}
// rate in ft/min
function format_vert_rate_brief(rate, displayUnits) {
if (rate === null || rate === undefined) {
return "";
}
return convert_vert_rate(rate, displayUnits).toFixed(displayUnits === "metric" ? 1 : 0);
}
// rate in ft/min
function format_vert_rate_long(rate, displayUnits) {
if (rate === null || rate === undefined) {
return "n/a";
}
var rate_text = convert_vert_rate(rate, displayUnits).toFixed(displayUnits === "metric" ? 1 : 0) + NBSP + get_unit_label("verticalRate", displayUnits);
return rate_text;
}
// rate in ft/min
function convert_vert_rate(rate, displayUnits) {
if (displayUnits === "metric") {
return (rate / 196.85); // ft/min to m/s
}
return rate;
}
// p is a [lon, lat] coordinate // p is a [lon, lat] coordinate
function format_latlng(p) { function format_latlng(p) {
return p[1].toFixed(3) + DEGREES + "," + NBSP + p[0].toFixed(3) + DEGREES; return p[1].toFixed(3) + DEGREES + "," + NBSP + p[0].toFixed(3) + DEGREES;
} }
function format_data_source(source) {
switch (source) {
case 'mlat':
return "MLAT";
case 'adsb':
return "ADS-B";
case 'mode_s':
return "Mode S";
case 'mode_ac':
return "Mode A/C";
}
return "";
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -6,6 +6,7 @@
<link rel="stylesheet" href="jquery/jquery-ui-1.11.4-smoothness.css" /> <link rel="stylesheet" href="jquery/jquery-ui-1.11.4-smoothness.css" />
<script src="jquery/jquery-3.0.0.min.js"></script> <script src="jquery/jquery-3.0.0.min.js"></script>
<script src="jquery/jquery-ui-1.11.4.min.js"></script> <script src="jquery/jquery-ui-1.11.4.min.js"></script>
<script src="jquery/plugins/jquery.validate.min.js"></script>
<link rel="stylesheet" href="ol3/ol-3.17.1.css" type="text/css" /> <link rel="stylesheet" href="ol3/ol-3.17.1.css" type="text/css" />
<script src="ol3/ol-3.17.1.js" type="text/javascript"></script> <script src="ol3/ol-3.17.1.js" type="text/javascript"></script>
@ -45,60 +46,7 @@
<input type="hidden" name="submit" value="submit"> <input type="hidden" name="submit" value="submit">
</form> </form>
<div id="map_container"> <div id="layout_container">
<div id="map_canvas"></div>
</div>
<div id="sidebar_container">
<div id="sidebar_canvas">
<div id="sudo_buttons">
<table style="width: 100%">
<tr>
<td style="width: 150px; text-align: center;" class="pointer">
[ <span onclick="resetMap();">Reset Map</span> ]
</td>
</tr>
</table>
</div> <!-- sudo_buttons -->
<div id="dump1090_infoblock">
<table style="width: 100%">
<tr class="infoblock_heading">
<td>
<b id="infoblock_name">FlightAware dump1090</b>
</td>
<td style="text-align: right">
<a href="https://github.com/flightaware/dump1090" id="dump1090_version" target="_blank"></a>
</td>
</tr>
<tr class="infoblock_body">
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr class="infoblock_body dim">
<td>(no aircraft selected)</td>
<td>&nbsp;</td>
</tr>
<tr class="infoblock_body">
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr class="infoblock_body">
<td>Aircraft (total): <span id="dump1090_total_ac">n/a</span></td>
<td>Messages: <span id="dump1090_message_rate">n/a</span>/sec</td>
</tr>
<tr class="infoblock_body">
<td>(with positions): <span id="dump1090_total_ac_positions">n/a</span></td>
<td>History: <span id="dump1090_total_history">n/a</span> positions</td>
</tr>
</table>
</div> <!-- dump1090_infoblock -->
<div id="selected_infoblock" class="hidden"> <div id="selected_infoblock" class="hidden">
<table style="width: 100%"> <table style="width: 100%">
<tr class="infoblock_heading"> <tr class="infoblock_heading">
@ -118,7 +66,7 @@
<span id="selected_registration"></span> <span id="selected_registration"></span>
<span id="selected_icaotype"></span> <span id="selected_icaotype"></span>
<span id="selected_emergency"></span> <span id="selected_emergency"></span>
<a id="selected_flightaware_link" href="" target="_blank">[FlightAware]</a> <span id="selected_flightaware_link"></span>
</td> </td>
</tr> </tr>
@ -136,6 +84,11 @@
<td>RSSI: <span id="selected_rssi">n/a</span></td> <td>RSSI: <span id="selected_rssi">n/a</span></td>
</tr> </tr>
<tr class="infoblock_body">
<td>Vertical rate: <span id="selected_vertical_rate">n/a</span></td>
<td>Messages: <span id="selected_message_count">n/a</span></td>
</tr>
<tr class="infoblock_body"> <tr class="infoblock_body">
<td>Track: <span id="selected_track">n/a</span></td> <td>Track: <span id="selected_track">n/a</span></td>
<td>Last seen: <span id="selected_seen">n/a</span></td> <td>Last seen: <span id="selected_seen">n/a</span></td>
@ -148,37 +101,124 @@
<tr class="infoblock_body"> <tr class="infoblock_body">
<td colspan="2">Distance from Site: <span id="selected_sitedist">n/a</span></td> <td colspan="2">Distance from Site: <span id="selected_sitedist">n/a</span></td>
</tr> </tr>
<tr class="infoblock_body">
<td colspan="2"><span id="selected_photo_link"></span></td>
</tr>
</table> </table>
</div> <!-- selected_infoblock --> </div> <!-- selected_infoblock -->
<div id="map_container">
<div id="map_canvas"></div>
<a id="toggle_sidebar_button" class="hide_sidebar" href="#"></a>
<a id="expand_sidebar_button" href="#"></a>
</div>
<div id="sidebar_container">
<div id="splitter" class="ui-resizable-handle ui-resizable-w"></div>
<div id="sidebar_canvas">
<div id="dump1090_infoblock">
<table style="width: 100%">
<tr class="infoblock_heading">
<td>
<b id="infoblock_name">FlightAware dump1090</b>
</td>
<td>
<span id="show_map_button" class="sidebarButton pointer">Show Map</span>
</td>
<td style="text-align: right">
<a href="https://github.com/flightaware/dump1090" id="dump1090_version" target="_blank"></a>
</td>
</tr>
<tr>
<td colspan="2">
<div id="sudo_buttons">
<span class="sidebarButton pointer" onclick="resetMap();">Reset Map</span>
<span class="sidebarButton pointer" onclick="selectAllPlanes();">Select All</span>
<span class="sidebarButton pointer" onclick="deselectAllPlanes();">Select None</span>
</div>
</td>
</tr>
<tr class="infoblock_body">
<td>Aircraft (total): <span id="dump1090_total_ac">n/a</span></td>
<td>Messages: <span id="dump1090_message_rate">n/a</span>/sec</td>
</tr>
<tr class="infoblock_body">
<td>(with positions): <span id="dump1090_total_ac_positions">n/a</span></td>
<td>History: <span id="dump1090_total_history">n/a</span> positions</td>
</tr>
</table>
</div> <!-- dump1090_infoblock -->
<div id="units_container">
<label for="units_selector">Units:</label>
<select name="units_selector" id="units_selector">
<option value="nautical">Aeronautical</option>
<option value="metric">Metric</option>
<option value="imperial">Imperial</option>
</select>
</div>
<form id="altitude_filter_form">
<label>Filter by altitude:</label>
<input id="altitude_filter_min" name="minAltitude" type="text" class="altitudeFilterInput" maxlength="5">
<label for="minAltitude" class="altitudeUnit"></label>
<span> to </span>
<input id="altitude_filter_max" name="maxAltitude" type="text" class="altitudeFilterInput" maxlength="5">
<label for="maxAltitude" class="altitudeUnit"></label>
<button type="submit">Filter</button>
<button id="altitude_filter_reset_button">Reset</button>
</form>
<div id="planes_table"> <div id="planes_table">
<table id="tableinfo" style="width: 100%"> <table id="tableinfo" style="width: 100%">
<thead style="background-color: #BBBBBB; cursor: pointer;"> <thead class="aircraft_table_header">
<tr> <tr>
<td id="icao" onclick="sortByICAO();">ICAO</td> <td id="icao" onclick="sortByICAO();">ICAO</td>
<td id="flag" onclick="sortByCountry()"><!-- column for flag image --></td> <td id="flag" onclick="sortByCountry()"><!-- column for flag image --></td>
<td id="flight" onclick="sortByFlight();">Flight</td> <td id="flight" onclick="sortByFlight();">Ident</td>
<td id="squawk" onclick="sortBySquawk();" style="text-align: right">Squawk</td> <td id="registration" onclick="sortByRegistration();">Registration</td>
<td id="altitude" onclick="sortByAltitude();" style="text-align: right">Altitude</td> <td id="aircraft_type" onclick="sortByAircraftType();"> Aircraft type</td>
<td id="speed" onclick="sortBySpeed();" style="text-align: right">Speed</td> <td id="squawk" onclick="sortBySquawk();">Squawk</td>
<td id="distance" onclick="sortByDistance();" style="text-align: right">Distance</td> <td id="altitude" onclick="sortByAltitude();">Altitude (<span class="altitudeUnit"></span>)</td>
<td id="track" onclick="sortByTrack();" style="text-align: right">Track</td> <td id="speed" onclick="sortBySpeed();">Speed (<span class="speedUnit"></span>)</td>
<td id="msgs" onclick="sortByMsgs();" style="text-align: right">Msgs</td> <td id="vert_rate" onclick="sortByVerticalRate();">Vertical Rate (<span class="verticalRateUnit"></span>)</td>
<td id="seen" onclick="sortBySeen();" style="text-align: right">Age</td> <td id="distance" onclick="sortByDistance();">Distance (<span class="distanceUnit"></span>)</td>
<td id="track" onclick="sortByTrack();">Track</td>
<td id="msgs" onclick="sortByMsgs();">Msgs</td>
<td id="seen" onclick="sortBySeen();">Age</td>
<td id="rssi" onclick="sortByRssi();">RSSI</td>
<td id="lat" onclick="sortByLatitude();">Latitude</td>
<td id="lon" onclick="sortByLongitude();">Longitude</td>
<td id="data_source" onclick="sortByDataSource();">Data Source</td>
<td id="airframes_mode_s_link">Airframes.org Link</td>
<td id="flightaware_mode_s_link">FlightAware Link</td>
<td id="flightaware_photo_link">Photos</td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr id="plane_row_template" class="plane_table_row hidden"> <tr id="plane_row_template" class="plane_table_row hidden">
<td>ICAO</td> <td style="text-transform: uppercase">ICAO</td>
<td><img style="width: 20px; height=12px" src="about:blank" alt="Flag"></td> <td><img style="width: 20px; height=12px" src="about:blank" alt="Flag"></td>
<td>FLIGHT</td> <td>FLIGHT</td>
<td>REGISTRATION</td>
<td>AIRCRAFT_TYPE</td>
<td style="text-align: right">SQUAWK</td> <td style="text-align: right">SQUAWK</td>
<td style="text-align: right">ALTITUDE</td> <td style="text-align: right">ALTITUDE</td>
<td style="text-align: right">SPEED</td> <td style="text-align: right">SPEED</td>
<td style="text-align: right">VERT_RATE</td>
<td style="text-align: right">DISTANCE</td> <td style="text-align: right">DISTANCE</td>
<td style="text-align: right">TRACK</td> <td style="text-align: right">TRACK</td>
<td style="text-align: right">MSGS</td> <td style="text-align: right">MSGS</td>
<td style="text-align: right">SEEN</td> <td style="text-align: right">SEEN</td>
<td style="text-align: right">RSSI</td>
<td style="text-align: right">LAT</td>
<td style="text-align: right">LON</td>
<td style="text-align: right">DATA_SOURCE</td>
<td style="text-align: center">AIRFRAMES_MODE_S_LINK</td>
<td style="text-align: center">FLIGHTAWARE_MODE_S_LINK</td>
<td style="text-align: center">FLIGHTAWARE_PHOTO_LINK</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -186,6 +226,7 @@
</div> <!-- sidebar_canvas --> </div> <!-- sidebar_canvas -->
</div> <!-- sidebar_container --> </div> <!-- sidebar_container -->
</div> <!-- layout_container -->
<div id="SpecialSquawkWarning" class="hidden"> <div id="SpecialSquawkWarning" class="hidden">
<b>Squawk 7x00 is reported and shown.</b><br> <b>Squawk 7x00 is reported and shown.</b><br>

File diff suppressed because one or more lines are too long

View file

@ -73,7 +73,7 @@ var _a320 = {
var _b777 = { var _b777 = {
key: "b777", key: "b777",
scale: 1.15 * 0.75, scale: 0.80 * 0.75,
size: [64, 64], size: [64, 64],
anchor: [32, 32], anchor: [32, 32],
path: "m 32,1 2,1 1,2 0,20 4,4 0,-4 3,0 0,4 -1,2 17,12 0,2 -16,-5 -7,0 0,13 -1,5 7,5 0,2 -8,-2 -1,2 -1,-2 -8,2 0,-2 7,-5 -1,-5 0,-13 -7,0 -16,5 0,-2 17,-12 -1,-2 0,-4 3,0 0,4 4,-4 0,-20 1,-2 2,-1z" path: "m 32,1 2,1 1,2 0,20 4,4 0,-4 3,0 0,4 -1,2 17,12 0,2 -16,-5 -7,0 0,13 -1,5 7,5 0,2 -8,-2 -1,2 -1,-2 -8,2 0,-2 7,-5 -1,-5 0,-13 -7,0 -16,5 0,-2 17,-12 -1,-2 0,-4 3,0 0,4 4,-4 0,-20 1,-2 2,-1z"
@ -121,6 +121,21 @@ var _balloon = {
markerRadius: 32 markerRadius: 32
}; };
var _helicopter = {
key : "helicopter",
scale : 0.50,
size : [64, 64],
anchor : [22, 32],
path : _rotorcraft_svg
};
var _single_prop = {
key : "single_prop",
scale : 0.30,
size : [64, 64],
anchor : [32, 25],
path : _beechcraft_svg
};
// by Oliver Jowett <oliver@mutability.co.uk> // by Oliver Jowett <oliver@mutability.co.uk>
// licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/) // licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
@ -134,13 +149,13 @@ var _a380 = {
var _b738 = { var _b738 = {
key: "b738", key: "b738",
scale: 0.63 * 0.75, scale: 0.70 * 0.75,
size: [64, 64], size: [64, 64],
anchor: [32, 32], anchor: [32, 32],
path: "m 32,61 -1,-1 -9,2 -2,1 0,-2 9,-6 1,-1 -1,-9 0,-11 -7,0 -1,1 0,-1 -3,1 -1,1 0,-1 -3,1 -9,3 -1,1 0,-2 1,-2 17,-9 1,-1 -1,-2 0,-3 1,-1 2,0 1,1 0,3 3,-2 0,-13 1,-5 1,-3 1,-1 1,1 1,3 1,5 0,13 3,2 0,-3 1,-1 2,0 1,1 0,3 -1,2 1,1 17,9 1,2 0,2 -1,-1 -9,-3 -3,-1 0,1 -1,-1 -3,-1 0,1 -1,-1 -7,0 0,11 -1,9 1,1 9,6 0,2 -2,-1 -9,-2 -1,1 z" path: "m 32,61 -1,-1 -9,2 -2,1 0,-2 9,-6 1,-1 -1,-9 0,-11 -7,0 -1,1 0,-1 -3,1 -1,1 0,-1 -3,1 -9,3 -1,1 0,-2 1,-2 17,-9 1,-1 -1,-2 0,-3 1,-1 2,0 1,1 0,3 3,-2 0,-13 1,-5 1,-3 1,-1 1,1 1,3 1,5 0,13 3,2 0,-3 1,-1 2,0 1,1 0,3 -1,2 1,1 17,9 1,2 0,2 -1,-1 -9,-3 -3,-1 0,1 -1,-1 -3,-1 0,1 -1,-1 -7,0 0,11 -1,9 1,1 9,6 0,2 -2,-1 -9,-2 -1,1 z"
}; };
var TypeIcons = { var TypeDesignatorIcons = {
'A318': _a320, // shortened a320 'A318': _a320, // shortened a320
'A319': _a320, // shortened a320 'A319': _a320, // shortened a320
'A320': _a320, 'A320': _a320,
@ -182,14 +197,30 @@ var TypeIcons = {
'C30J': _c130 'C30J': _c130
}; };
// Maps ICAO aircraft type description codes (e.g. "L2J") to aircraft icons. This is used if the ICAO type designator (e.g. "B731")
// cannot be found in the TypeDesignatorIcons mappings. The key can be one of the following:
// - Single character: The basic aircraft type letter code (e.g. "H" for helicopter).
// - Three characters: The ICAO type description code (e.g. "L2J" for landplanes with 2 jet engines).
// - Five characters: The ICAO type description code concatenated with the wake turbulence category code, separated by
// a dash (e.g. "L2J-M").
var TypeDescriptionIcons = {
'H': _helicopter,
'L1J': _g650,
'L1P': _single_prop,
'L1T': _single_prop,
'L2P': _b200,
'L2T': _b200,
'L2J-L': _g650,
'L2J-M': _a320,
'L2J-H': _b777,
};
var CategoryIcons = { var CategoryIcons = {
"A1" : { "A1" : _single_prop,
key : "A1",
scale : 0.30,
size : [64, 64],
anchor : [32, 25],
path : _beechcraft_svg
},
"A2" : { "A2" : {
key : "A2", key : "A2",
@ -215,13 +246,7 @@ var CategoryIcons = {
path : _heavy_svg path : _heavy_svg
}, },
"A7" : { "A7" : _helicopter,
key : "A7",
scale : 0.50,
size : [64, 64],
anchor : [22, 32],
path : _rotorcraft_svg
},
"B2" : _balloon "B2" : _balloon
}; };
@ -234,9 +259,27 @@ var DefaultIcon = {
path : _generic_plane_svg path : _generic_plane_svg
}; };
function getBaseMarker(category, type) { function getBaseMarker(category, typeDesignator, typeDescription, wtc) {
if (type in TypeIcons) { if (typeDesignator in TypeDesignatorIcons) {
return TypeIcons[type]; return TypeDesignatorIcons[typeDesignator];
}
if (typeDescription !== undefined && typeDescription !== null && typeDescription.length === 3) {
if (wtc !== undefined && wtc !== null && wtc.length === 1) {
var typeDescriptionWithWtc = typeDescription + "-" + wtc;
if (typeDescriptionWithWtc in TypeDescriptionIcons) {
return TypeDescriptionIcons[typeDescriptionWithWtc];
}
}
if (typeDescription in TypeDescriptionIcons) {
return TypeDescriptionIcons[typeDescription];
}
var basicType = typeDescription.charAt(0);
if (basicType in TypeDescriptionIcons) {
return TypeDescriptionIcons[basicType];
}
} }
if (category in CategoryIcons) { if (category in CategoryIcons) {
@ -246,7 +289,7 @@ function getBaseMarker(category, type) {
return DefaultIcon; return DefaultIcon;
} }
function svgPathToSvg(path, size, stroke, width, fill) { function svgPathToSvg(path, size, stroke, width, fill, transparentBorderWidth) {
var svg = '<svg width="' + size[0] + 'px" height="' + size[1] + 'px" version="1.1" xmlns="http://www.w3.org/2000/svg">'; var svg = '<svg width="' + size[0] + 'px" height="' + size[1] + 'px" version="1.1" xmlns="http://www.w3.org/2000/svg">';
svg += '<path d="' + path + '"'; svg += '<path d="' + path + '"';
if (stroke !== null) { if (stroke !== null) {
@ -258,11 +301,19 @@ function svgPathToSvg(path, size, stroke, width, fill) {
if (fill !== null) { if (fill !== null) {
svg += ' fill="' + fill + '"'; svg += ' fill="' + fill + '"';
} }
svg += '/></svg>'; svg += '/>';
// Add a transparent border to increase the size of the plane marker clickable area.
if (transparentBorderWidth > 0) {
// If the border is 100% transparent, OpenLayers will ignore it completely - see
// https://github.com/openlayers/ol3/issues/2961. The stroke opacity is set to 1% as a workaround.
// This is transparent enough to be invisible to the user.
svg += '<path d="' + path + '" fill="none" stroke="#FFFFFF" stroke-opacity="0.01" stroke-width="' + transparentBorderWidth + '" />';
}
svg += '</svg>';
return svg; return svg;
} }
function svgPathToURI(path, size, stroke, width, fill) { function svgPathToURI(path, size, stroke, width, fill, transparentBorderWidth) {
return "data:image/svg+xml;base64," + btoa(svgPathToSvg(path, size, stroke, width, fill)); return "data:image/svg+xml;base64," + btoa(svgPathToSvg(path, size, stroke, width, fill, transparentBorderWidth));
} }

View file

@ -43,11 +43,14 @@ function PlaneObject(icao) {
this.markerStaticIcon = null; this.markerStaticIcon = null;
this.markerStyleKey = null; this.markerStyleKey = null;
this.markerSvgKey = null; this.markerSvgKey = null;
this.filter = {};
// start from a computed registration, let the DB override it // start from a computed registration, let the DB override it
// if it has something else. // if it has something else.
this.registration = registration_from_hexid(this.icao); this.registration = registration_from_hexid(this.icao);
this.icaotype = null; this.icaotype = null;
this.typeDescription = null;
this.wtc = null;
// request metadata // request metadata
getAircraftData(this.icao).done(function(data) { getAircraftData(this.icao).done(function(data) {
@ -59,12 +62,32 @@ function PlaneObject(icao) {
this.icaotype = data.t; this.icaotype = data.t;
} }
if ("desc" in data) {
this.typeDescription = data.desc;
}
if ("wtc" in data) {
this.wtc = data.wtc;
}
if (this.selected) { if (this.selected) {
refreshSelected(); refreshSelected();
} }
}.bind(this)); }.bind(this));
} }
PlaneObject.prototype.isFiltered = function() {
if (this.filter.minAltitude !== undefined && this.filter.maxAltitude !== undefined) {
if (this.altitude === null || this.altitude === undefined) {
return true;
}
var planeAltitude = this.altitude === "ground" ? 0 : convert_altitude(this.altitude, this.filter.altitudeUnits);
return planeAltitude < this.filter.minAltitude || planeAltitude > this.filter.maxAltitude;
}
return false;
}
// Appends data to the running track so we can get a visual tail on the plane // Appends data to the running track so we can get a visual tail on the plane
// Only useful for a long running browser session. // Only useful for a long running browser session.
PlaneObject.prototype.updateTrack = function(estimate_time) { PlaneObject.prototype.updateTrack = function(estimate_time) {
@ -183,6 +206,27 @@ PlaneObject.prototype.clearLines = function() {
} }
}; };
PlaneObject.prototype.getDataSource = function() {
// MLAT
if (this.position_from_mlat) {
return 'mlat';
}
// Not MLAT, but position reported - ADSB
if (this.position !== null) {
return 'adsb';
}
var emptyHexRegex = /^0*$/;
// No position and no ICAO hex code - Mode A/C
if (emptyHexRegex.test(this.icao)) {
return 'mode_ac';
}
// No position and ICAO hex code present - Mode S
return 'mode_s';
};
PlaneObject.prototype.getMarkerColor = function() { PlaneObject.prototype.getMarkerColor = function() {
// Emergency squawks override everything else // Emergency squawks override everything else
if (this.squawk in SpecialSquawks) if (this.squawk in SpecialSquawks)
@ -258,9 +302,10 @@ PlaneObject.prototype.updateIcon = function() {
var col = this.getMarkerColor(); var col = this.getMarkerColor();
var opacity = (this.position_from_mlat ? 0.75 : 1.0); var opacity = (this.position_from_mlat ? 0.75 : 1.0);
var outline = (this.position_from_mlat ? OutlineMlatColor : OutlineADSBColor); var outline = (this.position_from_mlat ? OutlineMlatColor : OutlineADSBColor);
var baseMarker = getBaseMarker(this.category, this.icaotype); var baseMarker = getBaseMarker(this.category, this.icaotype, this.typeDescription, this.wtc);
var weight = ((this.selected ? 2 : 1) / baseMarker.scale).toFixed(1); var weight = ((this.selected ? 2 : 1) / baseMarker.scale).toFixed(1);
var rotation = (this.track === null ? 0 : this.track); var rotation = (this.track === null ? 0 : this.track);
var transparentBorderWidth = 16;
var svgKey = col + '!' + outline + '!' + baseMarker.key + '!' + weight; var svgKey = col + '!' + outline + '!' + baseMarker.key + '!' + weight;
var styleKey = opacity + '!' + rotation; var styleKey = opacity + '!' + rotation;
@ -274,7 +319,7 @@ PlaneObject.prototype.updateIcon = function() {
anchorYUnits: 'pixels', anchorYUnits: 'pixels',
scale: baseMarker.scale, scale: baseMarker.scale,
imgSize: baseMarker.size, imgSize: baseMarker.size,
src: svgPathToURI(baseMarker.path, baseMarker.size, outline, weight, col), src: svgPathToURI(baseMarker.path, baseMarker.size, outline, weight, col, transparentBorderWidth),
rotation: (baseMarker.noRotate ? 0 : rotation * Math.PI / 180.0), rotation: (baseMarker.noRotate ? 0 : rotation * Math.PI / 180.0),
opacity: opacity, opacity: opacity,
rotateWithView: (baseMarker.noRotate ? false : true) rotateWithView: (baseMarker.noRotate ? false : true)
@ -300,7 +345,7 @@ PlaneObject.prototype.updateIcon = function() {
anchorYUnits: 'pixels', anchorYUnits: 'pixels',
scale: 1.0, scale: 1.0,
imgSize: [size, size], imgSize: [size, size],
src: svgPathToURI(arrowPath, [size, size], outline, 1, outline), src: svgPathToURI(arrowPath, [size, size], outline, 1, outline, transparentBorderWidth),
rotation: rotation * Math.PI / 180.0, rotation: rotation * Math.PI / 180.0,
opacity: opacity, opacity: opacity,
rotateWithView: true rotateWithView: true
@ -421,7 +466,7 @@ PlaneObject.prototype.clearMarker = function() {
// Update our marker on the map // Update our marker on the map
PlaneObject.prototype.updateMarker = function(moved) { PlaneObject.prototype.updateMarker = function(moved) {
if (!this.visible || this.position == null) { if (!this.visible || this.position == null || this.isFiltered()) {
this.clearMarker(); this.clearMarker();
return; return;
} }
@ -480,9 +525,10 @@ PlaneObject.prototype.updateLines = function() {
var lastfixed = lastseg.fixed.getCoordinateAt(1.0); var lastfixed = lastseg.fixed.getCoordinateAt(1.0);
var geom = new ol.geom.LineString([lastfixed, ol.proj.fromLonLat(this.position)]); var geom = new ol.geom.LineString([lastfixed, ol.proj.fromLonLat(this.position)]);
var feature = new ol.Feature(geom); var feature = new ol.Feature(geom);
lastseg.feature = feature;
feature.setStyle(this.altitude === 'ground' ? groundStyle : airStyle); feature.setStyle(this.altitude === 'ground' ? groundStyle : airStyle);
if (PlaneTrailFeatures.length == 0) { if (PlaneTrailFeatures.getLength() == 0) {
PlaneTrailFeatures.push(feature); PlaneTrailFeatures.push(feature);
} else { } else {
PlaneTrailFeatures.setAt(0, feature); PlaneTrailFeatures.setAt(0, feature);

View file

@ -4,11 +4,15 @@
// Define our global variables // Define our global variables
var OLMap = null; var OLMap = null;
var StaticFeatures = new ol.Collection(); var StaticFeatures = new ol.Collection();
var SiteCircleFeatures = new ol.Collection();
var PlaneIconFeatures = new ol.Collection(); var PlaneIconFeatures = new ol.Collection();
var PlaneTrailFeatures = new ol.Collection(); var PlaneTrailFeatures = new ol.Collection();
var Planes = {}; var Planes = {};
var PlanesModeAc = {};
var PlanesOrdered = []; var PlanesOrdered = [];
var PlaneFilter = {};
var SelectedPlane = null; var SelectedPlane = null;
var SelectedAllPlanes = false;
var FollowSelected = false; var FollowSelected = false;
var SpecialSquawks = { var SpecialSquawks = {
@ -62,15 +66,19 @@ function processReceiverUpdate(data) {
for (var j=0; j < acs.length; j++) { for (var j=0; j < acs.length; j++) {
var ac = acs[j]; var ac = acs[j];
var hex = ac.hex; var hex = ac.hex;
var squawk = ac.squawk;
var plane = null; var plane = null;
// Do we already have this plane object in Planes? // Do we already have this plane object in Planes?
// If not make it. // If not make it.
if (Planes[hex]) { if (hex !== "000000" && Planes[hex]) {
plane = Planes[hex]; plane = Planes[hex];
} else if (hex === "000000" && PlanesModeAc[squawk]) {
plane = PlanesModeAc[squawk];
} else { } else {
plane = new PlaneObject(hex); plane = new PlaneObject(hex);
plane.filter = PlaneFilter;
plane.tr = PlaneRowTemplate.cloneNode(true); plane.tr = PlaneRowTemplate.cloneNode(true);
if (hex[0] === '~') { if (hex[0] === '~') {
@ -89,17 +97,34 @@ function processReceiverUpdate(data) {
$('img', plane.tr.cells[1]).css('display', 'none'); $('img', plane.tr.cells[1]).css('display', 'none');
} }
if (hex !== "000000") {
plane.tr.addEventListener('click', function(h, evt) { plane.tr.addEventListener('click', function(h, evt) {
if (evt.srcElement instanceof HTMLAnchorElement) {
evt.stopPropagation();
return;
}
if (!$("#map_container").is(":visible")) {
showMap();
}
selectPlaneByHex(h, false); selectPlaneByHex(h, false);
adjustSelectedInfoBlockPosition();
evt.preventDefault(); evt.preventDefault();
}.bind(undefined, hex)); }.bind(undefined, hex));
plane.tr.addEventListener('dblclick', function(h, evt) { plane.tr.addEventListener('dblclick', function(h, evt) {
if (!$("#map_container").is(":visible")) {
showMap();
}
selectPlaneByHex(h, true); selectPlaneByHex(h, true);
adjustSelectedInfoBlockPosition();
evt.preventDefault(); evt.preventDefault();
}.bind(undefined, hex)); }.bind(undefined, hex));
Planes[hex] = plane; Planes[hex] = plane;
} else {
PlanesModeAc[squawk] = plane;
}
PlanesOrdered.push(plane); PlanesOrdered.push(plane);
} }
@ -129,6 +154,7 @@ function fetchData() {
plane.updateTick(now, LastReceiverTimestamp); plane.updateTick(now, LastReceiverTimestamp);
} }
selectNewPlanes();
refreshTableInfo(); refreshTableInfo();
refreshSelected(); refreshSelected();
@ -197,6 +223,53 @@ function initialize() {
$("#loader").removeClass("hidden"); $("#loader").removeClass("hidden");
// Set up map/sidebar splitter
$("#sidebar_container").resizable({handles: {w: '#splitter'}});
// Set up aircraft information panel
$("#selected_infoblock").draggable({containment: "parent"});
// Set up event handlers for buttons
$("#toggle_sidebar_button").click(toggleSidebarVisibility);
$("#expand_sidebar_button").click(expandSidebar);
$("#show_map_button").click(showMap);
// Set initial element visibility
$("#show_map_button").hide();
setColumnVisibility();
// Initialize other controls
initializeUnitsSelector();
// Set up altitude filter button event handlers and validation options
$("#altitude_filter_form").submit(onFilterByAltitude);
$("#altitude_filter_form").validate({
errorPlacement: function(error, element) {
return true;
},
rules: {
minAltitude: {
number: true,
min: -99999,
max: 99999
},
maxAltitude: {
number: true,
min: -99999,
max: 99999
}
}
});
$("#altitude_filter_reset_button").click(onResetAltitudeFilter);
// Force map to redraw if sidebar container is resized - use a timer to debounce
var mapResizeTimeout;
$("#sidebar_container").on("resize", function() {
clearTimeout(mapResizeTimeout);
mapResizeTimeout = setTimeout(updateMapSize, 10);
});
// Get receiver metadata, reconfigure using it, then continue // Get receiver metadata, reconfigure using it, then continue
// with initialization // with initialization
$.ajax({ url: 'data/receiver.json', $.ajax({ url: 'data/receiver.json',
@ -354,7 +427,7 @@ function initialize_map() {
sortByDistance(); sortByDistance();
} else { } else {
SitePosition = null; SitePosition = null;
PlaneRowTemplate.cells[6].style.display = 'none'; // hide distance column PlaneRowTemplate.cells[9].style.display = 'none'; // hide distance column
document.getElementById("distance").style.display = 'none'; // hide distance header document.getElementById("distance").style.display = 'none'; // hide distance header
sortByAltitude(); sortByAltitude();
} }
@ -457,7 +530,7 @@ function initialize_map() {
controls: [new ol.control.Zoom(), controls: [new ol.control.Zoom(),
new ol.control.Rotate(), new ol.control.Rotate(),
new ol.control.Attribution({collapsed: false}), new ol.control.Attribution({collapsed: false}),
new ol.control.ScaleLine({units: Metric ? "metric" : "nautical"}), new ol.control.ScaleLine({units: DisplayUnits}),
new ol.control.LayerSwitcher() new ol.control.LayerSwitcher()
], ],
loadTilesWhileAnimating: true, loadTilesWhileAnimating: true,
@ -496,6 +569,10 @@ function initialize_map() {
null); null);
if (hex) { if (hex) {
selectPlaneByHex(hex, (evt.type === 'dblclick')); selectPlaneByHex(hex, (evt.type === 'dblclick'));
adjustSelectedInfoBlockPosition();
evt.stopPropagation();
} else {
deselectAllPlanes();
evt.stopPropagation(); evt.stopPropagation();
} }
}); });
@ -518,26 +595,7 @@ function initialize_map() {
StaticFeatures.push(feature); StaticFeatures.push(feature);
if (SiteCircles) { if (SiteCircles) {
var circleStyle = new ol.style.Style({ createSiteCircleFeatures();
fill: null,
stroke: new ol.style.Stroke({
color: '#000000',
width: 1
})
});
for (var i=0; i < SiteCirclesDistances.length; ++i) {
var distance = SiteCirclesDistances[i] * 1000.0;
if (!Metric) {
distance *= 1.852;
}
var circle = make_geodesic_circle(SitePosition, distance, 360);
circle.transform('EPSG:4326', 'EPSG:3857');
var feature = new ol.Feature(circle);
feature.setStyle(circleStyle);
StaticFeatures.push(feature);
}
} }
} }
@ -591,6 +649,39 @@ function initialize_map() {
}); });
} }
function createSiteCircleFeatures() {
// Clear existing circles first
SiteCircleFeatures.forEach(function(circleFeature) {
StaticFeatures.remove(circleFeature);
});
SiteCircleFeatures.clear();
var circleStyle = new ol.style.Style({
fill: null,
stroke: new ol.style.Stroke({
color: '#000000',
width: 1
})
});
var conversionFactor = 1000.0;
if (DisplayUnits === "nautical") {
conversionFactor = 1852.0;
} else if (DisplayUnits === "imperial") {
conversionFactor = 1609.0;
}
for (var i=0; i < SiteCirclesDistances.length; ++i) {
var distance = SiteCirclesDistances[i] * conversionFactor;
var circle = make_geodesic_circle(SitePosition, distance, 360);
circle.transform('EPSG:4326', 'EPSG:3857');
var feature = new ol.Feature(circle);
feature.setStyle(circleStyle);
StaticFeatures.push(feature);
SiteCircleFeatures.push(feature);
}
}
// This looks for planes to reap out of the master Planes variable // This looks for planes to reap out of the master Planes variable
function reaper() { function reaper() {
//console.log("Reaping started.."); //console.log("Reaping started..");
@ -601,11 +692,13 @@ function reaper() {
var plane = PlanesOrdered[i]; var plane = PlanesOrdered[i];
if (plane.seen > 300) { if (plane.seen > 300) {
// Reap it. // Reap it.
//console.log("Reaping " + plane.icao);
//console.log("parent " + plane.tr.parentNode);
plane.tr.parentNode.removeChild(plane.tr); plane.tr.parentNode.removeChild(plane.tr);
plane.tr = null; plane.tr = null;
if (plane.icao === "000000") {
delete PlanesModeAc[plane.squawk];
} else {
delete Planes[plane.icao]; delete Planes[plane.icao];
}
plane.destroy(); plane.destroy();
} else { } else {
// Keep it. // Keep it.
@ -655,8 +748,6 @@ function refreshSelected() {
selected = Planes[SelectedPlane]; selected = Planes[SelectedPlane];
} }
if (!selected) {
$('#selected_infoblock').css('display','none');
$('#dump1090_infoblock').css('display','block'); $('#dump1090_infoblock').css('display','block');
$('#dump1090_version').text(Dump1090Version); $('#dump1090_version').text(Dump1090Version);
$('#dump1090_total_ac').text(TrackedAircraft); $('#dump1090_total_ac').text(TrackedAircraft);
@ -669,19 +760,18 @@ function refreshSelected() {
$('#dump1090_message_rate').text("n/a"); $('#dump1090_message_rate').text("n/a");
} }
setSelectedInfoBlockVisibility();
if (!selected) {
return; return;
} }
$('#dump1090_infoblock').css('display','none');
$('#selected_infoblock').css('display','block');
$('#selected_flightaware_link').attr('href','//flightaware.com/live/modes/'+selected.icao+'/redirect');
if (selected.flight !== null && selected.flight !== "") { if (selected.flight !== null && selected.flight !== "") {
$('#selected_callsign').text(selected.flight); $('#selected_callsign').text(selected.flight);
} else { } else {
$('#selected_callsign').text('n/a'); $('#selected_callsign').text('n/a');
} }
$('#selected_flightaware_link').html(getFlightAwareModeSLink(selected.icao, selected.flight, "[FlightAware]"));
if (selected.registration !== null) { if (selected.registration !== null) {
$('#selected_registration').text(selected.registration); $('#selected_registration').text(selected.registration);
@ -703,7 +793,7 @@ function refreshSelected() {
emerg.className = 'hidden'; emerg.className = 'hidden';
} }
$("#selected_altitude").text(format_altitude_long(selected.altitude, selected.vert_rate)); $("#selected_altitude").text(format_altitude_long(selected.altitude, selected.vert_rate, DisplayUnits));
if (selected.squawk === null || selected.squawk === '0000') { if (selected.squawk === null || selected.squawk === '0000') {
$('#selected_squawk').text('n/a'); $('#selected_squawk').text('n/a');
@ -711,7 +801,8 @@ function refreshSelected() {
$('#selected_squawk').text(selected.squawk); $('#selected_squawk').text(selected.squawk);
} }
$('#selected_speed').text(format_speed_long(selected.speed)); $('#selected_speed').text(format_speed_long(selected.speed, DisplayUnits));
$('#selected_vertical_rate').text(format_vert_rate_long(selected.vert_rate, DisplayUnits));
$('#selected_icao').text(selected.icao.toUpperCase()); $('#selected_icao').text(selected.icao.toUpperCase());
$('#airframes_post_icao').attr('value',selected.icao); $('#airframes_post_icao').attr('value',selected.icao);
$('#selected_track').text(format_track_long(selected.track)); $('#selected_track').text(format_track_long(selected.track));
@ -750,8 +841,10 @@ function refreshSelected() {
} }
} }
$('#selected_sitedist').text(format_distance_long(selected.sitedist)); $('#selected_sitedist').text(format_distance_long(selected.sitedist, DisplayUnits));
$('#selected_rssi').text(selected.rssi.toFixed(1) + ' dBFS'); $('#selected_rssi').text(selected.rssi.toFixed(1) + ' dBFS');
$('#selected_message_count').text(selected.messages);
$('#selected_photo_link').html(getFlightAwarePhotoLink(selected.registration));
} }
// Refreshes the larger table of all the planes // Refreshes the larger table of all the planes
@ -762,10 +855,15 @@ function refreshTableInfo() {
TrackedAircraftPositions = 0 TrackedAircraftPositions = 0
TrackedHistorySize = 0 TrackedHistorySize = 0
$(".altitudeUnit").text(get_unit_label("altitude", DisplayUnits));
$(".speedUnit").text(get_unit_label("speed", DisplayUnits));
$(".distanceUnit").text(get_unit_label("distance", DisplayUnits));
$(".verticalRateUnit").text(get_unit_label("verticalRate", DisplayUnits));
for (var i = 0; i < PlanesOrdered.length; ++i) { for (var i = 0; i < PlanesOrdered.length; ++i) {
var tableplane = PlanesOrdered[i]; var tableplane = PlanesOrdered[i];
TrackedHistorySize += tableplane.history_size; TrackedHistorySize += tableplane.history_size;
if (!tableplane.visible) { if (!tableplane.visible || tableplane.isFiltered()) {
tableplane.tr.className = "plane_table_row hidden"; tableplane.tr.className = "plane_table_row hidden";
} else { } else {
TrackedAircraft++; TrackedAircraft++;
@ -787,14 +885,24 @@ function refreshTableInfo() {
} }
// ICAO doesn't change // ICAO doesn't change
tableplane.tr.cells[2].textContent = (tableplane.flight !== null ? tableplane.flight : ""); tableplane.tr.cells[2].innerHTML = getFlightAwareIdentLink(tableplane.flight);
tableplane.tr.cells[3].textContent = (tableplane.squawk !== null ? tableplane.squawk : ""); tableplane.tr.cells[3].textContent = (tableplane.registration !== null ? tableplane.registration : "");
tableplane.tr.cells[4].textContent = format_altitude_brief(tableplane.altitude, tableplane.vert_rate); tableplane.tr.cells[4].textContent = (tableplane.icaotype !== null ? tableplane.icaotype : "");
tableplane.tr.cells[5].textContent = format_speed_brief(tableplane.speed); tableplane.tr.cells[5].textContent = (tableplane.squawk !== null ? tableplane.squawk : "");
tableplane.tr.cells[6].textContent = format_distance_brief(tableplane.sitedist); tableplane.tr.cells[6].innerHTML = format_altitude_brief(tableplane.altitude, tableplane.vert_rate, DisplayUnits);
tableplane.tr.cells[7].textContent = format_track_brief(tableplane.track); tableplane.tr.cells[7].textContent = format_speed_brief(tableplane.speed, DisplayUnits);
tableplane.tr.cells[8].textContent = tableplane.messages; tableplane.tr.cells[8].textContent = format_vert_rate_brief(tableplane.vert_rate, DisplayUnits);
tableplane.tr.cells[9].textContent = tableplane.seen.toFixed(0); tableplane.tr.cells[9].textContent = format_distance_brief(tableplane.sitedist, DisplayUnits);
tableplane.tr.cells[10].textContent = format_track_brief(tableplane.track);
tableplane.tr.cells[11].textContent = tableplane.messages;
tableplane.tr.cells[12].textContent = tableplane.seen.toFixed(0);
tableplane.tr.cells[13].textContent = (tableplane.rssi !== null ? tableplane.rssi : "");
tableplane.tr.cells[14].textContent = (tableplane.position !== null ? tableplane.position[1].toFixed(4) : "");
tableplane.tr.cells[15].textContent = (tableplane.position !== null ? tableplane.position[0].toFixed(4) : "");
tableplane.tr.cells[16].textContent = format_data_source(tableplane.getDataSource());
tableplane.tr.cells[17].innerHTML = getAirframesModeSLink(tableplane.icao);
tableplane.tr.cells[18].innerHTML = getFlightAwareModeSLink(tableplane.icao, tableplane.flight);
tableplane.tr.cells[19].innerHTML = getFlightAwarePhotoLink(tableplane.registration);
tableplane.tr.className = classes; tableplane.tr.className = classes;
} }
} }
@ -829,14 +937,21 @@ function compareNumeric(xf,yf) {
function sortByICAO() { sortBy('icao', compareAlpha, function(x) { return x.icao; }); } function sortByICAO() { sortBy('icao', compareAlpha, function(x) { return x.icao; }); }
function sortByFlight() { sortBy('flight', compareAlpha, function(x) { return x.flight; }); } function sortByFlight() { sortBy('flight', compareAlpha, function(x) { return x.flight; }); }
function sortByRegistration() { sortBy('registration', compareAlpha, function(x) { return x.registration; }); }
function sortByAircraftType() { sortBy('icaotype', compareAlpha, function(x) { return x.icaotype; }); }
function sortBySquawk() { sortBy('squawk', compareAlpha, function(x) { return x.squawk; }); } function sortBySquawk() { sortBy('squawk', compareAlpha, function(x) { return x.squawk; }); }
function sortByAltitude() { sortBy('altitude',compareNumeric, function(x) { return (x.altitude == "ground" ? -1e9 : x.altitude); }); } function sortByAltitude() { sortBy('altitude',compareNumeric, function(x) { return (x.altitude == "ground" ? -1e9 : x.altitude); }); }
function sortBySpeed() { sortBy('speed', compareNumeric, function(x) { return x.speed; }); } function sortBySpeed() { sortBy('speed', compareNumeric, function(x) { return x.speed; }); }
function sortByVerticalRate() { sortBy('vert_rate', compareNumeric, function(x) { return x.vert_rate; }); }
function sortByDistance() { sortBy('sitedist',compareNumeric, function(x) { return x.sitedist; }); } function sortByDistance() { sortBy('sitedist',compareNumeric, function(x) { return x.sitedist; }); }
function sortByTrack() { sortBy('track', compareNumeric, function(x) { return x.track; }); } function sortByTrack() { sortBy('track', compareNumeric, function(x) { return x.track; }); }
function sortByMsgs() { sortBy('msgs', compareNumeric, function(x) { return x.messages; }); } function sortByMsgs() { sortBy('msgs', compareNumeric, function(x) { return x.messages; }); }
function sortBySeen() { sortBy('seen', compareNumeric, function(x) { return x.seen; }); } function sortBySeen() { sortBy('seen', compareNumeric, function(x) { return x.seen; }); }
function sortByCountry() { sortBy('country', compareAlpha, function(x) { return x.icaorange.country; }); } function sortByCountry() { sortBy('country', compareAlpha, function(x) { return x.icaorange.country; }); }
function sortByRssi() { sortBy('rssi', compareNumeric, function(x) { return x.rssi }); }
function sortByLatitude() { sortBy('lat', compareNumeric, function(x) { return (x.position !== null ? x.position[1] : null) }); }
function sortByLongitude() { sortBy('lon', compareNumeric, function(x) { return (x.position !== null ? x.position[0] : null) }); }
function sortByDataSource() { sortBy('data_source', compareAlpha, function(x) { return x.getDataSource() } ); }
var sortId = ''; var sortId = '';
var sortCompare = null; var sortCompare = null;
@ -894,6 +1009,11 @@ function sortBy(id,sc,se) {
function selectPlaneByHex(hex,autofollow) { function selectPlaneByHex(hex,autofollow) {
//console.log("select: " + hex); //console.log("select: " + hex);
// If SelectedPlane has something in it, clear out the selected // If SelectedPlane has something in it, clear out the selected
if (SelectedAllPlanes) {
deselectAllPlanes();
}
if (SelectedPlane != null) { if (SelectedPlane != null) {
Planes[SelectedPlane].selected = false; Planes[SelectedPlane].selected = false;
Planes[SelectedPlane].clearLines(); Planes[SelectedPlane].clearLines();
@ -929,6 +1049,67 @@ function selectPlaneByHex(hex,autofollow) {
refreshSelected(); refreshSelected();
} }
// loop through the planes and mark them as selected to show the paths for all planes
function selectAllPlanes() {
// if all planes are already selected, deselect them all
if (SelectedAllPlanes) {
deselectAllPlanes();
} else {
// If SelectedPlane has something in it, clear out the selected
if (SelectedPlane != null) {
Planes[SelectedPlane].selected = false;
Planes[SelectedPlane].clearLines();
Planes[SelectedPlane].updateMarker();
$(Planes[SelectedPlane].tr).removeClass("selected");
}
SelectedPlane = null;
for(var key in Planes) {
if (Planes[key].visible && !Planes[key].isFiltered()) {
Planes[key].selected = true;
Planes[key].updateLines();
Planes[key].updateMarker();
}
}
SelectedAllPlanes = true;
}
refreshSelected();
}
// on refreshes, try to find new planes and mark them as selected
function selectNewPlanes() {
if (SelectedAllPlanes) {
for (var key in Planes) {
if (!Planes[key].visible || Planes[key].isFiltered()) {
Planes[key].selected = false;
Planes[key].clearLines();
Planes[key].updateMarker();
} else {
if (Planes[key].selected !== true) {
Planes[key].selected = true;
Planes[key].updateLines();
Planes[key].updateMarker();
}
}
}
}
}
// deselect all the planes
function deselectAllPlanes() {
for(var key in Planes) {
Planes[key].selected = false;
Planes[key].clearLines();
Planes[key].updateMarker();
$(Planes[key].tr).removeClass("selected");
}
SelectedPlane = null;
SelectedAllPlanes = false;
refreshSelected();
}
function toggleFollowSelected() { function toggleFollowSelected() {
FollowSelected = !FollowSelected; FollowSelected = !FollowSelected;
if (FollowSelected && OLMap.getView().getZoom() < 8) if (FollowSelected && OLMap.getView().getZoom() < 8)
@ -948,3 +1129,269 @@ function resetMap() {
selectPlaneByHex(null,false); selectPlaneByHex(null,false);
} }
function updateMapSize() {
OLMap.updateSize();
}
function toggleSidebarVisibility(e) {
e.preventDefault();
$("#sidebar_container").toggle();
$("#expand_sidebar_button").toggle();
$("#toggle_sidebar_button").toggleClass("show_sidebar");
$("#toggle_sidebar_button").toggleClass("hide_sidebar");
updateMapSize();
}
function expandSidebar(e) {
e.preventDefault();
$("#map_container").hide()
$("#toggle_sidebar_button").hide();
$("#splitter").hide();
$("#sudo_buttons").hide();
$("#show_map_button").show();
$("#sidebar_container").width("100%");
setColumnVisibility();
setSelectedInfoBlockVisibility();
updateMapSize();
}
function showMap() {
$("#map_container").show()
$("#toggle_sidebar_button").show();
$("#splitter").show();
$("#sudo_buttons").show();
$("#show_map_button").hide();
$("#sidebar_container").width("470px");
setColumnVisibility();
setSelectedInfoBlockVisibility();
updateMapSize();
}
function showColumn(table, columnId, visible) {
var index = $(columnId).index();
if (index >= 0) {
var cells = $(table).find("td:nth-child(" + (index + 1).toString() + ")");
if (visible) {
cells.show();
} else {
cells.hide();
}
}
}
function setColumnVisibility() {
var mapIsVisible = $("#map_container").is(":visible");
var infoTable = $("#tableinfo");
showColumn(infoTable, "#registration", !mapIsVisible);
showColumn(infoTable, "#aircraft_type", !mapIsVisible);
showColumn(infoTable, "#vert_rate", !mapIsVisible);
showColumn(infoTable, "#rssi", !mapIsVisible);
showColumn(infoTable, "#lat", !mapIsVisible);
showColumn(infoTable, "#lon", !mapIsVisible);
showColumn(infoTable, "#data_source", !mapIsVisible);
showColumn(infoTable, "#airframes_mode_s_link", !mapIsVisible);
showColumn(infoTable, "#flightaware_mode_s_link", !mapIsVisible);
showColumn(infoTable, "#flightaware_photo_link", !mapIsVisible);
}
function setSelectedInfoBlockVisibility() {
var mapIsVisible = $("#map_container").is(":visible");
var planeSelected = (typeof SelectedPlane !== 'undefined' && SelectedPlane != null && SelectedPlane != "ICAO");
if (planeSelected && mapIsVisible) {
$('#selected_infoblock').show();
}
else {
$('#selected_infoblock').hide();
}
}
// Reposition selected plane info box if it overlaps plane marker
function adjustSelectedInfoBlockPosition() {
if (typeof Planes === 'undefined' || typeof SelectedPlane === 'undefined' || Planes === null) {
return;
}
var selectedPlane = Planes[SelectedPlane];
if (selectedPlane === undefined || selectedPlane === null || selectedPlane.marker === undefined || selectedPlane.marker === null) {
return;
}
try {
// Get marker position
var marker = selectedPlane.marker;
var markerCoordinates = selectedPlane.marker.getGeometry().getCoordinates();
var markerPosition = OLMap.getPixelFromCoordinate(markerCoordinates);
// Get info box position and size
var infoBox = $('#selected_infoblock');
var infoBoxPosition = infoBox.position();
var infoBoxExtent = getExtent(infoBoxPosition.left, infoBoxPosition.top, infoBox.outerWidth(), infoBox.outerHeight());
// Get map size
var mapCanvas = $('#map_canvas');
var mapExtent = getExtent(0, 0, mapCanvas.width(), mapCanvas.height());
// Check for overlap
if (isPointInsideExtent(markerPosition[0], markerPosition[1], infoBoxExtent)) {
// Array of possible new positions for info box
var candidatePositions = [];
candidatePositions.push( { x: 20, y: 20 } );
candidatePositions.push( { x: 20, y: markerPosition[1] + 40 } );
// Find new position
for (var i = 0; i < candidatePositions.length; i++) {
var candidatePosition = candidatePositions[i];
var candidateExtent = getExtent(candidatePosition.x, candidatePosition.y, infoBox.outerWidth(), infoBox.outerHeight());
if (!isPointInsideExtent(markerPosition[0], markerPosition[1], candidateExtent) && isPointInsideExtent(candidatePosition.x, candidatePosition.y, mapExtent)) {
// Found a new position that doesn't overlap marker - move box to that position
infoBox.css("left", candidatePosition.x);
infoBox.css("top", candidatePosition.y);
return;
}
}
}
}
catch(e) { }
}
function getExtent(x, y, width, height) {
return {
xMin: x,
yMin: y,
xMax: x + width - 1,
yMax: y + height - 1,
};
}
function isPointInsideExtent(x, y, extent) {
return x >= extent.xMin && x <= extent.xMax && y >= extent.yMin && y <= extent.yMax;
}
function initializeUnitsSelector() {
// Get display unit preferences from local storage
if (!localStorage.getItem('displayUnits')) {
localStorage['displayUnits'] = "nautical";
}
var displayUnits = localStorage['displayUnits'];
DisplayUnits = displayUnits;
// Initialize drop-down
var unitsSelector = $("#units_selector");
unitsSelector.val(displayUnits);
unitsSelector.on("change", onDisplayUnitsChanged);
}
function onDisplayUnitsChanged(e) {
var displayUnits = event.target.value;
// Save display units to local storage
localStorage['displayUnits'] = displayUnits;
DisplayUnits = displayUnits;
// Update filters
updatePlaneFilter();
// Refresh data
refreshTableInfo();
refreshSelected();
// Redraw range rings
if (SitePosition !== null && SitePosition !== undefined && SiteCircles) {
createSiteCircleFeatures();
}
// Reset map scale line units
OLMap.getControls().forEach(function(control) {
if (control instanceof ol.control.ScaleLine) {
control.setUnits(displayUnits);
}
});
}
function onFilterByAltitude(e) {
e.preventDefault();
updatePlaneFilter();
refreshTableInfo();
var selectedPlane = Planes[SelectedPlane];
if (selectedPlane !== undefined && selectedPlane !== null && selectedPlane.isFiltered()) {
SelectedPlane = null;
selectedPlane.selected = false;
selectedPlane.clearLines();
selectedPlane.updateMarker();
refreshSelected();
}
}
function onResetAltitudeFilter(e) {
$("#altitude_filter_min").val("");
$("#altitude_filter_max").val("");
updatePlaneFilter();
refreshTableInfo();
}
function updatePlaneFilter() {
var minAltitude = parseFloat($("#altitude_filter_min").val().trim());
var maxAltitude = parseFloat($("#altitude_filter_max").val().trim());
if (minAltitude === NaN) {
minAltitude = -Infinity;
}
if (maxAltitude === NaN) {
maxAltitude = Infinity;
}
PlaneFilter.minAltitude = minAltitude;
PlaneFilter.maxAltitude = maxAltitude;
PlaneFilter.altitudeUnits = DisplayUnits;
}
function getFlightAwareIdentLink(ident, linkText) {
if (ident !== null && ident !== "") {
if (!linkText) {
linkText = ident;
}
return "<a target=\"_blank\" href=\"https://flightaware.com/live/flight/" + ident.trim() + "\">" + linkText + "</a>";
}
return "";
}
function getFlightAwareModeSLink(code, ident, linkText) {
if (code !== null && code.length > 0 && code[0] !== '~' && code !== "000000") {
if (!linkText) {
linkText = "FlightAware: " + code.toUpperCase();
}
var linkHtml = "<a target=\"_blank\" href=\"https://flightaware.com/live/modes/" + code ;
if (ident !== null && ident !== "") {
linkHtml += "/ident/" + ident.trim();
}
linkHtml += "/redirect\">" + linkText + "</a>";
return linkHtml;
}
return "";
}
function getFlightAwarePhotoLink(registration) {
if (registration !== null && registration !== "") {
return "<a target=\"_blank\" href=\"https://flightaware.com/photos/aircraft/" + registration.trim() + "\">See Photos</a>";
}
return "";
}
function getAirframesModeSLink(code) {
if (code !== null && code.length > 0 && code[0] !== '~' && code !== "000000") {
return "<a href=\"http://www.airframes.org/\" onclick=\"$('#airframes_post_icao').attr('value','" + code + "'); document.getElementById('horrible_hack').submit.call(document.getElementById('airframes_post')); return false;\">Airframes.org: " + code.toUpperCase() + "</a>";
}
return "";
}

View file

@ -2,10 +2,96 @@ html, body {
margin: 0; padding: 0; background-color: #ffffff; font-family: Tahoma, Sans-Serif; margin: 0; padding: 0; background-color: #ffffff; font-family: Tahoma, Sans-Serif;
font-size: 10pt; overflow: auto; height: 100%; font-size: 10pt; overflow: auto; height: 100%;
} }
div#map_container { float: left; width: 100%; height: 100%; }
div#map_canvas { height: 100%; margin-right: 420px; }
div#sidebar_container { float: left; width: 410px; margin-left: -410px; height: 100%; overflow: auto; } #layout_container {
display: flex;
height: 100%;
}
#selected_infoblock {
position: absolute;
left: 20px;
top: 20px;
min-width: 360px;
padding: 20px;
background: #ffffff;
box-shadow: 4px 4px 10px #444444;
cursor: pointer;
z-index: 9999;
}
#map_container {
flex: 1 1 auto;
position: relative;
height: 100%;
}
#map_canvas {
position: absolute;
width: 100%;
height: 100%;
}
#toggle_sidebar_button {
display: block;
width: 48px;
height: 40px;
position: absolute;
top: 10px;
right: 10px;
}
#toggle_sidebar_button.show_sidebar {
background-image: url("images/show_sidebar_inactive_48x40.png");
}
#toggle_sidebar_button.show_sidebar:hover {
background-image: url("images/show_sidebar_active_48x40.png");
}
#toggle_sidebar_button.hide_sidebar {
background-image: url("images/hide_sidebar_inactive_48x40.png");
}
#toggle_sidebar_button.hide_sidebar:hover {
background-image: url("images/hide_sidebar_active_48x40.png");
}
#expand_sidebar_button {
display: block;
width: 48px;
height: 40px;
position: absolute;
top: 56px;
right: 10px;
}
#expand_sidebar_button {
background-image: url("images/show_sidebar_inactive_48x40.png");
}
#expand_sidebar_button:hover {
background-image: url("images/show_sidebar_active_48x40.png");
}
#sidebar_container {
display: flex;
width: 470px;
left: 0 !important;
}
#splitter {
flex: 0 0 6px;
cursor: col-resize;
background-color: #bbbbbb;
left: 0 !important;
}
#sidebar_canvas {
flex: 1 1 auto;
padding: 10px;
overflow: scroll;
}
div#SpecialSquawkWarning { position: absolute; bottom: 25px; right: 430px; border: 2px solid red; div#SpecialSquawkWarning { position: absolute; bottom: 25px; right: 430px; border: 2px solid red;
background-color: #FFFFA3; opacity: 0.75; filter:alpha(opacity=75); padding: 5px; background-color: #FFFFA3; opacity: 0.75; filter:alpha(opacity=75); padding: 5px;
@ -19,10 +105,37 @@ div#loader { z-index: 99; position: absolute; left: 0; top: 0; bottom: 0; right:
#spinny { width: 128px; height: 128px; position: absolute; top: 50%; left: 50%; margin: -64px 0 0 -64px; } #spinny { width: 128px; height: 128px; position: absolute; top: 50%; left: 50%; margin: -64px 0 0 -64px; }
#loader_progress { width: 250px; height: 20px; position: absolute; top: 50%; left: 50%; margin: 128px 0 0 -125px; } #loader_progress { width: 250px; height: 20px; position: absolute; top: 50%; left: 50%; margin: 128px 0 0 -125px; }
#tableinfo, #sudo_buttons { font-size: x-small; font-family: monospace; } #tableinfo { font-size: small; }
.vPosition { font-weight: bold; background-color: #d5ffd5; } #sudo_buttons {
.mlat { font-weight: bold; background-color: #d5d5ff; } display: flex;
padding: 15px 40px 15px 40px;
justify-content: space-between;
}
#units_container,
#altitude_filter_form {
font-size: small;
margin: 10px 0 10px 0;
}
.aircraft_table_header {
background-color: #409EDF;
color: #FFFFFF;
cursor: pointer;
}
.aircraft_table_header td {
font-size: smaller;
padding: 5px;
text-align: center;
}
.verticalRateTriangle {
font-family: "Courier New",monospace;
}
.vPosition { background-color: #C3FFDF; }
.mlat { background-color: #C7EAFC; }
.squawk7500 { font-weight: bold; background-color: #ff5555; } .squawk7500 { font-weight: bold; background-color: #ff5555; }
.squawk7600 { font-weight: bold; background-color: #00ffff; } .squawk7600 { font-weight: bold; background-color: #00ffff; }
.squawk7700 { font-weight: bold; background-color: #ffff00; } .squawk7700 { font-weight: bold; background-color: #ffff00; }
@ -42,3 +155,33 @@ div#loader { z-index: 99; position: absolute; left: 0; top: 0; bottom: 0; right:
.pointer { cursor: pointer; } .pointer { cursor: pointer; }
.sidebarButton {
background-color: #409EDF;
padding: 4px 15px 4px 15px;
color: #FFFFFF;
font-weight: normal;
font-size: small;
}
.sidebarButton:hover {
background-color: #3c6ea3;
}
.altitudeFilterInput {
width: 50px;
}
select.error, textarea.error, input.error {
color: #FF0000;
}
.ol-zoom {
left: auto !important;
right: 20px !important;
top: 120px !important;
}
.layer-switcher {
top: 190px !important;
right: 10px !important;
}