Merge remote-tracking branch 'upstream/master' into dev

This commit is contained in:
Oliver Jowett 2016-09-10 21:40:13 +01:00
commit ac2b977168
51 changed files with 877 additions and 162 deletions

1
.gitattributes vendored Normal file
View file

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

View file

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

View file

@ -58,6 +58,7 @@ static inline int slice_phase4(uint16_t *m) {
//
void demodulate2400(struct mag_buf *mag)
{
static struct modesMessage zeroMessage;
struct modesMessage mm;
unsigned char msg1[MODES_LONG_MSG_BYTES], msg2[MODES_LONG_MSG_BYTES], *msg;
uint32_t j;
@ -70,7 +71,6 @@ void demodulate2400(struct mag_buf *mag)
uint64_t sum_scaled_signal_power = 0;
memset(&mm, 0, sizeof(mm));
msg = msg1;
for (j = 0; j < mlen; j++) {
@ -297,6 +297,7 @@ void demodulate2400(struct mag_buf *mag)
msglen = modesMessageLenByType(bestmsg[0] >> 3);
// Set initial mm structure details
mm = zeroMessage;
mm.timestampMsg = mag->sampleTimestamp + (j*5) + bestphase;
// compute message receive time as block-start-time + difference in the 12MHz clock

View file

@ -711,7 +711,7 @@ void showHelp(void) {
"--stats-every <seconds> Show and reset stats every <seconds> seconds\n"
"--onlyaddr Show only ICAO addresses (testing purposes)\n"
"--metric Use metric units (meters, km/h, ...)\n"
"--hae Show altitudes as HAE (with H suffix) when available\n"
"--gnss Show altitudes as HAE/GNSS (with H suffix) when available\n"
"--snip <level> Strip IQ file removing samples < level\n"
"--debug <flags> Debug mode (verbose), see README for details\n"
"--quiet Disable output to stdout. Use for daemon applications\n"
@ -1018,7 +1018,7 @@ int main(int argc, char **argv) {
Modes.onlyaddr = 1;
} else if (!strcmp(argv[j],"--metric")) {
Modes.metric = 1;
} else if (!strcmp(argv[j],"--hae")) {
} else if (!strcmp(argv[j],"--hae") || !strcmp(argv[j],"--gnss")) {
Modes.use_hae = 1;
} else if (!strcmp(argv[j],"--aggressive")) {
#ifdef ALLOW_AGGRESSIVE

View file

@ -167,6 +167,8 @@ typedef struct rtlsdr_dev rtlsdr_dev_t;
#define MODES_ACFLAGS_LLBOTH_VALID (MODES_ACFLAGS_LLEVEN_VALID | MODES_ACFLAGS_LLODD_VALID)
#define MODES_ACFLAGS_AOG_GROUND (MODES_ACFLAGS_AOG_VALID | MODES_ACFLAGS_AOG)
#define INVALID_ALTITUDE (-9999)
#define MODES_NON_ICAO_ADDRESS (1<<24) // Set on addresses to indicate they are not ICAO addresses
#define MODES_DEBUG_DEMOD (1<<0)

View file

@ -39,9 +39,10 @@ int ModeAToModeC(unsigned int ModeA)
unsigned int FiveHundreds = 0;
unsigned int OneHundreds = 0;
if ( (ModeA & 0xFFFF8889) // check zero bits are zero, D1 set is illegal
|| ((ModeA & 0x000000F0) == 0) ) // C1,,C4 cannot be Zero
{return -9999;}
if ((ModeA & 0xFFFF8889) != 0 || // check zero bits are zero, D1 set is illegal
(ModeA & 0x000000F0) == 0) { // C1,,C4 cannot be Zero
return INVALID_ALTITUDE;
}
if (ModeA & 0x0010) {OneHundreds ^= 0x007;} // C1
if (ModeA & 0x0020) {OneHundreds ^= 0x003;} // C2
@ -51,8 +52,9 @@ int ModeAToModeC(unsigned int ModeA)
if ((OneHundreds & 5) == 5) {OneHundreds ^= 2;}
// Check for invalid codes, only 1 to 5 are valid
if (OneHundreds > 5)
{return -9999;}
if (OneHundreds > 5) {
return INVALID_ALTITUDE;
}
//if (ModeA & 0x0001) {FiveHundreds ^= 0x1FF;} // D1 never used for altitude
if (ModeA & 0x0002) {FiveHundreds ^= 0x0FF;} // D2

View file

@ -111,8 +111,6 @@ static int decodeID13Field(int ID13Field) {
return (hexGillham);
}
#define INVALID_ALTITUDE (-9999)
//
//=========================================================================
//
@ -355,6 +353,13 @@ int scoreModesMessage(unsigned char *msg, int validbits)
case 5: // surveillance, altitude reply
case 16: // long air-air surveillance
case 24: // Comm-D (ELM)
case 25: // Comm-D (ELM)
case 26: // Comm-D (ELM)
case 27: // Comm-D (ELM)
case 28: // Comm-D (ELM)
case 29: // Comm-D (ELM)
case 30: // Comm-D (ELM)
case 31: // Comm-D (ELM)
return icaoFilterTest(crc) ? 1000 : -1;
case 11: // All-call reply
@ -462,6 +467,13 @@ int decodeModesMessage(struct modesMessage *mm, unsigned char *msg)
case 5: // surveillance, altitude reply
case 16: // long air-air surveillance
case 24: // Comm-D (ELM)
case 25: // Comm-D (ELM)
case 26: // Comm-D (ELM)
case 27: // Comm-D (ELM)
case 28: // Comm-D (ELM)
case 29: // Comm-D (ELM)
case 30: // Comm-D (ELM)
case 31: // Comm-D (ELM)
// These message types use Address/Parity, i.e. our CRC syndrome is the sender's ICAO address.
// We can't tell if the CRC is correct or not as we don't know the correct address.
// Accept the message if it appears to be from a previously-seen aircraft

View file

@ -716,6 +716,7 @@ static int decodeBinMessage(struct client *c, char *p) {
int j;
char ch;
unsigned char msg[MODES_LONG_MSG_BYTES];
static struct modesMessage zeroMessage;
struct modesMessage mm;
MODES_NOTUSED(c);
memset(&mm, 0, sizeof(mm));
@ -732,6 +733,8 @@ static int decodeBinMessage(struct client *c, char *p) {
}
if (msgLen) {
mm = zeroMessage;
// Mark messages received over the internet as remote so that we don't try to
// pass them off as being received by this instance when forwarding them
mm.remote = 1;
@ -810,8 +813,10 @@ static int decodeHexMessage(struct client *c, char *hex) {
int l = strlen(hex), j;
unsigned char msg[MODES_LONG_MSG_BYTES];
struct modesMessage mm;
static struct modesMessage zeroMessage;
MODES_NOTUSED(c);
memset(&mm, 0, sizeof(mm));
mm = zeroMessage;
// Mark messages received over the internet as remote so that we don't try to
// pass them off as being received by this instance when forwarding them

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="markers.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="formatter.js"></script>
<script type="text/javascript" src="flags.js"></script>

View file

@ -66,6 +66,27 @@ function createBaseLayers() {
}
}
var nexrad = new ol.layer.Tile({
name: 'nexrad',
title: 'NEXRAD',
type: 'overlay',
opacity: 0.5,
visible: false
});
us.push(nexrad);
var refreshNexrad = function() {
// re-build the source to force a refresh of the nexrad tiles
var now = new Date().getTime();
nexrad.setSource(new ol.source.XYZ({
url : 'http://mesonet{1-3}.agron.iastate.edu/cache/tile.py/1.0.0/nexrad-n0q-900913/{z}/{x}/{y}.png?_=' + now,
attributions: 'NEXRAD courtesy of <a href="http://mesonet.agron.iastate.edu/">IEM</a>'
}));
};
refreshNexrad();
window.setInterval(refreshNexrad, 5 * 60000);
if (world.length > 0) {
layers.push(new ol.layer.Group({
name: 'world',

View file

@ -58,43 +58,194 @@ var _beechcraft_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";
var MarkerIcons = {
generic : {
scale : 0.4,
// From https://discussions.flightaware.com/ads-b-flight-tracking-f21/some-custom-svg-plane-icons-t37783.html
// by Peter Lowden
// licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
// NB: scaled so that 1 pixel is about 1.33m (0.75px = 1m)
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 : _generic_plane_svg
},
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"
};
light : {
scale : 0.4,
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],
anchor : [32, 25],
path : _beechcraft_svg
},
medium : {
scale : 0.4,
"A2" : {
key : "A2",
scale : 0.35,
size : [64, 64],
anchor : [32, 32],
path : _generic_plane_svg
},
heavy : {
scale : 0.6,
"A3" : {
key : "A3",
scale : 0.40,
size : [64, 64],
anchor : [32, 32],
path : _generic_plane_svg
},
"A5" : {
key : "A5",
scale : 0.73,
size : [64, 64],
anchor : [32, 32],
path : _heavy_svg
},
rotorcraft : {
scale : 0.5,
"A7" : {
key : "A7",
scale : 0.50,
size : [64, 64],
anchor : [22, 32],
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) {
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 + '"';

View file

@ -39,12 +39,17 @@ function PlaneObject(icao) {
this.marker = null;
this.markerStyle = null;
this.markerIcon = null;
this.markerStaticStyle = null;
this.markerStaticIcon = null;
this.markerStyleKey = null;
this.markerSvgKey = null;
// request metadata
this.registration = null;
// start from a computed registration, let the DB override it
// if it has something else.
this.registration = registration_from_hexid(this.icao);
this.icaotype = null;
// request metadata
getAircraftData(this.icao).done(function(data) {
if ("r" in data) {
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() {
// Emergency squawks override everything else
if (this.squawk in SpecialSquawks)
@ -269,35 +258,71 @@ PlaneObject.prototype.updateIcon = function() {
var col = this.getMarkerColor();
var opacity = (this.position_from_mlat ? 0.75 : 1.0);
var outline = (this.position_from_mlat ? OutlineMlatColor : OutlineADSBColor);
var type = this.getMarkerIconType();
var weight = ((this.selected ? 2 : 1) / MarkerIcons[type].scale).toFixed(1);
var baseMarker = getBaseMarker(this.category, this.icaotype);
var weight = ((this.selected ? 2 : 1) / baseMarker.scale).toFixed(1);
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;
if (this.markerStyle === null || this.markerIcon === null || this.markerSvgKey != svgKey) {
//console.log(this.icao + " new icon and style " + this.markerSvgKey + " -> " + svgKey);
this.markerIcon = new ol.style.Icon({
anchor: MarkerIcons[type].anchor,
var icon = new ol.style.Icon({
anchor: baseMarker.anchor,
anchorXUnits: 'pixels',
anchorYUnits: 'pixels',
scale: MarkerIcons[type].scale,
imgSize: MarkerIcons[type].size,
src: svgPathToURI(MarkerIcons[type].path, MarkerIcons[type].size, outline, weight, col),
rotation: rotation * Math.PI / 180.0,
opacity: opacity
scale: baseMarker.scale,
imgSize: baseMarker.size,
src: svgPathToURI(baseMarker.path, baseMarker.size, outline, weight, col),
rotation: (baseMarker.noRotate ? 0 : rotation * Math.PI / 180.0),
opacity: opacity,
rotateWithView: (baseMarker.noRotate ? false : true)
});
this.markerStyleKey = styleKey;
this.markerSvgKey = svgKey;
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.markerSvgKey = svgKey;
if (this.marker !== null) {
this.marker.setStyle(this.markerStyle);
this.markerStatic.setStyle(this.markerStaticStyle);
}
}
@ -305,6 +330,9 @@ PlaneObject.prototype.updateIcon = function() {
//console.log(this.icao + " new rotation");
this.markerIcon.setRotation(rotation * Math.PI / 180.0);
this.markerIcon.setOpacity(opacity);
if (this.staticIcon) {
this.staticIcon.setOpacity(opacity);
}
this.markerStyleKey = styleKey;
}
@ -385,8 +413,9 @@ PlaneObject.prototype.updateTick = function(receiver_timestamp, last_timestamp)
PlaneObject.prototype.clearMarker = function() {
if (this.marker) {
PlaneIconFeatures.remove(this.marker);
PlaneIconFeatures.remove(this.markerStatic);
/* 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 (moved) {
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 {
this.marker = new ol.Feature(new ol.geom.Point(ol.proj.fromLonLat(this.position)));
this.marker.hex = this.icao;
this.marker.setStyle(this.markerStyle);
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;
}

View file

@ -369,12 +369,48 @@ function initialize_map() {
// Initialize OL3
var layers = createBaseLayers();
var iconsLayer = new ol.layer.Vector({
name: 'ac_positions',
type: 'overlay',
title: 'Aircraft positions',
source: new ol.source.Vector({
features: PlaneIconFeatures,
})
});
layers.push(new ol.layer.Group({
title: 'Overlays',
layers: [
new ol.layer.Vector({
name: 'site_pos',
type: 'overlay',
title: 'Site position and range rings',
source: new ol.source.Vector({
features: StaticFeatures,
})
}),
new ol.layer.Vector({
name: 'ac_trail',
type: 'overlay',
title: 'Selected aircraft trail',
source: new ol.source.Vector({
features: PlaneTrailFeatures,
})
}),
iconsLayer
]
}));
var foundType = false;
ol.control.LayerSwitcher.forEachRecursive(layers, function(lyr) {
if (lyr.get('type') !== 'base')
if (!lyr.get('name'))
return;
if (lyr.get('type') === 'base') {
if (MapType === lyr.get('name')) {
foundType = true;
lyr.setVisible(true);
@ -387,6 +423,17 @@ function initialize_map() {
MapType = localStorage['MapType'] = evt.target.get('name');
}
});
} else if (lyr.get('type') === 'overlay') {
var visible = localStorage['layer_' + lyr.get('name')];
if (visible != undefined) {
// javascript, why must you taunt me with gratuitous type problems
lyr.setVisible(visible === "true");
}
lyr.on('change:visible', function(evt) {
localStorage['layer_' + evt.target.get('name')] = evt.target.getVisible();
});
}
})
if (!foundType) {
@ -400,34 +447,6 @@ function initialize_map() {
});
}
var iconsLayer = new ol.layer.Vector({
title: 'Aircraft positions',
source: new ol.source.Vector({
features: PlaneIconFeatures,
})
});
layers.push(new ol.layer.Group({
title: 'Overlays',
layers: [
new ol.layer.Vector({
title: 'Site position and range rings',
source: new ol.source.Vector({
features: StaticFeatures,
})
}),
new ol.layer.Vector({
title: 'Selected aircraft trail',
source: new ol.source.Vector({
features: PlaneTrailFeatures,
})
}),
iconsLayer
]
}));
OLMap = new ol.Map({
target: 'map_canvas',
layers: layers,

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,28 +5,40 @@
# 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
def extract(dbfile, todir, blocklimit, debug):
ac_count = 0
block_count = 0
def readcsv(name, infile, blocks):
print >>sys.stderr, 'Reading from', name
blocks = {}
if len(blocks) == 0:
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:
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][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'
blocks[bkey].setdefault(dkey, {}).update(entry)
print >>sys.stderr, 'Read', ac_count, 'aircraft from', name
def writedb(blocks, todir, blocklimit, debug):
block_count = 0
print >>sys.stderr, 'Writing blocks:',
@ -83,8 +95,20 @@ def extract(dbfile, todir, blocklimit, debug):
if __name__ == '__main__':
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)
blocks = {}
for filename in sys.argv[1:-1]:
if filename == '-':
readcsv('stdin', sys.stdin, blocks)
else:
extract(sys.argv[1], sys.argv[2], 1000, False)
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.

22
track.c
View file

@ -112,16 +112,24 @@ struct aircraft *trackFindAircraft(uint32_t addr) {
// (but we don't use it in situations where that matters)
static double greatcircle(double lat0, double lon0, double lat1, double lon1)
{
double dlat, dlon;
lat0 = lat0 * M_PI / 180.0;
lon0 = lon0 * M_PI / 180.0;
lat1 = lat1 * M_PI / 180.0;
lon1 = lon1 * M_PI / 180.0;
// avoid NaN
if (fabs(lat0 - lat1) < 0.0001 && fabs(lon0 - lon1) < 0.0001)
return 0.0;
dlat = fabs(lat1 - lat0);
dlon = fabs(lon1 - lon0);
return 6371e3 * acos(sin(lat0) * sin(lat1) + cos(lat0) * cos(lat1) * cos(fabs(lon0 - lon1)));
// use haversine for small distances for better numerical stability
if (dlat < 0.001 && dlon < 0.001) {
double a = sin(dlat/2) * sin(dlat/2) + cos(lat0) * cos(lat1) * sin(dlon/2) * sin(dlon/2);
return 6371e3 * 2 * atan2(sqrt(a), sqrt(1.0 - a));
}
// spherical law of cosines
return 6371e3 * acos(sin(lat0) * sin(lat1) + cos(lat0) * cos(lat1) * cos(dlon));
}
static void update_range_histogram(double lat, double lon)
@ -293,6 +301,8 @@ static int doLocalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now,
if (a->pos_nuc < *nuc)
*nuc = a->pos_nuc;
range_limit = 50e3;
} else if (!surface && (Modes.bUserFlags & MODES_USER_LATLON_VALID)) {
reflat = Modes.fUserLat;
reflon = Modes.fUserLon;
@ -306,7 +316,9 @@ static int doLocalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now,
// at 200NM distance, this may resolve to a position
// at (200-360) = 160NM in the wrong direction)
if (Modes.maxRange <= 1852*180) {
if (Modes.maxRange == 0) {
return (-1); // Can't do receiver-centered checks at all
} else if (Modes.maxRange <= 1852*180) {
range_limit = Modes.maxRange;
} else if (Modes.maxRange < 1852*360) {
range_limit = (1852*360) - Modes.maxRange;

View file

@ -69,6 +69,7 @@ void view1090InitConfig(void) {
Modes.interactive_rows = getTermRows();
Modes.interactive_display_ttl = MODES_INTERACTIVE_DISPLAY_TTL;
Modes.interactive = 1;
Modes.maxRange = 1852 * 300; // 300NM default max range
}
//
//=========================================================================
@ -130,6 +131,7 @@ void showHelp(void) {
"--net-bo-port <port> TCP Beast output listen port (default: 30005)\n"
"--lat <latitude> Reference/receiver latitide for surface posn (opt)\n"
"--lon <longitude> Reference/receiver longitude for surface posn (opt)\n"
"--max-range <distance> Absolute maximum range for position decoding (in nm, default: 300)\n"
"--no-crc-check Disable messages with broken CRC (discouraged)\n"
"--no-fix Disable single-bits error correction using CRC\n"
"--fix Enable single-bits error correction using CRC\n"
@ -192,6 +194,8 @@ int main(int argc, char **argv) {
Modes.nfix_crc = 0;
} else if (!strcmp(argv[j],"--aggressive")) {
Modes.nfix_crc = MODES_MAX_BITERRORS;
} else if (!strcmp(argv[j],"--max-range") && more) {
Modes.maxRange = atof(argv[++j]) * 1852.0; // convert to metres
} else if (!strcmp(argv[j],"--help")) {
showHelp();
exit(0);