ICAO コードのプレフィクスをおよその位置に関連付けてみる

てーしたはなしではねーんだけれども。

METAR Search (with simple parsing) で、ICAO コードのプレフィクスだけ入力すると地図でジャンプ出来ると便利だなーと思って。

SkyVector からお取り寄せてさらに必要情報だけにそぎ落としたこんな csv:

1 code,name,lon,lat
2 LECO,A Coruna Airport,-8.37733333,43.302
3 KOCH,A L Mangham Jr Regional Airport,-94.71016667,31.57783333
4 ...

を持っていて、また、「プレフィクスと国のマッピングテーブル」として、こんな(スラッシュ区切りの) csv:

 1 Country/ICAO Code Prefixes
 2 AFGHANISTAN/OA
 3 ALGERIA/DA
 4 ...
 5 CHILE/SC,NE
 6 CHINA/RC,VH,ZB,ZG,ZH,ZJ,ZL,ZP,ZS,ZT,ZU,ZW,ZY,VM
 7 COLOMBIA/SK
 8 ...
 9 US VIRGIN ISLANDS/MI,TI
10 USA/K,X,PA,PB,PF,PJ,PL,PM,PO,PP,PH,PW
11 UZBEKHISTAN/UT

を持ってた。今は「国」そのものには興味はなくて、地図でざっくり飛べればいい、ってニーズなので、「プレフィクス→プレフィクスごと位置平均」のマッピングを作りたい、ってわけだ。javascript から使いたいので json が出力な。

aaa.py (このファイル名はウソではなくてほんとに)
 1 # -*- coding: utf-8 -*-
 2 import csv
 3 from math import degrees, radians, cos, sin, atan, atan2
 4 import cmath
 5 import numpy as np
 6 
 7 def mean_of_angle(a):
 8     """
 9     see http://hhsprings.pinoko.jp/site-hhs/2016/07/mean-of-circular-quantities-%E3%81%A7%E6%82%A9%E3%82%80%E3%80%82/
10     """
11     rho = sum(cmath.rect(1, radians(d)) for d in a)
12     if np.isclose(rho, 0):
13         return np.nan  # or None
14     rho /= len(a)
15     th = cmath.phase(rho)
16     return degrees(th)
17 
18 # Country/ICAO Code Prefixes
19 prefixes = {1: set(), 2: set(), 3: set()}
20 for row in csv.reader(open("icao_code_prefixes.txt"), delimiter="/"):
21     if row[0] == "Country":
22         continue
23     spl = row[1].split(",")
24     for p in spl:
25         prefixes[len(p)].add(p)
26 
27 lons = {}
28 lats = {}
29 
30 # code, name, lon, lat
31 for row in csv.reader(open("knownloc_data_raw.csv")):
32     if row[0] == "code":
33         continue
34     code, _, lon, lat = row
35     lon, lat = float(lon), float(lat)
36 
37     for plen, prs in [(plen, prefixes[plen]) for plen in reversed(prefixes.keys())]:
38         p = code[:plen]
39         if p in prs:
40             if p not in lons:
41                 lons[p] = []
42                 lats[p] = []
43             lons[p].append(lon)
44             lats[p].append(lat)
45             break
46 
47 result = {}
48 for p in lons:
49     result[p] = (round(mean_of_angle(lons[p]), 3), round(mean_of_angle(lats[p]), 3))
50 
51 import json
52 # json.dumps の indent を使うと折り返しすぎちゃってかえって読みにくいの…
53 print(json.dumps(result).replace("],", "],\n").replace("{", "{\n ").replace("}", "\n}"))

てわけで、手持ちのデータからはこんなテーブルが出来た:

  1     var prefixlocmap = {
  2      "AG": [160.053, -9.428],
  3      "AY": [147.124, -6.024],
  4      "BG": [-50.753, 67.282],
  5      "BI": [-19.902, 64.809],
  6      "C": [-95.686, 54.286],
  7      "DA": [3.421, 32.443],
  8      "DB": [2.383, 6.356],
  9      "DF": [-2.917, 11.761],
 10      "DG": [-0.168, 5.604],
 11      "DI": [-6.136, 6.691],
 12      "DN": [6.008, 8.002],
 13      "DR": [6.309, 14.521],
 14      "DT": [9.836, 34.8],
 15      "DX": [1.168, 8.313],
 16      "EB": [4.376, 50.838],
 17      "ED": [9.753, 51.003],
 18      "EE": [24.259, 58.77],
 19      "EF": [25.311, 63.25],
 20      "EG": [-2.784, 52.711],
 21      "EH": [5.302, 52.058],
 22      "EI": [-8.057, 53.208],
 23      "EK": [9.139, 56.258],
 24      "EL": [6.205, 49.623],
 25      "EN": [14.798, 65.876],
 26      "EP": [19.175, 51.989],
 27      "ES": [16.25, 60.708],
 28      "ET": [9.495, 50.935],
 29      "EV": [22.204, 56.933],
 30      "EY": [23.465, 55.367],
 31      "FA": [26.299, -28.489],
 32      "FB": [25.963, -21.115],
 33      "FC": [13.569, -4.533],
 34      "FD": [31.512, -26.444],
 35      "FE": [18.52, 4.398],
 36      "FG": [9.256, 2.829],
 37      "FH": [-14.394, -7.97],
 38      "FI": [60.522, -20.094],
 39      "FJ": [72.412, -7.313],
 40      "FK": [12.051, 6.106],
 41      "FL": [27.646, -15.381],
 42      "FMC": [44.276, -12.172],
 43      "FME": [55.471, -21.106],
 44      "FMM": [48.435, -18.456],
 45      "FMN": [47.33, -14.492],
 46      "FMS": [46.956, -25.038],
 47      "FN": [15.087, -10.747],
 48      "FO": [11.369, -0.365],
 49      "FP": [6.713, 0.378],
 50      "FQ": [35.766, -18.227],
 51      "FS": [55.607, -4.497],
 52      "FT": [17.58, 10.936],
 53      "FV": [29.128, -18.896],
 54      "FW": [34.373, -14.732],
 55      "FX": [27.558, -29.444],
 56      "FY": [17.949, -21.952],
 57      "GA": [-7.763, 14.771],
 58      "GB": [-16.658, 13.342],
 59      "GC": [-16.078, 28.291],
 60      "GE": [-2.956, 35.28],
 61      "GF": [-13.195, 8.617],
 62      "GG": [-15.656, 11.889],
 63      "GM": [-7.36, 32.107],
 64      "GO": [-15.214, 14.136],
 65      "GQ": [-13.198, 19.655],
 66      "GU": [-13.612, 9.577],
 67      "GV": [-23.594, 16.162],
 68      "HA": [38.799, 8.975],
 69      "HD": [43.16, 11.547],
 70      "HE": [32.208, 28.435],
 71      "HK": [37.235, -1.595],
 72      "HL": [13.278, 32.895],
 73      "HR": [30.138, -1.968],
 74      "HS": [31.244, 15.057],
 75      "HT": [35.314, -5.649],
 76      "HU": [32.441, 0.041],
 77      "K": [-93.174, 38.189],
 78      "LA": [19.721, 41.415],
 79      "LB": [25.862, 42.743],
 80      "LC": [33.151, 34.834],
 81      "LD": [16.04, 44.375],
 82      "LE": [-2.577, 40.37],
 83      "LF": [2.368, 46.471],
 84      "LG": [23.915, 38.156],
 85      "LH": [19.282, 46.957],
 86      "LI": [12.301, 42.476],
 87      "LJ": [14.822, 46.019],
 88      "LK": [15.353, 49.756],
 89      "LL": [35.048, 31.895],
 90      "LM": [14.477, 35.858],
 91      "LO": [14.229, 47.587],
 92      "LP": [-17.828, 37.588],
 93      "LQ": [18.05, 44.127],
 94      "LR": [25.048, 45.977],
 95      "LS": [8.099, 46.83],
 96      "LT": [34.86, 38.985],
 97      "LU": [28.308, 47.543],
 98      "LW": [21.182, 41.571],
 99      "LX": [-5.35, 36.151],
100      "LY": [20.246, 43.841],
101      "LZ": [19.045, 48.734],
102      "MB": [-71.704, 21.609],
103      "MD": [-69.822, 18.879],
104      "MG": [-90.344, 15.086],
105      "MH": [-86.917, 15.188],
106      "MK": [-77.35, 18.22],
107      "MM": [-100.935, 22.452],
108      "MN": [-84.444, 12.726],
109      "MP": [-80.559, 8.845],
110      "MR": [-84.229, 10.125],
111      "MS": [-89.091, 13.566],
112      "MT": [-72.244, 19.156],
113      "MU": [-78.928, 21.683],
114      "MW": [-80.62, 19.49],
115      "MY": [-78.081, 25.799],
116      "MZ": [-88.308, 17.539],
117      "NC": [-160.399, -15.788],
118      "NF": [178.002, -17.901],
119      "NFT": [-174.483, -19.868],
120      "NG": [173.146, 1.381],
121      "NGF": [179.196, -8.525],
122      "NI": [-169.927, -19.078],
123      "NS": [-171.354, -14.081],
124      "NT": [-149.611, -17.557],
125      "NW": [166.216, -22.016],
126      "NZ": [174.044, -40.609],
127      "OA": [66.217, 35.161],
128      "OB": [50.634, 26.271],
129      "OE": [42.615, 24.589],
130      "OI": [52.257, 32.583],
131      "OJ": [35.668, 31.102],
132      "OK": [47.98, 29.227],
133      "OL": [35.49, 33.819],
134      "OM": [55.38, 24.919],
135      "OO": [56.187, 20.316],
136      "OP": [71.143, 30.091],
137      "OR": [44.987, 33.59],
138      "OS": [37.888, 35.284],
139      "OT": [51.496, 25.218],
140      "OY": [48.433, 14.657],
141      "PA": [-154.929, 61.825],
142      "PF": [-159.165, 63.255],
143      "PG": [145.264, 14.272],
144      "PH": [-157.286, 20.967],
145      "PK": [169.502, 7.893],
146      "PL": [-157.35, 1.986],
147      "PM": [-177.381, 28.201],
148      "PP": [-162.723, 65.319],
149      "PT": [149.138, 7.334],
150      "PW": [166.637, 19.282],
151      "RC": [120.845, 23.783],
152      "RJ": [137.033, 36.549],
153      "RK": [127.323, 36.303],
154      "RO": [126.309, 25.472],
155      "RP": [121.791, 11.788],
156      "SA": [-63.074, -35.45],
157      "SB": [-48.69, -15.524],
158      "SC": [-72.462, -38.846],
159      "SE": [-79.902, -1.229],
160      "SF": [-57.767, -51.683],
161      "SG": [-56.065, -25.973],
162      "SK": [-74.934, 6.521],
163      "SL": [-64.292, -16.812],
164      "SM": [-55.197, 5.631],
165      "SO": [-52.362, 4.82],
166      "SP": [-75.116, -10.561],
167      "SU": [-56.606, -33.954],
168      "SV": [-68.369, 9.569],
169      "SY": [-58.181, 6.652],
170      "TA": [-61.793, 17.137],
171      "TB": [-59.492, 13.075],
172      "TD": [-61.347, 15.442],
173      "TF": [-61.261, 15.428],
174      "TG": [-61.786, 12.004],
175      "TI": [-64.888, 18.019],
176      "TJ": [-66.448, 18.35],
177      "TK": [-62.654, 17.258],
178      "TL": [-60.972, 13.876],
179      "TN": [-66.666, 14.47],
180      "TQ": [-63.054, 18.205],
181      "TR": [-62.193, 16.791],
182      "TT": [-61.085, 10.873],
183      "TU": [-64.542, 18.445],
184      "TV": [-61.278, 12.923],
185      "TX": [-64.679, 32.364],
186      "UA": [68.084, 48.656],
187      "UB": [47.505, 40.254],
188      "UC": [74.657, 42.085],
189      "UD": [44.128, 40.449],
190      "UE": [129.773, 62.093],
191      "UG": [43.012, 41.819],
192      "UH": [151.147, 53.924],
193      "UI": [106.707, 53.118],
194      "UK": [29.888, 48.821],
195      "UL": [34.049, 62.019],
196      "UM": [26.863, 53.727],
197      "UN": [84.956, 54.99],
198      "UR": [43.057, 44.957],
199      "US": [65.165, 57.981],
200      "UT": [65.443, 39.711],
201      "UU": [38.485, 54.934],
202      "UW": [50.742, 54.039],
203      "VA": [74.671, 21.868],
204      "VC": [80.505, 6.732],
205      "VD": [104.328, 12.479],
206      "VE": [87.99, 24.334],
207      "VG": [91.11, 23.05],
208      "VH": [113.915, 22.309],
209      "VI": [76.946, 29.371],
210      "VL": [103.347, 18.127],
211      "VM": [113.591, 22.149],
212      "VN": [85.358, 27.697],
213      "VO": [78.677, 13.523],
214      "VQ": [89.425, 27.404],
215      "VR": [73.529, 4.192],
216      "VT": [100.82, 13.585],
217      "VV": [106.756, 13.817],
218      "VY": [96.055, 19.304],
219      "WA": [120.171, -4.255],
220      "WB": [114.857, 4.453],
221      "WI": [103.283, -1.551],
222      "WM": [101.777, 4.423],
223      "WS": [103.92, 1.377],
224      "Y": [139.526, -27.094],
225      "ZB": [114.619, 39.215],
226      "ZG": [112.51, 24.266],
227      "ZH": [114.025, 32.651],
228      "ZJ": [109.936, 19.119],
229      "ZL": [106.185, 35.48],
230      "ZM": [106.767, 47.843],
231      "ZP": [102.943, 25.104],
232      "ZS": [119.892, 30.319],
233      "ZU": [105.796, 28.946],
234      "ZW": [81.748, 41.727],
235      "ZY": [124.241, 42.558]
236     };

むろんこれを「絶対的な正」として使うのはやめてください。これはあくまでも「ワタシに既知な情報だけを使って平均を取ったもの」に過ぎませんし、また、狭い国ならともかくアメリカ(「K」)なんかはこんなん全然役にも立たない。せめて州のレベルまで絞り込めないとちっとも嬉しかないわけね。

まぁともあれ METAR Search (with simple parsing) が少しは使いやすくなったかなと思う。