Merge remote-tracking branch 'upstream/master' into dev
This commit is contained in:
commit
ac2b977168
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*.xz binary
|
1
Makefile
1
Makefile
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
12
mode_ac.c
12
mode_ac.c
|
@ -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
|
||||
|
|
16
mode_s.c
16
mode_s.c
|
@ -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
|
||||
|
|
7
net_io.c
7
net_io.c
|
@ -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
|
@ -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>
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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 + '"';
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
315
public_html/registrations.js
Normal file
315
public_html/registrations.js
Normal 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;
|
||||
}
|
|
@ -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
40
tools/README.aircraft-db
Normal 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.
|
|
@ -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
38
tools/filter-regs.js
Normal 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
31
tools/vrs-to-csv.py
Executable 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
BIN
tools/vrs.csv.xz
Normal file
Binary file not shown.
22
track.c
22
track.c
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue