dump1090/public_html/planeObject.js
Oliver Jowett f1f76cb8e3 When receiving mlat, allow larger gaps between positions before plotting estimated tracks.
mlat positions aren't generally so frequent as proper ES, so they'd almost always plot
as estimated.
2015-06-29 12:44:19 +01:00

424 lines
16 KiB
JavaScript

"use strict";
function PlaneObject(icao) {
// Info about the plane
this.icao = icao;
this.flight = null;
this.squawk = null;
this.selected = false;
this.category = null;
// Basic location information
this.altitude = null;
this.speed = null;
this.track = null;
this.position = null;
this.sitedist = null;
// Data packet numbers
this.messages = null;
this.rssi = null;
// Track history as a series of line segments
this.track_linesegs = [];
this.history_size = 0;
// When was this last updated (receiver timestamp)
this.last_message_time = null;
this.last_position_time = null;
// When was this last updated (seconds before last update)
this.seen = null;
this.seen_pos = null;
// Display info
this.visible = true;
this.marker = null;
this.icon = { type: 'generic',
fillOpacity: 0.9 };
// request metadata
this.registration = null;
this.icaotype = null;
getAircraftData(this.icao).done(function(data) {
if ("r" in data) {
this.registration = data.r;
}
if ("t" in data) {
this.icaotype = data.t;
}
if (this.selected) {
refreshSelected();
}
}.bind(this));
}
// Appends data to the running track so we can get a visual tail on the plane
// Only useful for a long running browser session.
PlaneObject.prototype.updateTrack = function(estimate_time) {
var here = this.position;
if (!here)
return;
if (this.track_linesegs.length == 0) {
// Brand new track
//console.log(this.icao + " new track");
var newseg = { track : new google.maps.MVCArray([here,here]),
line : null,
head_update : this.last_position_time,
tail_update : this.last_position_time,
estimated : false,
ground : (this.altitude === "ground")
};
this.track_linesegs.push(newseg);
this.history_size += 2;
return;
}
var lastseg = this.track_linesegs[this.track_linesegs.length - 1];
var lastpos = lastseg.track.getAt(lastseg.track.getLength() - 1);
var elapsed = (this.last_position_time - lastseg.head_update);
var new_data = (here !== lastpos);
var est_track = (elapsed > estimate_time);
var ground_track = (this.altitude === "ground");
if (!new_data)
return false;
if (est_track) {
if (!lastseg.estimated) {
// >5s gap in data, create a new estimated segment
//console.log(this.icao + " switching to estimated");
this.track_linesegs.push({ track : new google.maps.MVCArray([lastpos, here]),
line : null,
head_update : this.last_position_time,
estimated : true });
this.history_size += 2;
return true;
}
// Append to ongoing estimated line
//console.log(this.icao + " extending estimated (" + lastseg.track.getLength() + ")");
lastseg.track.push(here);
lastseg.head_update = this.last_position_time;
this.history_size++;
return true;
}
if (lastseg.estimated) {
// We are back to good data.
//console.log(this.icao + " switching to good track");
this.track_linesegs.push({ track : new google.maps.MVCArray([lastpos, here]),
line : null,
head_update : this.last_position_time,
tail_update : this.last_position_time,
estimated : false,
ground : (this.altitude === "ground") });
this.history_size += 2;
return true;
}
if ( (lastseg.ground && this.altitude !== "ground") ||
(!lastseg.ground && this.altitude === "ground") ) {
//console.log(this.icao + " ground state changed");
// Create a new segment as the ground state changed.
// assume the state changed halfway between the two points
var midpoint = google.maps.geometry.spherical.interpolate(lastpos,here,0.5);
lastseg.track.push(midpoint);
this.track_linesegs.push({ track : new google.maps.MVCArray([midpoint,here,here]),
line : null,
head_update : this.last_position_time,
tail_update : this.last_position_time,
estimated : false,
ground : (this.altitude === "ground") });
this.history_size += 4;
return true;
}
// Add more data to the existing track.
// We only retain some historical points, at 5+ second intervals,
// plus the most recent point
if (this.last_position_time - lastseg.tail_update >= 5) {
// enough time has elapsed; retain the last point and add a new one
//console.log(this.icao + " retain last point");
lastseg.track.push(here);
lastseg.tail_update = lastseg.head_update;
this.history_size ++;
} else {
// replace the last point with the current position
lastseg.track.setAt(lastseg.track.getLength()-1, here);
}
lastseg.head_update = this.last_position_time;
return true;
};
// This is to remove the line from the screen if we deselect the plane
PlaneObject.prototype.clearLines = function() {
for (var i = 0; i < this.track_linesegs.length; ++i) {
var seg = this.track_linesegs[i];
if (seg.line !== null) {
seg.line.setMap(null);
seg.line = null;
}
}
};
PlaneObject.prototype.getMarkerIconType = function() {
var lookup = {
'A1' : 'light',
'A2' : 'medium',
'A3' : 'medium',
'A5' : 'heavy',
'A7' : 'rotorcraft'
};
if (this.category === null || !(this.category in lookup))
return 'generic'
else
return lookup[this.category];
}
PlaneObject.prototype.getMarkerColor = function() {
// Emergency squawks override everything else
if (this.squawk in SpecialSquawks)
return SpecialSquawks[this.squawk].markerColor;
var h, s, l;
if (this.altitude === null) {
h = ColorByAlt.unknown.h;
s = ColorByAlt.unknown.s;
l = ColorByAlt.unknown.l;
} else if (this.altitude === "ground") {
h = ColorByAlt.ground.h;
s = ColorByAlt.ground.s;
l = ColorByAlt.ground.l;
} else {
s = ColorByAlt.air.s;
l = ColorByAlt.air.l;
// find the pair of points the current altitude lies between,
// and interpolate the hue between those points
var hpoints = ColorByAlt.air.h;
h = hpoints[0].val;
for (var i = hpoints.length-1; i >= 0; --i) {
if (this.altitude > hpoints[i].alt) {
if (i == hpoints.length-1) {
h = hpoints[i].val;
} else {
h = hpoints[i].val + (hpoints[i+1].val - hpoints[i].val) * (this.altitude - hpoints[i].alt) / (hpoints[i+1].alt - hpoints[i].alt)
}
break;
}
}
}
// If we have not seen a recent position update, change color
if (this.seen_pos > 15) {
h += ColorByAlt.stale.h;
s += ColorByAlt.stale.s;
l += ColorByAlt.stale.l;
}
// If this marker is selected, change color
if (this.selected){
h += ColorByAlt.selected.h;
s += ColorByAlt.selected.s;
l += ColorByAlt.selected.l;
}
if (h < 0) {
h = (h % 360) + 360;
} else if (h >= 360) {
h = h % 360;
}
if (s < 5) s = 5;
else if (s > 95) s = 95;
if (l < 5) l = 5;
else if (l > 95) l = 95;
return 'hsl(' + h.toFixed(0) + ',' + s.toFixed(0) + '%,' + l.toFixed(0) + '%)'
}
PlaneObject.prototype.updateIcon = function() {
var col = this.getMarkerColor();
var type = this.getMarkerIconType();
var weight = this.selected ? 2 : 1;
var rotation = (this.track === null ? 0 : this.track);
if (col === this.icon.fillColor && weight === this.icon.strokeWeight && rotation === this.icon.rotation && type == this.icon.type)
return false; // no changes
this.icon.fillColor = col;
this.icon.strokeWeight = weight;
this.icon.rotation = rotation;
this.icon.type = type;
this.icon.path = MarkerIcons[type].path;
this.icon.anchor = MarkerIcons[type].anchor;
this.icon.scale = MarkerIcons[type].scale;
if (this.marker)
this.marker.setIcon(this.icon);
return true;
};
// Update our data
PlaneObject.prototype.updateData = function(receiver_timestamp, data) {
// Update all of our data
this.messages = data.messages;
this.rssi = data.rssi;
this.last_message_time = receiver_timestamp - data.seen;
if (typeof data.altitude !== "undefined")
this.altitude = data.altitude;
if (typeof data.vert_rate !== "undefined")
this.vert_rate = data.vert_rate;
if (typeof data.speed !== "undefined")
this.speed = data.speed;
if (typeof data.track !== "undefined")
this.track = data.track;
if (typeof data.lat !== "undefined") {
this.position = new google.maps.LatLng(data.lat, data.lon);
this.last_position_time = receiver_timestamp - data.seen_pos;
if (SitePosition !== null) {
this.sitedist = google.maps.geometry.spherical.computeDistanceBetween (SitePosition, this.position);
}
}
if (typeof data.flight !== "undefined")
this.flight = data.flight;
if (typeof data.squawk !== "undefined")
this.squawk = data.squawk;
if (typeof data.category !== "undefined")
this.category = data.category;
this.position_from_mlat = false
if (typeof data.mlat !== "undefined") {
for (var i = 0; i < data.mlat.length; ++i) {
if (data.mlat[i] === "lat" || data.mlat[i] == "lon") {
this.position_from_mlat = true;
break;
}
}
}
};
PlaneObject.prototype.updateTick = function(receiver_timestamp, last_timestamp) {
// recompute seen and seen_pos
this.seen = receiver_timestamp - this.last_message_time;
this.seen_pos = (this.last_position_time === null ? null : receiver_timestamp - this.last_position_time);
// If no packet in over 58 seconds, clear the plane.
if (this.seen > 58) {
if (this.visible) {
//console.log("hiding " + this.icao);
this.clearMarker();
this.visible = false;
if (SelectedPlane == this.icao)
selectPlaneByHex(null,false);
}
} else {
this.visible = true;
if (this.position !== null) {
if (this.updateTrack(receiver_timestamp - last_timestamp + (this.position_from_mlat ? 30 : 5))) {
this.updateLines();
this.updateMarker(true);
} else {
this.updateMarker(false); // didn't move
}
}
}
};
PlaneObject.prototype.clearMarker = function() {
if (this.marker) {
this.marker.setMap(null);
google.maps.event.clearListeners(this.marker, 'click');
this.marker = null;
}
};
// Update our marker on the map
PlaneObject.prototype.updateMarker = function(moved) {
if (!this.visible) {
this.clearMarker();
return;
}
if (this.marker) {
if (moved)
this.marker.setPosition(this.position);
this.updateIcon();
} else {
this.updateIcon();
this.marker = new google.maps.Marker({
position: this.position,
map: GoogleMap,
icon: this.icon,
visible: true
});
// Trap clicks for this marker.
google.maps.event.addListener(this.marker, 'click', selectPlaneByHex.bind(undefined,this.icao,false));
google.maps.event.addListener(this.marker, 'dblclick', selectPlaneByHex.bind(undefined,this.icao,true));
}
// Setting the marker title
var title = (this.flight === null || this.flight.length == 0) ? this.icao : (this.flight+' ('+this.icao+')');
if (title !== this.marker.title)
this.marker.setTitle(title);
};
// Update our planes tail line,
PlaneObject.prototype.updateLines = function() {
if (!this.selected)
return;
for (var i = 0; i < this.track_linesegs.length; ++i) {
var seg = this.track_linesegs[i];
if (seg.line === null) {
// console.log("create line for seg " + i + " with " + seg.track.getLength() + " points" + (seg.estimated ? " (estimated)" : ""));
// for (var j = 0; j < seg.track.getLength(); j++) {
// console.log(" point " + j + " at " + seg.track.getAt(j).lat() + "," + seg.track.getAt(j).lng());
// }
if (seg.estimated) {
var lineSymbol = {
path: 'M 0,-1 0,1',
strokeOpacity : 1,
strokeColor : '#804040',
strokeWeight : 2,
scale: 2
};
seg.line = new google.maps.Polyline({
path: seg.track,
strokeOpacity: 0,
icons: [{
icon: lineSymbol,
offset: '0',
repeat: '10px' }],
map : GoogleMap });
} else {
seg.line = new google.maps.Polyline({
path: seg.track,
strokeOpacity: 1.0,
strokeColor: (seg.ground ? '#408040' : '#000000'),
strokeWeight: 3,
map: GoogleMap });
}
}
}
};
PlaneObject.prototype.destroy = function() {
this.clearLines();
this.clearMarker();
};