Merge branch 'master' into tracking-rewrite

This commit is contained in:
Oliver Jowett 2016-09-10 20:53:59 +01:00
commit 8e8b8588bf
41 changed files with 739 additions and 103 deletions

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
*.xz binary

View file

@ -24,7 +24,7 @@ UNAME := $(shell uname)
ifeq ($(UNAME), Linux) ifeq ($(UNAME), Linux)
LIBS+=-lrt LIBS+=-lrt
CFLAGS+=-std=c11 -D_BSD_SOURCE=1 -D_POSIX_C_SOURCE=200809L CFLAGS+=-std=c11 -D_DEFAULT_SOURCE
endif endif
ifeq ($(UNAME), Darwin) ifeq ($(UNAME), Darwin)
# TODO: Putting GCC in C11 mode breaks things. # TODO: Putting GCC in C11 mode breaks things.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -16,6 +16,7 @@
<script type="text/javascript" src="config.js"></script> <script type="text/javascript" src="config.js"></script>
<script type="text/javascript" src="markers.js"></script> <script type="text/javascript" src="markers.js"></script>
<script type="text/javascript" src="dbloader.js"></script> <script type="text/javascript" src="dbloader.js"></script>
<script type="text/javascript" src="registrations.js"></script>
<script type="text/javascript" src="planeObject.js"></script> <script type="text/javascript" src="planeObject.js"></script>
<script type="text/javascript" src="formatter.js"></script> <script type="text/javascript" src="formatter.js"></script>
<script type="text/javascript" src="flags.js"></script> <script type="text/javascript" src="flags.js"></script>

View file

@ -58,43 +58,194 @@ var _beechcraft_svg =
var _heavy_svg = var _heavy_svg =
"m28.64874,12.035023l0,8.801421l-4.585627,3.066495c0.126825,-0.257055 0.094102,-0.531839 0.095802,-0.802796l-0.015437,-3.087446l-2.230453,-0.012673l0.019009,3.599141c0.000513,0.577993 0.076338,0.923195 0.589296,1.241956l-5.533809,3.630512c0.166511,-0.256275 0.153699,-0.551367 0.153699,-0.841892l-0.005929,-3.270195l-2.160751,-0.012672l-0.006337,3.637159c0.016349,0.5301 0.096662,1.090947 0.576623,1.419379l-11.976014,7.825597c-2.106287,1.48859 -1.705322,3.044253 -1.56512,4.587637l26.645047,-9.048544l0,13.750239l0.722364,5.062875l-8.681027,6.387208c-1.239945,1.059417 -1.080616,2.171837 -0.842757,3.256969l11.278998,-2.946479c0.130159,3.116897 1.559821,3.171571 1.780561,0.006336l11.278998,2.94648c0.23786,-1.085133 0.397189,-2.197552 -0.842756,-3.256969l-8.681026,-6.387207l0.722362,-5.062875l0,-13.750239l26.645048,9.042207c0.140203,-1.543381 0.541167,-3.092711 -1.56512,-4.581301l-11.976015,-7.825597c0.47996,-0.328434 0.553938,-0.889279 0.570286,-1.419379l0,-3.63716l-2.160751,0.012673l-0.005378,3.328244c-0.002334,0.294243 0.007077,0.545056 0.178191,0.817583l-5.565189,-3.664251c0.512962,-0.318761 0.59512,-0.663963 0.595633,-1.241956l0.019009,-3.599141l-2.230454,0.012673l-0.015793,3.100403c0.001462,0.282341 -0.019949,0.535579 0.124839,0.794638l-4.614307,-3.071294l0,-8.801421c-1.111672,-11.152869 -5.489391,-11.217579 -6.735717,-0.006336z"; "m28.64874,12.035023l0,8.801421l-4.585627,3.066495c0.126825,-0.257055 0.094102,-0.531839 0.095802,-0.802796l-0.015437,-3.087446l-2.230453,-0.012673l0.019009,3.599141c0.000513,0.577993 0.076338,0.923195 0.589296,1.241956l-5.533809,3.630512c0.166511,-0.256275 0.153699,-0.551367 0.153699,-0.841892l-0.005929,-3.270195l-2.160751,-0.012672l-0.006337,3.637159c0.016349,0.5301 0.096662,1.090947 0.576623,1.419379l-11.976014,7.825597c-2.106287,1.48859 -1.705322,3.044253 -1.56512,4.587637l26.645047,-9.048544l0,13.750239l0.722364,5.062875l-8.681027,6.387208c-1.239945,1.059417 -1.080616,2.171837 -0.842757,3.256969l11.278998,-2.946479c0.130159,3.116897 1.559821,3.171571 1.780561,0.006336l11.278998,2.94648c0.23786,-1.085133 0.397189,-2.197552 -0.842756,-3.256969l-8.681026,-6.387207l0.722362,-5.062875l0,-13.750239l26.645048,9.042207c0.140203,-1.543381 0.541167,-3.092711 -1.56512,-4.581301l-11.976015,-7.825597c0.47996,-0.328434 0.553938,-0.889279 0.570286,-1.419379l0,-3.63716l-2.160751,0.012673l-0.005378,3.328244c-0.002334,0.294243 0.007077,0.545056 0.178191,0.817583l-5.565189,-3.664251c0.512962,-0.318761 0.59512,-0.663963 0.595633,-1.241956l0.019009,-3.599141l-2.230454,0.012673l-0.015793,3.100403c0.001462,0.282341 -0.019949,0.535579 0.124839,0.794638l-4.614307,-3.071294l0,-8.801421c-1.111672,-11.152869 -5.489391,-11.217579 -6.735717,-0.006336z";
var MarkerIcons = { // From https://discussions.flightaware.com/ads-b-flight-tracking-f21/some-custom-svg-plane-icons-t37783.html
generic : { // by Peter Lowden
scale : 0.4, // licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
size : [64, 64],
anchor : [32, 32],
path : _generic_plane_svg
},
light : { // NB: scaled so that 1 pixel is about 1.33m (0.75px = 1m)
scale : 0.4, var _a320 = {
key: "a320",
scale: 0.60 * 0.75,
size: [64, 64],
anchor: [32, 28],
path: "m 32,1 2,1 2,3 0,18 4,1 0,-4 3,0 0,5 17,6 0,3 -15,-2 -9,0 0,12 -2,6 7,3 0,2 -8,-1 -1,2 -1,-2 -8,1 0,-2 7,-3 -2,-6 0,-12 -9,0 -15,2 0,-3 17,-6 0,-5 3,0 0,4 4,-1 0,-18 2,-3 2,-1z"
};
var _b777 = {
key: "b777",
scale: 1.15 * 0.75,
size: [64, 64],
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"
};
var _dash8 = {
key: "dash8",
scale: 0.52 * 0.75,
size: [64, 64],
anchor: [32, 30],
path: "m 32,1 3,4 0,20 4,0 0,-5 1,-1 1,1 0,5 17,2 0,3 -17,2 0,3 -1,1 -1,-1 0,-3 -4,0 0,15 -1,8 6,0 1,1 0,3 -8,0 -1,1 -1,-1 -8,0 0,-3 1,-1 6,0 -1,-8 0,-15 -4,0 0,3, -1,1 -1,-1 0,-3 -17,-2 0,-3 17,-2 0,-5 1,-1 1,1 0,5 4,0 0,-20 3,-4z"
};
var _b200 = {
key: "b200",
scale: 0.31 * 0.75,
size: [64, 64],
anchor: [32, 19],
path: "m 32,1 1,0 1,2 1,4 0,5 5,0 0,-5 -1,-1 2,-2 2,2 -1,1 0,5 17,2 0,3 -17,3 0,1 -2,0 0,-1 -5,0 0,5 -2,8 6,3 0,2 -6,-1 -1,0 -6,1 0,-2 6,-3 -2,-8 0,-5 -5,0 0,1, -2,0 0,-1 -17,-3 0,-3 17,-2 0,-5 -1,-1 2,-2 2,2 -1,1 0,5 5,0 0,-5 1,-4 1,-2 z"
};
var _g650 = {
key: "g650",
scale: 0.58 * 0.75,
size: [64, 64],
anchor: [32, 26],
path: "m 32,1 1,0 1,2 1,4 0,10 21,17 0,5 -2,-2 -16,-8 -3,0 0,3 2,0 1,1 0,5 -1,1 0,3 -2,0 0,1 7,5 0,3 -9,-3 -1,0 -9,3 0,-3 7,-5 0,-1 -2,0 0,-3 -1,-1 0,-5 1,-1 2,0 0,-3 -3,0 -16,8 -2,2 0,-5 21,-17 0,-10 1,-4 1,-2z"
};
var _c130 = {
key: "c130",
scale: 0.75 * 0.75,
size: [64, 64],
anchor: [32, 17],
path: "m 31,1 1,0 1,1 1,2 0,8 3,0 0,-3 1,-1 1,1 0,3 6,0 0,-3 1,-1 1,1 0,3 10,1 0,2 -1,1 -17,3 -5,0 0,10 -1,1 8,2 0,1 -1,1 -8,0 -1,1 -1,-1 -8,0 -1,-1 0,-1 8,-2 -1,-1 0,-10 -5,0 -17,-3 -1,-1 0,-2 10,-1 0,-3 1,-1 1,1 0,3 6,0 0,-3 1,-1 1,1 0,3 3,0 0,-8 1,-2 1,-1 z"
};
var _balloon = {
key: "balloon",
scale: 0.50,
size: [64, 64],
anchor: [32, 32],
path: "m 27,1 10,0 3,1 3,1 1,1 2,1 6,6 1,2 1,1 1,3 1,3 0,10 -1,3 -1,3 -1,1 -1,2 -6,6 -2,1 -1,1 -2,1 -2,1 -2,8 -1,0 2,-8 -3,1 -6,0 -3,-1 2,8 9,0 0,6 -10,0 0,-6 -2,-8 -2,-1 -2,-1 -1,-1 -2,-1 -6,-6 -1,-2 -1,-1 -1,-3 -1,-3 0,-10 1,-3 1,-3 1,-1 1,-2 6,-6 2,-1 1,-1 3,-1 3,-1 z",
noRotate: true,
markerRadius: 32
};
// by Oliver Jowett <oliver@mutability.co.uk>
// licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
var _a380 = {
key: "a380",
scale: 1.26 * 0.75,
size: [64, 64],
anchor: [32, 30],
path: "m 32,59 -1,-4 -4,1 -7,3 -1,0 1,-3 1,-1 7,-6 2,-2 -1,-5 0,-9 -1,-2 -2,0 -6,2 -5,2 -5,2 -9,4 0,1 0,-3 1,-2 9,-7 -1,-1 0,-4 1,-1 1,0 1,1 0,3 1,0 5,-4 0,-5 1,-1 1,0 1,1 0,3 6,-5 1,-2 0,-7 1,-5 1,-2 1,-1 1,1 1,2 1,5 0,7 1,2 6,5 0,-3 1,-1 1,0 1,1 0,5 5,4 1,0 0,-3 1,-1 1,0 1,1 0,4 -1,1 9,7 1,2 0,3 0,-1 -9,-4 -5,-2 -5,-2 -6,-2 -2,0 -1,2 0,9 -1,5 2,2 7,6 1,1 1,3 -1,0 -7,-3 -4,-1 -1,4 z"
};
var _b738 = {
key: "b738",
scale: 0.63 * 0.75,
size: [64, 64],
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"
};
var TypeIcons = {
'A318': _a320, // shortened a320
'A319': _a320, // shortened a320
'A320': _a320,
'A321': _a320, // stretched a320
'A388': _a380,
// dubious since these are old-generation 737s
// but the shape is similar
'B731': _b738,
'B732': _b738,
'B733': _b738,
'B734': _b738,
'B735': _b738,
// these probably need reworking
// since they vary in length
'B736': _b738,
'B737': _b738,
'B738': _b738,
'B739': _b738,
'B772': _b777, // all pretty similar except for length
'B77W': _b777,
'B773': _b777,
'B77L': _b777,
'DH8A': _dash8,
'DH8B': _dash8,
'DH8C': _dash8,
'DH8D': _dash8,
'BE20': _b200,
'GLF5': _g650, // close enough
'GLF6': _g650,
'C130': _c130,
'C30J': _c130
};
var CategoryIcons = {
"A1" : {
key : "A1",
scale : 0.30,
size : [64, 64], size : [64, 64],
anchor : [32, 25], anchor : [32, 25],
path : _beechcraft_svg path : _beechcraft_svg
}, },
medium : { "A2" : {
scale : 0.4, key : "A2",
scale : 0.35,
size : [64, 64], size : [64, 64],
anchor : [32, 32], anchor : [32, 32],
path : _generic_plane_svg path : _generic_plane_svg
}, },
heavy : { "A3" : {
scale : 0.6, key : "A3",
scale : 0.40,
size : [64, 64],
anchor : [32, 32],
path : _generic_plane_svg
},
"A5" : {
key : "A5",
scale : 0.73,
size : [64, 64], size : [64, 64],
anchor : [32, 32], anchor : [32, 32],
path : _heavy_svg path : _heavy_svg
}, },
rotorcraft : { "A7" : {
scale : 0.5, key : "A7",
scale : 0.50,
size : [64, 64], size : [64, 64],
anchor : [22, 32], anchor : [22, 32],
path : _rotorcraft_svg path : _rotorcraft_svg
} },
"B2" : _balloon
}; };
var DefaultIcon = {
key : "default",
scale : 0.4,
size : [64, 64],
anchor : [32, 32],
path : _generic_plane_svg
};
function getBaseMarker(category, type) {
if (type in TypeIcons) {
return TypeIcons[type];
}
if (category in CategoryIcons) {
return CategoryIcons[category];
}
return DefaultIcon;
}
function svgPathToSvg(path, size, stroke, width, fill) { function svgPathToSvg(path, size, stroke, width, fill) {
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 + '"';

View file

@ -39,12 +39,17 @@ function PlaneObject(icao) {
this.marker = null; this.marker = null;
this.markerStyle = null; this.markerStyle = null;
this.markerIcon = null; this.markerIcon = null;
this.markerStaticStyle = null;
this.markerStaticIcon = null;
this.markerStyleKey = null; this.markerStyleKey = null;
this.markerSvgKey = null; this.markerSvgKey = null;
// request metadata // start from a computed registration, let the DB override it
this.registration = null; // if it has something else.
this.registration = registration_from_hexid(this.icao);
this.icaotype = null; this.icaotype = null;
// request metadata
getAircraftData(this.icao).done(function(data) { getAircraftData(this.icao).done(function(data) {
if ("r" in data) { if ("r" in data) {
this.registration = data.r; this.registration = data.r;
@ -178,22 +183,6 @@ PlaneObject.prototype.clearLines = function() {
} }
}; };
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() { PlaneObject.prototype.getMarkerColor = function() {
// Emergency squawks override everything else // Emergency squawks override everything else
if (this.squawk in SpecialSquawks) if (this.squawk in SpecialSquawks)
@ -269,35 +258,71 @@ 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 type = this.getMarkerIconType(); var baseMarker = getBaseMarker(this.category, this.icaotype);
var weight = ((this.selected ? 2 : 1) / MarkerIcons[type].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 svgKey = col + '!' + outline + '!' + type + '!' + weight; var svgKey = col + '!' + outline + '!' + baseMarker.key + '!' + weight;
var styleKey = opacity + '!' + rotation; var styleKey = opacity + '!' + rotation;
if (this.markerStyle === null || this.markerIcon === null || this.markerSvgKey != svgKey) { if (this.markerStyle === null || this.markerIcon === null || this.markerSvgKey != svgKey) {
//console.log(this.icao + " new icon and style " + this.markerSvgKey + " -> " + svgKey); //console.log(this.icao + " new icon and style " + this.markerSvgKey + " -> " + svgKey);
this.markerIcon = new ol.style.Icon({ var icon = new ol.style.Icon({
anchor: MarkerIcons[type].anchor, anchor: baseMarker.anchor,
anchorXUnits: 'pixels', anchorXUnits: 'pixels',
anchorYUnits: 'pixels', anchorYUnits: 'pixels',
scale: MarkerIcons[type].scale, scale: baseMarker.scale,
imgSize: MarkerIcons[type].size, imgSize: baseMarker.size,
src: svgPathToURI(MarkerIcons[type].path, MarkerIcons[type].size, outline, weight, col), src: svgPathToURI(baseMarker.path, baseMarker.size, outline, weight, col),
rotation: rotation * Math.PI / 180.0, rotation: (baseMarker.noRotate ? 0 : rotation * Math.PI / 180.0),
opacity: opacity opacity: opacity,
rotateWithView: (baseMarker.noRotate ? false : true)
}); });
if (baseMarker.noRotate) {
// the base marker won't be rotated
this.markerStaticIcon = icon;
this.markerStaticStyle = new ol.style.Style({
image: this.markerStaticIcon
});
// create an arrow that we will rotate around the base marker
// to indicate heading
var offset = baseMarker.markerRadius * baseMarker.scale + 6;
var size = offset * 2;
var arrowPath = "M " + offset + ",0 m 4,4 -8,0 4,-4 z";
this.markerIcon = new ol.style.Icon({
anchor: [offset, offset],
anchorXUnits: 'pixels',
anchorYUnits: 'pixels',
scale: 1.0,
imgSize: [size, size],
src: svgPathToURI(arrowPath, [size, size], outline, 1, outline),
rotation: rotation * Math.PI / 180.0,
opacity: opacity,
rotateWithView: true
});
this.markerStyle = new ol.style.Style({
image: this.markerIcon
});
} else {
this.markerIcon = icon;
this.markerStyle = new ol.style.Style({
image: this.markerIcon
});
this.markerStaticIcon = null;
this.markerStaticStyle = new ol.style.Style({});
}
this.markerStyleKey = styleKey; this.markerStyleKey = styleKey;
this.markerSvgKey = svgKey; this.markerSvgKey = svgKey;
this.markerStyle = new ol.style.Style({
image: this.markerIcon
});
if (this.marker !== null) { if (this.marker !== null) {
this.marker.setStyle(this.markerStyle); this.marker.setStyle(this.markerStyle);
this.markerStatic.setStyle(this.markerStaticStyle);
} }
} }
@ -305,6 +330,9 @@ PlaneObject.prototype.updateIcon = function() {
//console.log(this.icao + " new rotation"); //console.log(this.icao + " new rotation");
this.markerIcon.setRotation(rotation * Math.PI / 180.0); this.markerIcon.setRotation(rotation * Math.PI / 180.0);
this.markerIcon.setOpacity(opacity); this.markerIcon.setOpacity(opacity);
if (this.staticIcon) {
this.staticIcon.setOpacity(opacity);
}
this.markerStyleKey = styleKey; this.markerStyleKey = styleKey;
} }
@ -385,8 +413,9 @@ PlaneObject.prototype.updateTick = function(receiver_timestamp, last_timestamp)
PlaneObject.prototype.clearMarker = function() { PlaneObject.prototype.clearMarker = function() {
if (this.marker) { if (this.marker) {
PlaneIconFeatures.remove(this.marker); PlaneIconFeatures.remove(this.marker);
PlaneIconFeatures.remove(this.markerStatic);
/* FIXME google.maps.event.clearListeners(this.marker, 'click'); */ /* FIXME google.maps.event.clearListeners(this.marker, 'click'); */
this.marker = null; this.marker = this.markerStatic = null;
} }
}; };
@ -401,12 +430,18 @@ PlaneObject.prototype.updateMarker = function(moved) {
if (this.marker) { if (this.marker) {
if (moved) { if (moved) {
this.marker.setGeometry(new ol.geom.Point(ol.proj.fromLonLat(this.position))); this.marker.setGeometry(new ol.geom.Point(ol.proj.fromLonLat(this.position)));
this.markerStatic.setGeometry(new ol.geom.Point(ol.proj.fromLonLat(this.position)));
} }
} else { } else {
this.marker = new ol.Feature(new ol.geom.Point(ol.proj.fromLonLat(this.position))); this.marker = new ol.Feature(new ol.geom.Point(ol.proj.fromLonLat(this.position)));
this.marker.hex = this.icao; this.marker.hex = this.icao;
this.marker.setStyle(this.markerStyle); this.marker.setStyle(this.markerStyle);
PlaneIconFeatures.push(this.marker); PlaneIconFeatures.push(this.marker);
this.markerStatic = new ol.Feature(new ol.geom.Point(ol.proj.fromLonLat(this.position)));
this.markerStatic.hex = this.icao;
this.markerStatic.setStyle(this.markerStaticStyle);
PlaneIconFeatures.push(this.markerStatic);
} }
}; };

View file

@ -0,0 +1,315 @@
// Various reverse-engineered versions of the allocation algorithms
// used by different countries to allocate 24-bit ICAO addresses based
// on the aircraft registration.
//
// These were worked out by looking at the allocation patterns and
// working backwards to an algorithm that generates that pattern,
// spot-checking aircraft to see if it worked.
// YMMV.
registration_from_hexid = (function () {
// hide the guts in a closure
var limited_alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ"; // 24 chars; no I, O
var full_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // 26 chars
// handles 3-letter suffixes assigned with a regular pattern
//
// start: first hexid of range
// s1: major stride (interval between different first letters)
// s2: minor stride (interval between different second letters)
// prefix: the registration prefix
//
// optionally:
// alphabet: the alphabet to use (defaults full_alphabet)
// first: the suffix to use at the start of the range (default: AAA)
// last: the last valid suffix in the range (default: ZZZ)
var stride_mappings = [
{ start: 0x008011, s1: 26*26, s2: 26, prefix: "ZS-" },
{ start: 0x390000, s1: 1024, s2: 32, prefix: "F-G" },
{ start: 0x398000, s1: 1024, s2: 32, prefix: "F-H" },
{ start: 0x3C4421, s1: 1024, s2: 32, prefix: "D-A", first: 'AAA', last: 'OZZ' },
{ start: 0x3C0001, s1: 26*26, s2: 26, prefix: "D-A", first: 'PAA', last: 'ZZZ' },
{ start: 0x3C8421, s1: 1024, s2: 32, prefix: "D-B", first: 'AAA', last: 'OZZ' },
{ start: 0x3C2001, s1: 26*26, s2: 26, prefix: "D-B", first: 'PAA', last: 'ZZZ' },
{ start: 0x3CC000, s1: 26*26, s2: 26, prefix: "D-C" },
{ start: 0x3D04A8, s1: 26*26, s2: 26, prefix: "D-E" },
{ start: 0x3D4950, s1: 26*26, s2: 26, prefix: "D-F" },
{ start: 0x3D8DF8, s1: 26*26, s2: 26, prefix: "D-G" },
{ start: 0x3DD2A0, s1: 26*26, s2: 26, prefix: "D-H" },
{ start: 0x3E1748, s1: 26*26, s2: 26, prefix: "D-I" },
{ start: 0x448421, s1: 1024, s2: 32, prefix: "OO-" },
{ start: 0x458421, s1: 1024, s2: 32, prefix: "OY-" },
{ start: 0x460000, s1: 26*26, s2: 26, prefix: "OH-" },
{ start: 0x468421, s1: 1024, s2: 32, prefix: "SX-" },
{ start: 0x490421, s1: 1024, s2: 32, prefix: "CS-" },
{ start: 0x4A0421, s1: 1024, s2: 32, prefix: "YR-" },
{ start: 0x4B8421, s1: 1024, s2: 32, prefix: "TC-" },
{ start: 0x740421, s1: 1024, s2: 32, prefix: "JY-" },
{ start: 0x760421, s1: 1024, s2: 32, prefix: "AP-" },
{ start: 0x768421, s1: 1024, s2: 32, prefix: "9V-" },
{ start: 0x778421, s1: 1024, s2: 32, prefix: "YK-" },
{ start: 0xC00001, s1: 26*26, s2: 26, prefix: "C-F" },
{ start: 0xC044A9, s1: 26*26, s2: 26, prefix: "C-G" },
{ start: 0xE01041, s1: 4096, s2: 64, prefix: "LV-" }
];
// numeric registrations
// start: start hexid in range
// first: first numeric registration
// count: number of numeric registrations
// template: registration template, trailing characters are replaced with the numeric registration
var numeric_mappings = [
{ start: 0x140000, first: 0, count: 100000, template: "RA-00000" },
{ start: 0x0B03E8, first: 1000, count: 1000, template: "CU-T0000" }
];
// fill in some derived data
for (var i = 0; i < stride_mappings.length; ++i) {
var mapping = stride_mappings[i];
if (!mapping.alphabet) {
mapping.alphabet = full_alphabet;
}
if (mapping.first) {
var c1 = mapping.alphabet.indexOf(mapping.first.charAt(0));
var c2 = mapping.alphabet.indexOf(mapping.first.charAt(1));
var c3 = mapping.alphabet.indexOf(mapping.first.charAt(2));
mapping.offset = c1 * mapping.s1 + c2 * mapping.s2 + c3;
} else {
mapping.offset = 0;
}
if (mapping.last) {
var c1 = mapping.alphabet.indexOf(mapping.last.charAt(0));
var c2 = mapping.alphabet.indexOf(mapping.last.charAt(1));
var c3 = mapping.alphabet.indexOf(mapping.last.charAt(2));
mapping.end = mapping.start - mapping.offset +
c1 * mapping.s1 +
c2 * mapping.s2 +
c3 - mapping.offset;
} else {
mapping.end = mapping.start - mapping.offset +
(mapping.alphabet.length - 1) * mapping.s1 +
(mapping.alphabet.length - 1) * mapping.s2 +
(mapping.alphabet.length - 1);
}
}
for (var i = 0; i < numeric_mappings.length; ++i) {
numeric_mappings[i].end = numeric_mappings[i].start + numeric_mappings[i].count - 1;
}
function lookup(hexid) {
var hexid = +("0x" + hexid);
reg = n_reg(hexid);
if (reg)
return reg;
reg = ja_reg(hexid);
if (reg)
return reg;
reg = hl_reg(hexid);
if (reg)
return reg;
reg = numeric_reg(hexid);
if (reg)
return reg;
reg = stride_reg(hexid);
if (reg)
return reg;
return null;
}
function stride_reg(hexid) {
// try the mappings in stride_mappings
var i;
for (i = 0; i < stride_mappings.length; ++i) {
var mapping = stride_mappings[i];
if (hexid < mapping.start || hexid > mapping.end)
continue;
var offset = hexid - mapping.start + mapping.offset;
var i1 = Math.floor(offset / mapping.s1);
offset = offset % mapping.s1;
var i2 = Math.floor(offset / mapping.s2);
offset = offset % mapping.s2;
var i3 = offset;
if (i1 < 0 || i1 >= mapping.alphabet.length ||
i2 < 0 || i2 >= mapping.alphabet.length ||
i3 < 0 || i3 >= mapping.alphabet.length)
continue;
return mapping.prefix + mapping.alphabet.charAt(i1) + mapping.alphabet.charAt(i2) + mapping.alphabet.charAt(i3);
}
// nothing
return null;
}
function numeric_reg(hexid) {
// try the mappings in numeric_mappings
var i;
for (i = 0; i < numeric_mappings.length; ++i) {
var mapping = numeric_mappings[i];
if (hexid < mapping.start || hexid > mapping.end)
continue;
var reg = (hexid - mapping.start + mapping.first) + "";
return mapping.template.substring(0, mapping.template.length - reg.length) + reg;
}
}
//
// US N-numbers
//
function n_letters(rem) {
if (rem == 0)
return "";
--rem;
return limited_alphabet.charAt(Math.floor(rem / 25)) + n_letter(rem % 25);
}
function n_letter(rem) {
if (rem == 0)
return "";
--rem;
return limited_alphabet.charAt(rem);
}
function n_reg(hexid) {
var offset = hexid - 0xA00001;
if (offset < 0 || offset >= 915399) {
return null;
}
var digit1 = Math.floor(offset / 101711) + 1;
var reg = "N" + digit1;
offset = offset % 101711;
if (offset <= 600) {
// Na, NaA .. NaZ, NaAA .. NaZZ
return reg + n_letters(offset);
}
// Na0* .. Na9*
offset -= 601;
var digit2 = Math.floor(offset / 10111);
reg += digit2;
offset = offset % 10111;
if (offset <= 600) {
// Nab, NabA..NabZ, NabAA..NabZZ
return reg + n_letters(offset);
}
// Nab0* .. Nab9*
offset -= 601;
var digit3 = Math.floor(offset / 951);
reg += digit3;
offset = offset % 951;
if (offset <= 600) {
// Nabc, NabcA .. NabcZ, NabcAA .. NabcZZ
return reg + n_letters(offset);
}
// Nabc0* .. Nabc9*
offset -= 601;
var digit4 = Math.floor(offset / 35);
reg += digit4.toFixed(0);
offset = offset % 35;
if (offset <= 24) {
// Nabcd, NabcdA .. NabcdZ
return reg + n_letter(offset);
}
// Nabcd0 .. Nabcd9
offset -= 25;
return reg + offset.toFixed(0);
}
// South Korea
function hl_reg(hexid) {
if (hexid >= 0x71BA00 && hexid <= 0x71bf99) {
return "HL" + (hexid - 0x71BA00 + 0x7200).toString(16);
}
if (hexid >= 0x71C000 && hexid <= 0x71C099) {
return "HL" + (hexid - 0x71C000 + 0x8000).toString(16);
}
if (hexid >= 0x71C200 && hexid <= 0x71C299) {
return "HL" + (hexid - 0x71C200 + 0x8200).toString(16);
}
return null;
}
// Japan
function ja_reg(hexid) {
var offset = hexid - 0x840000;
if (offset < 0 || offset >= 229840)
return null;
var reg = "JA";
var digit1 = Math.floor(offset / 22984);
if (digit1 < 0 || digit1 > 9)
return null;
reg += digit1;
offset = offset % 22984;
var digit2 = Math.floor(offset / 916);
if (digit2 < 0 || digit2 > 9)
return null;
reg += digit2;
offset = offset % 916;
if (offset < 340) {
// 3rd is a digit, 4th is a digit or letter
var digit3 = Math.floor(offset / 34);
reg += digit3;
offset = offset % 34;
if (offset < 10) {
// 4th is a digit
return reg + offset;
}
// 4th is a letter
offset -= 10;
return reg + limited_alphabet.charAt(offset);
}
// 3rd and 4th are letters
offset -= 340;
var letter3 = Math.floor(offset / 24);
return reg + limited_alphabet.charAt(letter3) + limited_alphabet.charAt(offset % 24);
}
return lookup;
})();
// make nodejs happy:
if (typeof module !== 'undefined') {
module.exports = registration_from_hexid;
}

40
tools/README.aircraft-db Normal file
View file

@ -0,0 +1,40 @@
The dump1090 webmap uses a static database of json files to provide aircraft
information.
This directory has some tools to turn a CSV file with aircraft data into
the json format that the dump1090 map expects.
The default data comes from a database kindly provided by VRS (unfortunately
no longer updated). This data is in vrs.csv. It was extracted by:
$ wget http://www.virtualradarserver.co.uk/Files/BasicAircraftLookup.sqb.gz
$ gunzip BasicAircraftLookup.sqb.gz
$ tools/vrs-to-csv.py BasicAircraftLookup.sqb >tools/vrs.csv
You can modify vrs.csv (or build a new CSV entirely) and update the database.
First, as an optional step, you can prune out all registrations which match
what the in-browser hexid-to-registration logic would generate anyway. This
requires nodejs, see the comments in filter-regs.js
$ nodejs tools/filter-regs.js <tools/vrs.csv >tools/vrs-pruned.csv
Next, turn the pruned CSV into a set of json files:
$ tools/csv-to-json.py tools/vrs-pruned.csv public_html/db
The contents of public_html/db should be installed where the webmap can find
them; the Debian packaging puts these in
/usr/share/dump1090-mutability/html/db
The CSV format is very simple. The first line must be a header line that names
the columns. These columns are understood:
icao24: the 6-digit hex address of the aircraft
r: the registration / tail number of the aircraft
t: the ICAO aircraft type of the aircraft, e.g. B773
Any other columns are put into the json DB under the name you give them, but
the standard map code won't do anything special with them. You can pick these
columns up in the PlaneObject constructor (see planeObject.js where it calls
getAircraftData()) for later use.

View file

@ -5,29 +5,41 @@
# into a bunch of json files suitable for use by the webmap # into a bunch of json files suitable for use by the webmap
# #
import sqlite3, json, sys import sqlite3, json, sys, csv
from contextlib import closing from contextlib import closing
def extract(dbfile, todir, blocklimit, debug): def readcsv(name, infile, blocks):
print >>sys.stderr, 'Reading from', name
if len(blocks) == 0:
for i in xrange(16):
blocks['%01X' % i] = {}
ac_count = 0 ac_count = 0
reader = csv.DictReader(infile)
if not 'icao24' in reader.fieldnames:
raise RuntimeError('CSV should have at least an "icao24" column')
for row in reader:
icao24 = row['icao24']
entry = {}
for k,v in row.items():
if k != 'icao24' and v != '':
entry[k] = v
if len(entry) > 0:
ac_count += 1
bkey = icao24[0:1].upper()
dkey = icao24[1:].upper()
blocks[bkey].setdefault(dkey, {}).update(entry)
print >>sys.stderr, 'Read', ac_count, 'aircraft from', name
def writedb(blocks, todir, blocklimit, debug):
block_count = 0 block_count = 0
blocks = {}
for i in xrange(16):
blocks['%01X' % i] = {}
print >>sys.stderr, 'Reading', dbfile
with closing(sqlite3.connect(dbfile)) as db:
with closing(db.execute('SELECT a.Icao, a.Registration, m.Icao FROM Aircraft a, Model m WHERE a.ModelID = m.ModelID')) as c:
for icao24, reg, icaotype in c:
bkey = icao24[0:1].upper()
dkey = icao24[1:].upper()
blocks[bkey][dkey] = {}
if reg: blocks[bkey][dkey]['r'] = reg
if icaotype: blocks[bkey][dkey]['t'] = icaotype
ac_count += 1
print >>sys.stderr, 'Read', ac_count, 'aircraft'
print >>sys.stderr, 'Writing blocks:', print >>sys.stderr, 'Writing blocks:',
queue = sorted(blocks.keys()) queue = sorted(blocks.keys())
@ -83,8 +95,20 @@ def extract(dbfile, todir, blocklimit, debug):
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) < 3: if len(sys.argv) < 3:
print >>sys.stderr, 'Syntax: %s <path to BasicAircraftLookup.sqb> <path to DB dir>' % sys.argv[0] print >>sys.stderr, 'Reads a CSV file with aircraft information and produces a directory of JSON files'
print >>sys.stderr, 'Syntax: %s <path to CSV> [... additional CSV files ...] <path to DB dir>' % sys.argv[0]
print >>sys.stderr, 'Use "-" as the CSV path to read from stdin'
print >>sys.stderr, 'If multiple CSV files are specified and they provide conflicting data'
print >>sys.stderr, 'then the data from the last-listed CSV file is used'
sys.exit(1) sys.exit(1)
else:
extract(sys.argv[1], sys.argv[2], 1000, False) blocks = {}
sys.exit(0) for filename in sys.argv[1:-1]:
if filename == '-':
readcsv('stdin', sys.stdin, blocks)
else:
with closing(open(filename, 'r')) as infile:
readcsv(filename, infile, blocks)
writedb(blocks, sys.argv[-1], 1000, False)
sys.exit(0)

38
tools/filter-regs.js Normal file
View file

@ -0,0 +1,38 @@
//
// This script processes a CSV file that contains
// ICAO addresses (column 'icao24') and registrations
// (column 'r')
//
// It removes all registration entries that exactly match
// what dump1090 would have computed from the hexid anyway,
// reducing the size of the CSV in the cases where the
// two approaches match.
//
// Any additional columns are passed through unchanged.
//
// To run it:
//
// sudo apt-get install nodejs
// sudo apt-get install npm
// npm install csv # must be done in the same dir as this script
// nodejs filter-regs.js <input.csv >output.csv
var reglookup = require('../public_html/registrations.js');
var csv = require('csv');
var parser = csv.parse({columns: true});
var writer = csv.stringify({header: true});
var transformer = csv.transform(function (record, callback) {
if (('icao24' in record) && ('r' in record)) {
var computed = reglookup(record.icao24);
if (computed === record.r) {
record.r = '';
} else if (computed !== null) {
console.warn(record.icao24 + " computed " + computed + " but CSV data had " + record.r);
}
}
callback(null, record);
});
process.stdin.pipe(parser).pipe(transformer).pipe(writer).pipe(process.stdout);

31
tools/vrs-to-csv.py Executable file
View file

@ -0,0 +1,31 @@
#!/usr/bin/env python2
#
# Converts a Virtual Radar Server BasicAircraftLookup.sqb database
# to a CSV file suitable for feeding to csv-to-json.py
#
import sqlite3, csv, sys
from contextlib import closing
def extract(dbfile):
writer = csv.DictWriter(sys.stdout,
fieldnames=['icao24', 'r', 't'])
writer.writeheader()
with closing(sqlite3.connect(dbfile)) as db:
with closing(db.execute('SELECT a.Icao, a.Registration, m.Icao FROM Aircraft a, Model m WHERE a.ModelID = m.ModelID')) as c:
for icao24, reg, icaotype in c:
writer.writerow({
'icao24': icao24,
'r': reg,
't': icaotype
})
if __name__ == '__main__':
if len(sys.argv) < 2:
print >>sys.stderr, 'Reads a VRS sqlite database and writes a CSV to stdout'
print >>sys.stderr, 'Syntax: %s <path to BasicAircraftLookup.sqb>' % sys.argv[0]
sys.exit(1)
else:
extract(sys.argv[1])
sys.exit(0)

BIN
tools/vrs.csv.xz Normal file

Binary file not shown.