Merge pull request #76 from adust09/feat/east-asia-enhancement

feat: East Asia intelligence coverage enhancement
This commit is contained in:
Shadowbroker
2026-03-15 23:46:30 -06:00
committed by GitHub
14 changed files with 1467 additions and 11 deletions
+30
View File
@@ -64,6 +64,36 @@
"name": "Stars and Stripes",
"url": "https://www.stripes.com/feeds/pacific.rss",
"weight": 4
},
{
"name": "Yonhap",
"url": "https://en.yna.co.kr/RSS/news.xml",
"weight": 4
},
{
"name": "Nikkei Asia",
"url": "https://asia.nikkei.com/rss",
"weight": 3
},
{
"name": "Taipei Times",
"url": "https://www.taipeitimes.com/xml/pda.rss",
"weight": 4
},
{
"name": "Asia Times",
"url": "https://asiatimes.com/feed/",
"weight": 3
},
{
"name": "Defense News",
"url": "https://www.defensenews.com/arc/outboundfeeds/rss/",
"weight": 3
},
{
"name": "Japan Times",
"url": "https://www.japantimes.co.jp/feed/",
"weight": 3
}
]
}
+582
View File
@@ -142,5 +142,587 @@
"branch": "navy",
"lat": -7.316,
"lng": 72.411
},
{
"name": "Fuzhou Changle Air Base",
"country": "China",
"operator": "PLAAF",
"branch": "air_force",
"lat": 25.935,
"lng": 119.663
},
{
"name": "Longtian Air Base",
"country": "China",
"operator": "PLAAF",
"branch": "air_force",
"lat": 25.674,
"lng": 119.507
},
{
"name": "Huian Air Base",
"country": "China",
"operator": "PLAAF",
"branch": "air_force",
"lat": 25.028,
"lng": 118.802
},
{
"name": "Zhangzhou Air Base",
"country": "China",
"operator": "PLAAF",
"branch": "air_force",
"lat": 24.575,
"lng": 117.588
},
{
"name": "Suixi Air Base",
"country": "China",
"operator": "PLAAF",
"branch": "air_force",
"lat": 21.374,
"lng": 110.228
},
{
"name": "Nanning Wuxu Air Base",
"country": "China",
"operator": "PLAAF",
"branch": "air_force",
"lat": 22.609,
"lng": 108.171
},
{
"name": "Jinan Yaoqiang Air Base",
"country": "China",
"operator": "PLAAF",
"branch": "air_force",
"lat": 36.857,
"lng": 117.216
},
{
"name": "Wuhan Wangjiadun Air Base",
"country": "China",
"operator": "PLAAF",
"branch": "air_force",
"lat": 30.500,
"lng": 114.211
},
{
"name": "Changsha Datuopu Air Base",
"country": "China",
"operator": "PLAAF",
"branch": "air_force",
"lat": 28.068,
"lng": 112.866
},
{
"name": "Dingxin Air Base",
"country": "China",
"operator": "PLAAF",
"branch": "air_force",
"lat": 40.296,
"lng": 99.750
},
{
"name": "Hotan Air Base",
"country": "China",
"operator": "PLAAF",
"branch": "air_force",
"lat": 37.039,
"lng": 79.866
},
{
"name": "Lingshui Air Base",
"country": "China",
"operator": "PLAAF / PLAN",
"branch": "air_force",
"lat": 18.507,
"lng": 110.034
},
{
"name": "Sanya Phoenix Air Base",
"country": "China",
"operator": "PLAN Aviation",
"branch": "air_force",
"lat": 18.303,
"lng": 109.412
},
{
"name": "Zhanjiang Naval Base",
"country": "China",
"operator": "PLAN Southern Theater",
"branch": "navy",
"lat": 21.190,
"lng": 110.405
},
{
"name": "Qingdao / Jiaozhou Naval Base",
"country": "China",
"operator": "PLAN Northern Theater",
"branch": "navy",
"lat": 36.100,
"lng": 120.272
},
{
"name": "Ningbo / Zhoushan Naval Base",
"country": "China",
"operator": "PLAN Eastern Theater",
"branch": "navy",
"lat": 29.949,
"lng": 122.094
},
{
"name": "Yulin Naval Base",
"country": "China",
"operator": "PLAN (SSBN)",
"branch": "navy",
"lat": 18.226,
"lng": 109.557
},
{
"name": "Shanghai Wusong Naval Base",
"country": "China",
"operator": "PLAN Eastern Theater",
"branch": "navy",
"lat": 31.398,
"lng": 121.503
},
{
"name": "Dalian Naval Shipyard",
"country": "China",
"operator": "PLAN (carrier construction)",
"branch": "navy",
"lat": 38.936,
"lng": 121.625
},
{
"name": "Jiangnan Shipyard (Changxing Island)",
"country": "China",
"operator": "PLAN (carrier/destroyer construction)",
"branch": "navy",
"lat": 31.358,
"lng": 121.746
},
{
"name": "Xiangshan Naval Base",
"country": "China",
"operator": "PLAN Eastern Theater",
"branch": "navy",
"lat": 29.480,
"lng": 121.933
},
{
"name": "Woody Island (Yongxing)",
"country": "China",
"operator": "PLA Sansha Garrison",
"branch": "navy",
"lat": 16.833,
"lng": 112.333
},
{
"name": "Fiery Cross Reef",
"country": "China",
"operator": "PLA (SCS outpost)",
"branch": "air_force",
"lat": 9.550,
"lng": 112.892
},
{
"name": "Subi Reef",
"country": "China",
"operator": "PLA (SCS outpost)",
"branch": "air_force",
"lat": 10.923,
"lng": 114.083
},
{
"name": "Mischief Reef",
"country": "China",
"operator": "PLA (SCS outpost)",
"branch": "navy",
"lat": 9.904,
"lng": 115.536
},
{
"name": "Dehua PLARF Base (Base 613)",
"country": "China",
"operator": "PLARF 61 Base",
"branch": "missile",
"lat": 25.494,
"lng": 118.241
},
{
"name": "Huangshan PLARF Base (Base 612)",
"country": "China",
"operator": "PLARF 61 Base",
"branch": "missile",
"lat": 29.714,
"lng": 118.337
},
{
"name": "Jiande PLARF Base",
"country": "China",
"operator": "PLARF 61 Base",
"branch": "missile",
"lat": 29.480,
"lng": 119.281
},
{
"name": "Ganzhou PLARF Base",
"country": "China",
"operator": "PLARF 61 Base",
"branch": "missile",
"lat": 25.831,
"lng": 114.935
},
{
"name": "Meizhou PLARF Base",
"country": "China",
"operator": "PLARF 61 Base",
"branch": "missile",
"lat": 24.288,
"lng": 116.122
},
{
"name": "Leping PLARF Base",
"country": "China",
"operator": "PLARF 62 Base",
"branch": "missile",
"lat": 28.963,
"lng": 117.129
},
{
"name": "Nanyang PLARF Base",
"country": "China",
"operator": "PLARF 63 Base",
"branch": "missile",
"lat": 33.003,
"lng": 112.528
},
{
"name": "Luoyang PLARF Base",
"country": "China",
"operator": "PLARF 63 Base",
"branch": "missile",
"lat": 34.620,
"lng": 112.454
},
{
"name": "Haiyang PLARF Base (ICBM)",
"country": "China",
"operator": "PLARF 65 Base",
"branch": "missile",
"lat": 36.776,
"lng": 121.158
},
{
"name": "Sundian PLARF Base (ICBM)",
"country": "China",
"operator": "PLARF 66 Base",
"branch": "missile",
"lat": 40.200,
"lng": 113.200
},
{
"name": "Vladivostok (Pacific Fleet HQ)",
"country": "Russia",
"operator": "Russian Pacific Fleet",
"branch": "navy",
"lat": 43.114,
"lng": 131.885
},
{
"name": "Vilyuchinsk SSBN Base",
"country": "Russia",
"operator": "Russian Pacific Fleet (SSBN)",
"branch": "navy",
"lat": 52.929,
"lng": 158.405
},
{
"name": "Yelizovo Air Base (Kamchatka)",
"country": "Russia",
"operator": "Russian VKS / Pacific Fleet Aviation",
"branch": "air_force",
"lat": 53.167,
"lng": 158.454
},
{
"name": "Khabarovsk-Bolshoy Air Base",
"country": "Russia",
"operator": "Russian VKS",
"branch": "air_force",
"lat": 48.528,
"lng": 135.188
},
{
"name": "Ukrainka Air Base (Tu-95 bomber)",
"country": "Russia",
"operator": "Russian VKS Long-Range Aviation",
"branch": "air_force",
"lat": 51.170,
"lng": 128.400
},
{
"name": "Fokino Naval Base",
"country": "Russia",
"operator": "Russian Pacific Fleet",
"branch": "navy",
"lat": 42.961,
"lng": 132.447
},
{
"name": "Sovgavan Naval Base",
"country": "Russia",
"operator": "Russian Pacific Fleet",
"branch": "navy",
"lat": 48.966,
"lng": 140.291
},
{
"name": "Vozdvizhenka Air Base",
"country": "Russia",
"operator": "Russian VKS",
"branch": "air_force",
"lat": 43.907,
"lng": 131.984
},
{
"name": "Etorofu / Iturup (Kuril Islands)",
"country": "Russia",
"operator": "Russian Army 18th MG Div",
"branch": "army",
"lat": 44.927,
"lng": 147.863
},
{
"name": "Yongbyon Nuclear Complex",
"country": "North Korea",
"operator": "DPRK Nuclear Program",
"branch": "nuclear",
"lat": 39.796,
"lng": 125.754
},
{
"name": "Punggye-ri Nuclear Test Site",
"country": "North Korea",
"operator": "DPRK Nuclear Program",
"branch": "nuclear",
"lat": 41.281,
"lng": 129.104
},
{
"name": "Sinpo Submarine Base",
"country": "North Korea",
"operator": "KPN (SLBM)",
"branch": "navy",
"lat": 40.024,
"lng": 128.178
},
{
"name": "Sohae Satellite Launching Station",
"country": "North Korea",
"operator": "DPRK NADA",
"branch": "missile",
"lat": 39.660,
"lng": 124.705
},
{
"name": "Tonghae Satellite Launching Ground",
"country": "North Korea",
"operator": "DPRK NADA",
"branch": "missile",
"lat": 40.856,
"lng": 129.665
},
{
"name": "Kaechon Air Base",
"country": "North Korea",
"operator": "KPAF",
"branch": "air_force",
"lat": 39.752,
"lng": 125.890
},
{
"name": "Wonsan-Kalma Air Base",
"country": "North Korea",
"operator": "KPAF",
"branch": "air_force",
"lat": 39.167,
"lng": 127.487
},
{
"name": "Nampo Naval Base",
"country": "North Korea",
"operator": "KPN West Fleet",
"branch": "navy",
"lat": 38.737,
"lng": 125.408
},
{
"name": "Haeju Forward Naval Base",
"country": "North Korea",
"operator": "KPN",
"branch": "navy",
"lat": 38.033,
"lng": 125.714
},
{
"name": "Koksan Long-Range Artillery Base",
"country": "North Korea",
"operator": "KPA Artillery Corps",
"branch": "army",
"lat": 38.330,
"lng": 126.586
},
{
"name": "Hualien Air Force Base (Jiashan)",
"country": "Taiwan",
"operator": "ROCAF 5th TFW",
"branch": "air_force",
"lat": 24.024,
"lng": 121.617
},
{
"name": "Ching Chuan Kang Air Base",
"country": "Taiwan",
"operator": "ROCAF 3rd TFW",
"branch": "air_force",
"lat": 24.264,
"lng": 120.621
},
{
"name": "Zuoying Naval Base",
"country": "Taiwan",
"operator": "ROCN Fleet Command",
"branch": "navy",
"lat": 22.702,
"lng": 120.268
},
{
"name": "Tainan Air Force Base",
"country": "Taiwan",
"operator": "ROCAF 1st TFW",
"branch": "air_force",
"lat": 22.951,
"lng": 120.206
},
{
"name": "Pingtung Air Base (South)",
"country": "Taiwan",
"operator": "ROCAF 6th Mixed Wing",
"branch": "air_force",
"lat": 22.673,
"lng": 120.462
},
{
"name": "Hsinchu Air Force Base",
"country": "Taiwan",
"operator": "ROCAF 2nd TFW",
"branch": "air_force",
"lat": 24.818,
"lng": 120.939
},
{
"name": "Suao Naval Base",
"country": "Taiwan",
"operator": "ROCN 168th Fleet",
"branch": "navy",
"lat": 24.594,
"lng": 121.862
},
{
"name": "Taitung Zhihang Air Base",
"country": "Taiwan",
"operator": "ROCAF 7th FTG",
"branch": "air_force",
"lat": 22.793,
"lng": 121.182
},
{
"name": "Subic Bay (EDCA site)",
"country": "Philippines",
"operator": "Philippine Navy / US EDCA",
"branch": "navy",
"lat": 14.794,
"lng": 120.281
},
{
"name": "Clark Air Base (EDCA site)",
"country": "Philippines",
"operator": "Philippine Air Force / US EDCA",
"branch": "air_force",
"lat": 15.186,
"lng": 120.560
},
{
"name": "Basa Air Base",
"country": "Philippines",
"operator": "Philippine Air Force",
"branch": "air_force",
"lat": 14.988,
"lng": 120.493
},
{
"name": "Antonio Bautista Air Base (Palawan)",
"country": "Philippines",
"operator": "Philippine Air Force",
"branch": "air_force",
"lat": 9.742,
"lng": 118.759
},
{
"name": "Lal-lo (EDCA site, Cagayan)",
"country": "Philippines",
"operator": "Philippine Army / US EDCA",
"branch": "army",
"lat": 18.200,
"lng": 121.659
},
{
"name": "Balabac Naval Station",
"country": "Philippines",
"operator": "Philippine Navy",
"branch": "navy",
"lat": 7.986,
"lng": 117.062
},
{
"name": "RAAF Base Darwin",
"country": "Australia",
"operator": "RAAF / USMC Rotational",
"branch": "air_force",
"lat": -12.415,
"lng": 130.876
},
{
"name": "RAAF Base Tindal",
"country": "Australia",
"operator": "RAAF / USAF Rotational",
"branch": "air_force",
"lat": -14.521,
"lng": 132.378
},
{
"name": "HMAS Stirling (Garden Island)",
"country": "Australia",
"operator": "RAN / AUKUS submarine base",
"branch": "navy",
"lat": -32.235,
"lng": 115.691
},
{
"name": "Pine Gap (Joint Intelligence)",
"country": "Australia",
"operator": "ASD / CIA Joint Facility",
"branch": "army",
"lat": -23.799,
"lng": 133.737
}
]
+646
View File
@@ -0,0 +1,646 @@
{
"412000001": {
"hull_number": "101",
"name": "Nanchang",
"class": "Type 055",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_055_destroyer"
},
"412000002": {
"hull_number": "102",
"name": "Lhasa",
"class": "Type 055",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_055_destroyer"
},
"412000003": {
"hull_number": "103",
"name": "Anshan",
"class": "Type 055",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_055_destroyer"
},
"412000004": {
"hull_number": "104",
"name": "Wuxi",
"class": "Type 055",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_055_destroyer"
},
"412000005": {
"hull_number": "105",
"name": "Dalian",
"class": "Type 055",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_055_destroyer"
},
"412000006": {
"hull_number": "106",
"name": "Yan'an",
"class": "Type 055",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_055_destroyer"
},
"412000007": {
"hull_number": "107",
"name": "Zunyi",
"class": "Type 055",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_055_destroyer"
},
"412000008": {
"hull_number": "108",
"name": "Xianyang",
"class": "Type 055",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_055_destroyer"
},
"412000101": {
"hull_number": "117",
"name": "Xining",
"class": "Type 052D",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052D_destroyer"
},
"412000102": {
"hull_number": "118",
"name": "Urumqi",
"class": "Type 052D",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052D_destroyer"
},
"412000103": {
"hull_number": "119",
"name": "Guiyang",
"class": "Type 052D",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052D_destroyer"
},
"412000104": {
"hull_number": "120",
"name": "Chengdu",
"class": "Type 052D",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052D_destroyer"
},
"412000105": {
"hull_number": "131",
"name": "Taiyuan",
"class": "Type 052D",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052D_destroyer"
},
"412000106": {
"hull_number": "132",
"name": "Suzhou",
"class": "Type 052D",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052D_destroyer"
},
"412000107": {
"hull_number": "133",
"name": "Nantong",
"class": "Type 052D",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052D_destroyer"
},
"412000108": {
"hull_number": "134",
"name": "Suqian",
"class": "Type 052D",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052D_destroyer"
},
"412000109": {
"hull_number": "135",
"name": "Lianyungang",
"class": "Type 052D",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052D_destroyer"
},
"412000110": {
"hull_number": "136",
"name": "Xuchang",
"class": "Type 052D",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052D_destroyer"
},
"412000111": {
"hull_number": "155",
"name": "Nanjing",
"class": "Type 052D",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052D_destroyer"
},
"412000112": {
"hull_number": "156",
"name": "Zibo",
"class": "Type 052D",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052D_destroyer"
},
"412000113": {
"hull_number": "157",
"name": "Lishui",
"class": "Type 052D",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052D_destroyer"
},
"412000114": {
"hull_number": "161",
"name": "Hohhot",
"class": "Type 052D",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052D_destroyer"
},
"412000115": {
"hull_number": "162",
"name": "Yancheng",
"class": "Type 052D",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052D_destroyer"
},
"412000116": {
"hull_number": "163",
"name": "Kaifeng",
"class": "Type 052D",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052D_destroyer"
},
"412000117": {
"hull_number": "164",
"name": "Taizhou",
"class": "Type 052D",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052D_destroyer"
},
"412000201": {
"hull_number": "538",
"name": "Yantai",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412000202": {
"hull_number": "539",
"name": "Wuhu",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412000203": {
"hull_number": "540",
"name": "Huainan",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412000204": {
"hull_number": "541",
"name": "Huaihua",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412000205": {
"hull_number": "542",
"name": "Zaozhuang",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412000206": {
"hull_number": "529",
"name": "Zhoushan",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412000207": {
"hull_number": "530",
"name": "Xuzhou",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412000208": {
"hull_number": "531",
"name": "Xiangtan",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412000209": {
"hull_number": "532",
"name": "Jingzhou",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412000210": {
"hull_number": "536",
"name": "Xuchang",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412000211": {
"hull_number": "546",
"name": "Yancheng",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412000212": {
"hull_number": "547",
"name": "Linyi",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412000213": {
"hull_number": "548",
"name": "Yiyang",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412000214": {
"hull_number": "549",
"name": "Changzhou",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412000215": {
"hull_number": "550",
"name": "Weifang",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412000301": {
"hull_number": "31",
"name": "Hainan",
"class": "Type 075",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_075_landing_helicopter_dock"
},
"412000302": {
"hull_number": "32",
"name": "Guangxi",
"class": "Type 075",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_075_landing_helicopter_dock"
},
"412000303": {
"hull_number": "33",
"name": "Anhui",
"class": "Type 075",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_075_landing_helicopter_dock"
},
"412000401": {
"hull_number": "16",
"name": "Liaoning",
"class": "Type 001",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Chinese_aircraft_carrier_Liaoning"
},
"412000402": {
"hull_number": "17",
"name": "Shandong",
"class": "Type 002",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Chinese_aircraft_carrier_Shandong"
},
"412000403": {
"hull_number": "18",
"name": "Fujian",
"class": "Type 003",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Chinese_aircraft_carrier_Fujian"
},
"412000501": {
"hull_number": "980",
"name": "Hulunhu",
"class": "Type 901",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_901_replenishment_ship"
},
"412000502": {
"hull_number": "981",
"name": "Chaganhu",
"class": "Type 901",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_901_replenishment_ship"
},
"412000601": {
"hull_number": "998",
"name": "Kunlun Shan",
"class": "Type 071",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_071_amphibious_transport_dock"
},
"412000602": {
"hull_number": "999",
"name": "Jinggang Shan",
"class": "Type 071",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_071_amphibious_transport_dock"
},
"412000603": {
"hull_number": "989",
"name": "Changbai Shan",
"class": "Type 071",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_071_amphibious_transport_dock"
},
"412000604": {
"hull_number": "988",
"name": "Yimeng Shan",
"class": "Type 071",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_071_amphibious_transport_dock"
},
"412000605": {
"hull_number": "987",
"name": "Wuzhi Shan",
"class": "Type 071",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_071_amphibious_transport_dock"
},
"412000606": {
"hull_number": "986",
"name": "Longhu Shan",
"class": "Type 071",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_071_amphibious_transport_dock"
},
"412000607": {
"hull_number": "985",
"name": "Dabie Shan",
"class": "Type 071",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_071_amphibious_transport_dock"
},
"412000608": {
"hull_number": "984",
"name": "Wuyi Shan",
"class": "Type 071",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_071_amphibious_transport_dock"
},
"412000701": {
"hull_number": "815A-1",
"name": "Dongdiao",
"class": "Type 815A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_815_electronic_reconnaissance_ship"
},
"412000702": {
"hull_number": "815A-2",
"name": "Haiwangxing",
"class": "Type 815A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_815_electronic_reconnaissance_ship"
},
"412000703": {
"hull_number": "815A-3",
"name": "Tianwangxing",
"class": "Type 815A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_815_electronic_reconnaissance_ship"
},
"412009001": {
"hull_number": "2901",
"name": "CCG 2901",
"class": "12000-ton Cutter",
"force": "CCG",
"wiki": "https://en.wikipedia.org/wiki/China_Coast_Guard"
},
"412009002": {
"hull_number": "3901",
"name": "CCG 3901",
"class": "12000-ton Cutter",
"force": "CCG",
"wiki": "https://en.wikipedia.org/wiki/China_Coast_Guard"
},
"412009003": {
"hull_number": "1305",
"name": "CCG 1305",
"class": "Type 818",
"force": "CCG",
"wiki": "https://en.wikipedia.org/wiki/China_Coast_Guard"
},
"412009004": {
"hull_number": "1306",
"name": "CCG 1306",
"class": "Type 818",
"force": "CCG",
"wiki": "https://en.wikipedia.org/wiki/China_Coast_Guard"
},
"412009005": {
"hull_number": "2502",
"name": "CCG 2502",
"class": "5000-ton Cutter",
"force": "CCG",
"wiki": "https://en.wikipedia.org/wiki/China_Coast_Guard"
},
"412009006": {
"hull_number": "2302",
"name": "CCG 2302",
"class": "3000-ton Cutter",
"force": "CCG",
"wiki": "https://en.wikipedia.org/wiki/China_Coast_Guard"
},
"412009007": {
"hull_number": "2303",
"name": "CCG 2303",
"class": "3000-ton Cutter",
"force": "CCG",
"wiki": "https://en.wikipedia.org/wiki/China_Coast_Guard"
},
"412009008": {
"hull_number": "1103",
"name": "CCG 1103",
"class": "Type 718B",
"force": "CCG",
"wiki": "https://en.wikipedia.org/wiki/China_Coast_Guard"
},
"412009009": {
"hull_number": "1105",
"name": "CCG 1105",
"class": "Type 718B",
"force": "CCG",
"wiki": "https://en.wikipedia.org/wiki/China_Coast_Guard"
},
"412009010": {
"hull_number": "1302",
"name": "CCG 1302",
"class": "Type 818",
"force": "CCG",
"wiki": "https://en.wikipedia.org/wiki/China_Coast_Guard"
},
"412000801": {
"hull_number": "171",
"name": "Haikou",
"class": "Type 052C",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052C_destroyer"
},
"412000802": {
"hull_number": "170",
"name": "Lanzhou",
"class": "Type 052C",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052C_destroyer"
},
"412000803": {
"hull_number": "150",
"name": "Changchun",
"class": "Type 052C",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052C_destroyer"
},
"412000804": {
"hull_number": "151",
"name": "Zhengzhou",
"class": "Type 052C",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052C_destroyer"
},
"412000805": {
"hull_number": "152",
"name": "Jinan",
"class": "Type 052C",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052C_destroyer"
},
"412000806": {
"hull_number": "153",
"name": "Xi'an",
"class": "Type 052C",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052C_destroyer"
},
"412000901": {
"hull_number": "572",
"name": "Hengshui",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412000902": {
"hull_number": "573",
"name": "Liuzhou",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412000903": {
"hull_number": "574",
"name": "Sanya",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412000904": {
"hull_number": "575",
"name": "Yueyang",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412000905": {
"hull_number": "576",
"name": "Daqing",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412000906": {
"hull_number": "577",
"name": "Huanggang",
"class": "Type 054A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_054A_frigate"
},
"412001001": {
"hull_number": "500",
"name": "Xianfeng",
"class": "Type 056A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_056_corvette"
},
"412001002": {
"hull_number": "501",
"name": "Xinyang",
"class": "Type 056A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_056_corvette"
},
"412001003": {
"hull_number": "502",
"name": "Huangshi",
"class": "Type 056",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_056_corvette"
},
"412001004": {
"hull_number": "509",
"name": "Huaian",
"class": "Type 056A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_056_corvette"
},
"412001005": {
"hull_number": "510",
"name": "Ningde",
"class": "Type 056A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_056_corvette"
},
"412001101": {
"hull_number": "795",
"name": "Nanchong",
"class": "Type 039A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_039A_submarine"
},
"412001201": {
"hull_number": "892",
"name": "Hualuoshan",
"class": "Type 903A",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_903_replenishment_ship"
},
"412001202": {
"hull_number": "889",
"name": "Taihu",
"class": "Type 903",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_903_replenishment_ship"
},
"412001301": {
"hull_number": "636",
"name": "Nanning",
"class": "Type 052DL",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052D_destroyer"
},
"412001302": {
"hull_number": "165",
"name": "Zhanjiang",
"class": "Type 052DL",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052D_destroyer"
},
"412001303": {
"hull_number": "166",
"name": "Huainan",
"class": "Type 052DL",
"force": "PLAN",
"wiki": "https://en.wikipedia.org/wiki/Type_052D_destroyer"
}
}
+5
View File
@@ -39,6 +39,11 @@ def fetch_ships():
for ship in ships:
enrich_with_yacht_alert(ship)
# Enrich ships with PLAN/CCG vessel data
from services.fetchers.plan_vessel_alert import enrich_with_plan_vessel
for ship in ships:
enrich_with_plan_vessel(ship)
logger.info(f"Ships: {len(carriers)} carriers + {len(ais_vessels)} AIS vessels")
with _data_lock:
latest_data['ships'] = ships
+12 -1
View File
@@ -38,6 +38,11 @@ _ICAO_COUNTRY_RANGES = [
(0x840000, 0x87FFFF, "Japan", "JSDF"),
(0x700000, 0x71FFFF, "South Korea", "ROK"),
(0xE80000, 0xE80FFF, "Taiwan", "ROC"),
(0x150000, 0x157FFF, "Russia", "VKS"),
(0x7C0000, 0x7FFFFF, "Australia", "RAAF"),
(0x758000, 0x75FFFF, "Philippines", "PAF"),
(0x768000, 0x76FFFF, "Singapore", "RSAF"),
(0x720000, 0x727FFF, "North Korea", "KPAF"),
]
@@ -66,18 +71,24 @@ def _classify_military_type(raw_model: str) -> str:
if any(k in model for k in [
"F16", "F35", "F22", "F15", "F18", "T38", "T6", "A10",
"J10", "J11", "J15", "J16", "J20", "JF17",
"SU27", "SU30", "SU35",
"SU27", "SU30", "SU35", "SU57", "MIG29", "MIG31",
"F15J", "F2", "IDF", "FA50", "KF21",
]):
return "fighter"
if any(k in model for k in [
"TU95", "TU160", "TU22",
]):
return "bomber"
if any(k in model for k in [
"C17", "C5", "C130", "C30", "A400", "V22",
"Y20", "Y9", "Y8", "C2",
"IL76", "AN124", "AN12",
]):
return "cargo"
if any(k in model for k in [
"P8", "E3", "E8", "U2",
"KJ500", "KJ200", "GX11", "P1", "E767", "E2K", "E2C",
"A50", "TU214R", "IL20",
]):
return "recon"
return "default"
+16
View File
@@ -111,6 +111,22 @@ _KEYWORD_COORDS = {
"singapore": (1.352, 103.819),
"bangkok": (13.756, 100.501),
"jakarta": (-6.208, 106.845),
# East Asia — islands, straits, and disputed areas
"pratas": (20.71, 116.72),
"dongsha": (20.71, 116.72),
"kinmen": (24.45, 118.38),
"matsu": (26.16, 119.94),
"scarborough": (15.14, 117.77),
"paracel": (16.50, 112.00),
"spratly": (10.00, 114.00),
"miyako strait": (24.78, 125.30),
"bashi channel": (21.00, 121.50),
"luzon strait": (20.50, 121.50),
" dmz ": (38.00, 127.00),
"yalu": (40.00, 124.40),
"yongbyon": (39.80, 125.76),
"wonsan": (39.18, 127.48),
"busan": (35.18, 129.07),
}
# Immutable after module load — sort by descending keyword length so
@@ -0,0 +1,42 @@
"""PLAN/CCG Vessel Alert DB — load and enrich AIS vessels with Chinese navy/coast guard metadata."""
import os
import json
import logging
logger = logging.getLogger("services.data_fetcher")
_PLAN_CCG_DB: dict = {}
def _load_plan_ccg_db():
"""Load plan_ccg_vessels.json into memory at import time."""
global _PLAN_CCG_DB
json_path = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
"data", "plan_ccg_vessels.json"
)
if not os.path.exists(json_path):
logger.warning(f"PLAN/CCG vessel DB not found at {json_path}")
return
try:
with open(json_path, "r", encoding="utf-8") as fh:
_PLAN_CCG_DB.update(json.load(fh))
logger.info(f"PLAN/CCG vessel DB loaded: {len(_PLAN_CCG_DB)} vessels")
except (IOError, OSError, json.JSONDecodeError, ValueError, KeyError) as e:
logger.error(f"Failed to load PLAN/CCG vessel DB: {e}")
_load_plan_ccg_db()
def enrich_with_plan_vessel(ship: dict) -> dict:
"""If ship's MMSI is in the PLAN/CCG DB, attach enrichment metadata."""
mmsi = str(ship.get("mmsi", "")).strip()
if mmsi and mmsi in _PLAN_CCG_DB:
info = _PLAN_CCG_DB[mmsi]
ship["plan_name"] = info.get("name", "")
ship["plan_class"] = info.get("class", "")
ship["plan_force"] = info.get("force", "")
ship["plan_hull"] = info.get("hull_number", "")
ship["plan_wiki"] = info.get("wiki", "")
return ship
+7 -1
View File
@@ -9,7 +9,7 @@ from pathlib import Path
logger = logging.getLogger(__name__)
CONFIG_PATH = Path(__file__).parent.parent / "config" / "news_feeds.json"
MAX_FEEDS = 20
MAX_FEEDS = 25
DEFAULT_FEEDS = [
{"name": "NPR", "url": "https://feeds.npr.org/1004/rss.xml", "weight": 4},
@@ -25,6 +25,12 @@ DEFAULT_FEEDS = [
{"name": "SCMP", "url": "https://www.scmp.com/rss/91/feed", "weight": 4},
{"name": "The Diplomat", "url": "https://thediplomat.com/feed/", "weight": 4},
{"name": "Stars and Stripes", "url": "https://www.stripes.com/feeds/pacific.rss", "weight": 4},
{"name": "Yonhap", "url": "https://en.yna.co.kr/RSS/news.xml", "weight": 4},
{"name": "Nikkei Asia", "url": "https://asia.nikkei.com/rss", "weight": 3},
{"name": "Taipei Times", "url": "https://www.taipeitimes.com/xml/pda.rss", "weight": 4},
{"name": "Asia Times", "url": "https://asiatimes.com/feed/", "weight": 3},
{"name": "Defense News", "url": "https://www.defensenews.com/arc/outboundfeeds/rss/", "weight": 3},
{"name": "Japan Times", "url": "https://www.japantimes.co.jp/feed/", "weight": 3},
]
+34
View File
@@ -36,6 +36,24 @@ class TestEnrichCountry:
def test_invalid_hex_with_empty(self):
assert _enrich_country("ZZZZ", "") == ("Military Asset", "")
def test_russia_range(self):
assert _enrich_country("150000", "Unknown") == ("Russia", "VKS")
def test_russia_range_end(self):
assert _enrich_country("157FFF", "Unknown") == ("Russia", "VKS")
def test_australia_range(self):
assert _enrich_country("7C0000", "Unknown") == ("Australia", "RAAF")
def test_philippines_range(self):
assert _enrich_country("758000", "Unknown") == ("Philippines", "PAF")
def test_singapore_range(self):
assert _enrich_country("768000", "Unknown") == ("Singapore", "RSAF")
def test_north_korea_range(self):
assert _enrich_country("720000", "Unknown") == ("North Korea", "KPAF")
class TestClassifyMilitaryType:
@pytest.mark.parametrize("model,expected", [
@@ -52,6 +70,22 @@ class TestClassifyMilitaryType:
("H60", "heli"),
("K35", "tanker"),
("Boeing 737", "default"),
# Russian aircraft
("SU-27", "fighter"),
("SU-30", "fighter"),
("SU-35", "fighter"),
("SU-57", "fighter"),
("MiG-29", "fighter"),
("MiG-31", "fighter"),
("Tu-95", "bomber"),
("Tu-160", "bomber"),
("Tu-22", "bomber"),
("IL-76", "cargo"),
("AN-124", "cargo"),
("AN-12", "cargo"),
("A-50", "recon"),
("Tu-214R", "recon"),
("IL-20", "recon"),
])
def test_classification(self, model: str, expected: str):
assert _classify_military_type(model) == expected
+12 -1
View File
@@ -35,11 +35,22 @@ class TestMilitaryBasesData:
assert -180 <= entry["lng"] <= 180, f"{entry['name']} has invalid lng"
def test_branch_values_are_known(self):
known_branches = {"air_force", "navy", "marines", "army"}
known_branches = {"air_force", "navy", "marines", "army", "missile", "nuclear"}
raw = json.loads(BASES_PATH.read_text(encoding="utf-8"))
for entry in raw:
assert entry["branch"] in known_branches, f"{entry['name']} has unknown branch: {entry['branch']}"
def test_adversary_bases_present(self):
raw = json.loads(BASES_PATH.read_text(encoding="utf-8"))
countries = {entry["country"] for entry in raw}
for expected in ("China", "Russia", "North Korea", "Taiwan"):
assert expected in countries, f"Missing bases for {expected}"
def test_no_duplicate_names(self):
raw = json.loads(BASES_PATH.read_text(encoding="utf-8"))
names = [entry["name"] for entry in raw]
assert len(names) == len(set(names)), "Duplicate base names found"
class TestFetchMilitaryBases:
"""Test the fetcher populates latest_data correctly."""
+49 -1
View File
@@ -72,6 +72,53 @@ class TestResolveCoords:
result = _resolve_coords("visit the uk soon")
assert result == (55.378, -3.435)
# -- New East Asia island/strait keywords ------------------------------------
def test_pratas(self):
assert _resolve_coords("china patrols near pratas islands") == (20.71, 116.72)
def test_dongsha(self):
assert _resolve_coords("dongsha atoll tensions") == (20.71, 116.72)
def test_kinmen(self):
assert _resolve_coords("artillery drill near kinmen") == (24.45, 118.38)
def test_matsu(self):
assert _resolve_coords("matsu island cable cut") == (26.16, 119.94)
def test_scarborough(self):
assert _resolve_coords("scarborough shoal standoff") == (15.14, 117.77)
def test_paracel(self):
assert _resolve_coords("paracel islands dispute") == (16.50, 112.00)
def test_spratly(self):
assert _resolve_coords("spratly island reclamation") == (10.00, 114.00)
def test_miyako_strait(self):
assert _resolve_coords("PLAN warships transit miyako strait") == (24.78, 125.30)
def test_bashi_channel(self):
assert _resolve_coords("submarine detected in bashi channel") == (21.00, 121.50)
def test_luzon_strait(self):
assert _resolve_coords("luzon strait patrol") == (20.50, 121.50)
def test_dmz(self):
assert _resolve_coords("tension at the dmz border") == (38.00, 127.00)
def test_yalu(self):
assert _resolve_coords("troops near yalu river") == (40.00, 124.40)
def test_yongbyon(self):
assert _resolve_coords("activity at yongbyon reactor") == (39.80, 125.76)
def test_wonsan(self):
assert _resolve_coords("missile launch from wonsan") == (39.18, 127.48)
def test_busan(self):
assert _resolve_coords("naval exercise near busan port") == (35.18, 129.07)
# -- No match --------------------------------------------------------------
def test_no_match_returns_none(self):
@@ -98,5 +145,6 @@ class TestFeedConfig:
def test_new_east_asia_feeds_present(self):
names = {f["name"] for f in DEFAULT_FEEDS}
expected = {"FocusTaiwan", "Kyodo", "SCMP", "The Diplomat", "Stars and Stripes"}
expected = {"FocusTaiwan", "Kyodo", "SCMP", "The Diplomat", "Stars and Stripes",
"Yonhap", "Nikkei Asia", "Taipei Times", "Asia Times", "Defense News", "Japan Times"}
assert expected.issubset(names)
+14 -6
View File
@@ -1475,11 +1475,11 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele
id="military-bases-layer"
type="circle"
paint={{
'circle-color': '#ef4444',
'circle-color': ['match', ['get', 'side'], 'red', '#ef4444', 'green', '#22c55e', '#3b82f6'],
'circle-radius': ['interpolate', ['linear'], ['zoom'], 2, 4, 6, 7, 10, 10],
'circle-opacity': 0.8,
'circle-stroke-width': 2,
'circle-stroke-color': '#fca5a5',
'circle-stroke-color': ['match', ['get', 'side'], 'red', '#fca5a5', 'green', '#86efac', '#93c5fd'],
}}
/>
<Layer
@@ -1494,7 +1494,7 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele
'text-allow-overlap': false,
}}
paint={{
'text-color': '#fca5a5',
'text-color': ['match', ['get', 'side'], 'red', '#fca5a5', 'green', '#86efac', '#93c5fd'],
'text-halo-color': 'rgba(0,0,0,0.9)',
'text-halo-width': 1,
}}
@@ -1890,7 +1890,15 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele
if (!base) return null;
const branchLabel: Record<string, string> = {
air_force: 'AIR FORCE', navy: 'NAVY', marines: 'MARINES', army: 'ARMY',
missile: 'MISSILE FORCES', nuclear: 'NUCLEAR FACILITY',
};
const isAdversary = ['China', 'Russia', 'North Korea'].includes(base.country);
const isROC = base.country === 'Taiwan';
const accentColor = isAdversary ? 'red' : isROC ? 'green' : 'blue';
const borderCls = isAdversary ? 'border-red-400/40' : isROC ? 'border-green-400/40' : 'border-blue-400/40';
const textCls = isAdversary ? 'text-[#fca5a5]' : isROC ? 'text-[#86efac]' : 'text-[#93c5fd]';
const titleCls = isAdversary ? 'text-red-400 border-b border-red-400/20' : isROC ? 'text-green-400 border-b border-green-400/20' : 'text-blue-400 border-b border-blue-400/20';
const footerCls = isAdversary ? 'text-red-600' : isROC ? 'text-green-600' : 'text-blue-600';
return (
<Popup
longitude={base.lng}
@@ -1901,8 +1909,8 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele
className="threat-popup"
maxWidth="280px"
>
<div className="map-popup bg-[#1a1035] border border-red-400/40 text-[#fca5a5] min-w-[200px]">
<div className="map-popup-title text-red-400 border-b border-red-400/20 pb-1">
<div className={`map-popup bg-[#1a1035] border ${borderCls} ${textCls} min-w-[200px]`}>
<div className={`map-popup-title ${titleCls} pb-1`}>
{base.name}
</div>
<div className="map-popup-row">
@@ -1911,7 +1919,7 @@ const MaplibreViewer = ({ data, activeLayers, onEntityClick, flyToLocation, sele
<div className="map-popup-row">
Location: <span className="text-white">{base.country}</span>
</div>
<div className="mt-1.5 text-[9px] text-red-600 tracking-wider">
<div className={`mt-1.5 text-[9px] ${footerCls} tracking-wider`}>
MILITARY BASE {branchLabel[base.branch] || base.branch.toUpperCase()}
</div>
</div>
@@ -197,6 +197,16 @@ export function buildDataCentersGeoJSON(datacenters?: DataCenter[]): FC {
// ─── Military Bases ─────────────────────────────────────────────────────────
// Classify base alignment: red = adversary, blue = US/allied, green = ROC
const _ADVERSARY_COUNTRIES = new Set(["China", "Russia", "North Korea"]);
const _ROC_COUNTRIES = new Set(["Taiwan"]);
function _baseSide(country: string, operator: string): "red" | "blue" | "green" {
if (_ADVERSARY_COUNTRIES.has(country)) return "red";
if (_ROC_COUNTRIES.has(country)) return "green";
return "blue";
}
export function buildMilitaryBasesGeoJSON(bases?: MilitaryBase[]): FC {
if (!bases?.length) return null;
return {
@@ -210,6 +220,7 @@ export function buildMilitaryBasesGeoJSON(bases?: MilitaryBase[]): FC {
country: base.country || '',
operator: base.operator || '',
branch: base.branch || '',
side: _baseSide(base.country || '', base.operator || ''),
},
geometry: { type: 'Point' as const, coordinates: [base.lng, base.lat] }
}))
+7 -1
View File
@@ -43,7 +43,7 @@ export interface PrivateJet extends FlightBase {
export interface MilitaryFlight extends FlightBase {
type: "military_flight";
military_type?: "heli" | "fighter" | "tanker" | "cargo" | "recon" | "default";
military_type?: "heli" | "fighter" | "bomber" | "tanker" | "cargo" | "recon" | "default";
force?: string;
}
@@ -106,6 +106,12 @@ export interface Ship {
yacht_length?: number;
yacht_year?: number;
yacht_link?: string;
// PLAN/CCG vessel enrichment
plan_name?: string;
plan_class?: string;
plan_force?: string;
plan_hull?: string;
plan_wiki?: string;
// Carrier enrichment
wiki?: string;
homeport?: string;