diff --git a/public_html/gmap.html b/public_html/gmap.html
index 98cbd4a..ecc7103 100644
--- a/public_html/gmap.html
+++ b/public_html/gmap.html
@@ -16,6 +16,7 @@
+
diff --git a/public_html/planeObject.js b/public_html/planeObject.js
index 92da329..0c07f1d 100644
--- a/public_html/planeObject.js
+++ b/public_html/planeObject.js
@@ -44,9 +44,12 @@ function PlaneObject(icao) {
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;
diff --git a/public_html/registrations.js b/public_html/registrations.js
new file mode 100644
index 0000000..299554e
--- /dev/null
+++ b/public_html/registrations.js
@@ -0,0 +1,310 @@
+// 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: "RA0000" },
+ { start: 0x0B03E8, first: 1000, count: 1000, template: "CUT0000" }
+ ];
+
+ // 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;
+})();