Make PlaneObject a class.

Rearrange internal storage of positions.
Maintain sitedist within PlaneObject, not externally.
Clean up speed/dist/etc formatting.
Show both metric & imperial values in the plane detail infobox.
This commit is contained in:
Oliver Jowett 2015-01-07 17:18:33 +00:00
parent 58e5485c2a
commit 29509d9633
3 changed files with 419 additions and 413 deletions
public_html

View file

@ -28,333 +28,329 @@ var PlaneSvg = "M 0,0 " +
"-2.858644 -0.145909,-5.208974 -0.209316,-5.222958 -0.06341,-0.01399 -0.974464," +
"-0.0493 -2.024551,-0.07845 L 23.247235,38.61921 18.831373,39.8906 C 4.9432155," +
"43.88916 4.2929558,44.057819 3.4954426,43.86823 2.7487826,43.690732 2.2007966," +
"42.916622 1.9565564,41.694305 z"
"42.916622 1.9565564,41.694305 z";
var planeObject = {
// Basic location information
altitude : null,
speed : null,
track : null,
latitude : null,
longitude : null,
function PlaneObject(icao) {
// Info about the plane
flight : null,
squawk : null,
icao : null,
is_selected : false,
this.icao = icao;
this.flight = null;
this.squawk = null;
this.selected = false;
// Basic location information
this.altitude = null;
this.speed = null;
this.track = null;
this.position = null;
this.sitedist = null;
// Data packet numbers
messages : null,
this.messages = null;
// Track history as a series of line segments
track_linesegs : [],
this.track_linesegs = [];
this.history_size = 0;
// When was this last updated (receiver timestamp)
last_message_time : null,
last_position_time : null,
this.last_message_time = null;
this.last_position_time = null;
// When was this last updated (seconds before last update)
seen : null,
seen_pos : null,
this.seen = null;
this.seen_pos = null;
history_size : 0,
visible : true,
// Display info
this.visible = true;
this.marker = null;
this.icon = { strokeWeight: 1,
path: PlaneSvg,
scale: 0.4,
fillColor: MarkerColor,
fillOpacity: 0.9,
anchor: new google.maps.Point(32, 32), // Set anchor to middle of plane.
rotation: 0 };
}
// GMap Details
marker : null,
icon : {
strokeWeight: 1,
path: PlaneSvg,
scale: 0.4,
fillColor: MarkerColor,
fillOpacity: 0.9,
anchor: new google.maps.Point(32, 32), // Set anchor to middle of plane.
rotation: 0
},
// 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() {
var here = this.position;
// Appends data to the running track so we can get a visual tail on the plane
// Only useful for a long running browser session.
updateTrack : function() {
var here = new google.maps.LatLng(this.latitude, this.longitude);
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 > 5);
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
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;
}
}
},
updateIcon : function() {
var col = MarkerColor;
// If this marker is selected we should make it lighter than the rest.
if (this.is_selected)
col = SelectedColor;
// If we have not seen a recent update, change color
if (this.seen > 15)
col = StaleColor;
// If the squawk code is one of the international emergency codes,
// match the info window alert color.
if (this.squawk in SpecialSquawks)
col = SpecialSquawks[this.squawk].markerColor;
var weight = this.is_selected ? 2 : 1;
var rotation = (this.track === null ? 0 : this.track);
if (col === this.icon.fillColor && weight === this.icon.strokeWeight && rotation === this.icon.rotation)
return false; // no changes
this.icon.fillColor = col;
this.icon.strokeWeight = weight;
this.icon.rotation = rotation;
if (this.marker)
this.marker.setIcon(this.icon);
return true;
},
// TODO: Trigger actions of a selecting a plane
selectPlane : function(){
selectPlaneByHex(this.icao);
},
// Update our data
updateData : function(receiver_timestamp, data){
// Update all of our data
this.icao = data.hex;
this.messages = data.messages;
this.last_message_time = receiver_timestamp - data.seen;
if (typeof data.altitude !== "undefined")
this.altitude = data.altitude;
if (typeof data.speed !== "undefined")
this.speed = data.speed;
if (typeof data.track !== "undefined")
this.track = data.track;
if (typeof data.lat !== "undefined") {
this.latitude = data.lat;
this.longitude = data.lon;
this.last_position_time = receiver_timestamp - data.seen_pos;
}
if (typeof data.flight !== "undefined")
this.flight = data.flight;
if (typeof data.squawk !== "undefined")
this.squawk = data.squawk;
},
updateTick : function(receiver_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);
}
} else {
this.visible = true;
if (this.latitude !== null) {
if (this.updateTrack()) {
this.updateLines();
this.updateMarker(true);
} else {
this.updateMarker(false); // didn't move
}
}
}
},
clearMarker: function() {
if (this.marker) {
this.marker.setMap(null);
this.marker = null;
}
},
// Update our marker on the map
updateMarker: function(moved) {
if (!this.visible) {
this.clearMarker();
return;
}
if (this.marker) {
if (moved)
this.marker.setPosition(new google.maps.LatLng(this.latitude, this.longitude));
this.updateIcon();
} else {
this.updateIcon();
this.marker = new google.maps.Marker({
position: new google.maps.LatLng(this.latitude, this.longitude),
map: GoogleMap,
icon: this.icon,
visible: true
});
// This is so we can match icao address
this.marker.icao = this.icao;
// Trap clicks for this marker.
google.maps.event.addListener(this.marker, 'click', this.selectPlane);
}
// Setting the marker title
if (this.flight === null || this.flight.length == 0) {
this.marker.setTitle(this.hex);
} else {
this.marker.setTitle(this.flight+' ('+this.icao+')');
}
},
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;
}
// Update our planes tail line,
updateLines: function() {
if (!this.is_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 });
}
}
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 > 5);
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;
};
destroy : function() {
this.clearLines();
this.clearMarker();
// 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.updateIcon = function() {
var col = MarkerColor;
// If this marker is selected we should make it lighter than the rest.
if (this.is_selected)
col = SelectedColor;
// If we have not seen a recent update, change color
if (this.seen > 15)
col = StaleColor;
// If the squawk code is one of the international emergency codes,
// match the info window alert color.
if (this.squawk in SpecialSquawks)
col = SpecialSquawks[this.squawk].markerColor;
var weight = this.is_selected ? 2 : 1;
var rotation = (this.track === null ? 0 : this.track);
if (col === this.icon.fillColor && weight === this.icon.strokeWeight && rotation === this.icon.rotation)
return false; // no changes
this.icon.fillColor = col;
this.icon.strokeWeight = weight;
this.icon.rotation = rotation;
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.icao = data.hex;
this.messages = data.messages;
this.last_message_time = receiver_timestamp - data.seen;
if (typeof data.altitude !== "undefined")
this.altitude = data.altitude;
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;
};
PlaneObject.prototype.updateTick = function(receiver_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);
}
} else {
this.visible = true;
if (this.position !== null) {
if (this.updateTrack()) {
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
});
// This is so we can match icao address
this.marker.icao = this.icao;
// Trap clicks for this marker.
google.maps.event.addListener(this.marker, 'click', selectPlaneByHex.bind(undefined,this.icao));
}
// Setting the marker title
if (this.flight === null || this.flight.length == 0) {
this.marker.setTitle(this.hex);
} else {
this.marker.setTitle(this.flight+' ('+this.icao+')');
}
};
// Update our planes tail line,
PlaneObject.prototype.updateLines = function() {
if (!this.is_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();
};