libacfutils
A general purpose library of utility functions designed to make it easier to develop addons for the X-Plane flight simulator.
Loading...
Searching...
No Matches
airportdb.c
1/*
2 * CDDL HEADER START
3 *
4 * This file and its contents are supplied under the terms of the
5 * Common Development and Distribution License ("CDDL"), version 1.0.
6 * You may only use this file in accordance with the terms of version
7 * 1.0 of the CDDL.
8 *
9 * A full copy of the text of the CDDL should have accompanied this
10 * source. A copy of the CDDL is also available via the Internet at
11 * http://www.illumos.org/license/CDDL.
12 *
13 * CDDL HEADER END
14 *
15 * Copyright 2023 Saso Kiselkov. All rights reserved.
16 */
17
18#include <errno.h>
19#include <iconv.h>
20#include <stddef.h>
21#include <locale.h>
22#include <math.h>
23#include <stdint.h>
24#include <string.h>
25#include <stdlib.h>
26#include <ctype.h>
27
28#if IBM
29#include <windows.h>
30#include <strsafe.h>
31#else /* !IBM */
32#include <sys/types.h>
33#include <sys/stat.h>
34#include <unistd.h>
35#include <dirent.h>
36#endif /* !IBM */
37
38#include "acfutils/airportdb.h"
39#include "acfutils/assert.h"
40#include "acfutils/avl.h"
41#include "acfutils/conf.h"
42#include "acfutils/geom.h"
43#include "acfutils/helpers.h"
44#include "acfutils/list.h"
45#include "acfutils/math.h"
46#include "acfutils/optional.h"
47#include "acfutils/perf.h"
48#include "acfutils/safe_alloc.h"
49#include "acfutils/types.h"
50
51#define RWY_PROXIMITY_LAT_FRACT 3
52#define RWY_PROXIMITY_LON_DISPL 609.57 /* meters, 2000 ft */
53
54#define RWY_APCH_PROXIMITY_LAT_ANGLE 3.3 /* degrees */
55#define RWY_APCH_PROXIMITY_LON_DISPL 5500 /* meters */
56/* precomputed, since it doesn't change */
57#define RWY_APCH_PROXIMITY_LAT_DISPL (RWY_APCH_PROXIMITY_LON_DISPL * \
58 __builtin_tan(DEG2RAD(RWY_APCH_PROXIMITY_LAT_ANGLE)))
59#define ARPTDB_CACHE_VERSION 21
60
61#define VGSI_LAT_DISPL_FACT 2 /* rwy width multiplier */
62#define VGSI_HDG_MATCH_THRESH 5 /* degrees */
63#define ILS_HDG_MATCH_THRESH 2 /* degrees */
64/*
65 * GS emitters don't originate their beam at the ground, so we add a bit of
66 * a fudge factor to account for antenna height to our TCH computation.
67 */
68#define ILS_GS_GND_OFFSET 5 /* meters */
69
70#define MIN_RWY_LEN 10 /* meters */
71#define RWY_GPA_LIMIT 10 /* degrees */
72#define RWY_TCH_LIMIT 200 /* feet */
73#define TCH_IS_VALID(tch) (tch > 0 && tch < RWY_TCH_LIMIT)
74
75#define TILE_NAME_FMT "%+03.0f%+04.0f"
76
77#define ARPT_LOAD_LIMIT NM2MET(8) /* meters */
78
79/*
80 * Visual Glide Slope Indicator type (PAPI, VASI, etc.).
81 * Type codes used in apt.dat (XP-APT1000-Spec.pdf at data.x-plane.com).
82 */
83typedef enum {
84 VGSI_VASI = 1,
85 VGSI_PAPI_4L = 2,
86 VGSI_PAPI_4R = 3,
87 VGSI_PAPI_20DEG = 4,
88 VGSI_PAPI_3C = 5
89} vgsi_t;
90
91typedef struct tile_s {
92 geo_pos2_t pos; /* tile position (see `geo_pos2tile_pos') */
93 avl_tree_t arpts; /* airport_t's sorted by `airport_compar' */
94 avl_node_t node;
95} tile_t;
96
97typedef struct {
98 list_node_t node;
99 char *fname;
101
102static struct {
103 const char *code;
104 const char *name;
105} iso3166_codes[] = {
106 { "AFG", "Afghanistan" },
107 { "ALA", "Åland Islands" },
108 { "ALB", "Albania" },
109 { "DZA", "Algeria" },
110 { "ASM", "American Samoa" },
111 { "AND", "Andorra" },
112 { "AGO", "Angola" },
113 { "AIA", "Anguilla" },
114 { "ATA", "Antarctica" },
115 { "ATG", "Antigua and Barbuda" },
116 { "ARG", "Argentina" },
117 { "ARM", "Armenia" },
118 { "ABW", "Aruba" },
119 { "AUS", "Australia" },
120 { "AUT", "Austria" },
121 { "AZE", "Azerbaijan" },
122 { "BHS", "Bahamas" },
123 { "BHR", "Bahrain" },
124 { "BGD", "Bangladesh" },
125 { "BRB", "Barbados" },
126 { "BLR", "Belarus" },
127 { "BEL", "Belgium" },
128 { "BLZ", "Belize" },
129 { "BEN", "Benin" },
130 { "BMU", "Bermuda" },
131 { "BTN", "Bhutan" },
132 { "BOL", "Bolivia" },
133 { "BES", "Bonaire, Sint Eustatius and Saba" },
134 { "BIH", "Bosnia and Herzegovina" },
135 { "BWA", "Botswana" },
136 { "BVT", "Bouvet Island" },
137 { "BRA", "Brazil" },
138 { "IOT", "British Indian Ocean Territory" },
139 { "BRN", "Brunei Darussalam" },
140 { "BGR", "Bulgaria" },
141 { "BFA", "Burkina Faso" },
142 { "BDI", "Burundi" },
143 { "CPV", "Cabo Verde" },
144 { "KHM", "Cambodia" },
145 { "CMR", "Cameroon" },
146 { "CAN", "Canada" },
147 { "CYM", "Cayman Islands" },
148 { "CAF", "Central African Republic" },
149 { "TCD", "Chad" },
150 { "CHL", "Chile" },
151 { "CHN", "China" },
152 { "CXR", "Christmas Island" },
153 { "CCK", "Cocos Islands" },
154 { "COL", "Colombia" },
155 { "COM", "Comoros" },
156 { "COD", "Democratic Republic of the Congo" },
157 { "COG", "Congo" },
158 { "COK", "Cook Islands" },
159 { "CRI", "Costa Rica" },
160 { "CIV", "Côte d'Ivoire" },
161 { "HRV", "Croatia" },
162 { "CUB", "Cuba" },
163 { "CUW", "Curaçao" },
164 { "CYP", "Cyprus" },
165 { "CZE", "Czechia" },
166 { "DNK", "Denmark" },
167 { "DJI", "Djibouti" },
168 { "DMA", "Dominica" },
169 { "DOM", "Dominican Republic" },
170 { "ECU", "Ecuador" },
171 { "EGY", "Egypt" },
172 { "SLV", "El Salvador" },
173 { "GNQ", "Equatorial Guinea" },
174 { "ERI", "Eritrea" },
175 { "EST", "Estonia" },
176 { "SWZ", "Eswatini" },
177 { "ETH", "Ethiopia" },
178 { "FLK", "Falkland Islands" },
179 { "FRO", "Faroe Islands" },
180 { "FJI", "Fiji" },
181 { "FIN", "Finland" },
182 { "FRA", "France" },
183 { "GUF", "French Guiana" },
184 { "PYF", "French Polynesia" },
185 { "ATF", "French Southern Territories" },
186 { "GAB", "Gabon" },
187 { "GMB", "Gambia" },
188 { "GEO", "Georgia" },
189 { "DEU", "Germany" },
190 { "GHA", "Ghana" },
191 { "GIB", "Gibraltar" },
192 { "GRC", "Greece" },
193 { "GRL", "Greenland" },
194 { "GRD", "Grenada" },
195 { "GLP", "Guadeloupe" },
196 { "GUM", "Guam" },
197 { "GTM", "Guatemala" },
198 { "GGY", "Guernsey" },
199 { "GIN", "Guinea" },
200 { "GNB", "Guinea-Bissau" },
201 { "GUY", "Guyana" },
202 { "HTI", "Haiti" },
203 { "HMD", "Heard Island and McDonald Islands" },
204 { "VAT", "Holy See" },
205 { "HND", "Honduras" },
206 { "HKG", "Hong Kong" },
207 { "HUN", "Hungary" },
208 { "ISL", "Iceland" },
209 { "IND", "India" },
210 { "IDN", "Indonesia" },
211 { "IRN", "Iran" },
212 { "IRQ", "Iraq" },
213 { "IRL", "Ireland" },
214 { "IMN", "Isle of Man" },
215 { "ISR", "Israel" },
216 { "ITA", "Italy" },
217 { "JAM", "Jamaica" },
218 { "JPN", "Japan" },
219 { "JEY", "Jersey" },
220 { "JOR", "Jordan" },
221 { "KAZ", "Kazakhstan" },
222 { "KEN", "Kenya" },
223 { "KIR", "Kiribati" },
224 { "PRK", "Democratic People's Republic of Korea" },
225 { "KOR", "Republic of Korea" },
226 { "KWT", "Kuwait" },
227 { "KGZ", "Kyrgyzstan" },
228 { "LAO", "Laos" },
229 { "LVA", "Latvia" },
230 { "LBN", "Lebanon" },
231 { "LSO", "Lesotho" },
232 { "LBR", "Liberia" },
233 { "LBY", "Libya" },
234 { "LIE", "Liechtenstein" },
235 { "LTU", "Lithuania" },
236 { "LUX", "Luxembourg" },
237 { "MAC", "Macao" },
238 { "MKD", "Republic of North Macedonia" },
239 { "MDG", "Madagascar" },
240 { "MWI", "Malawi" },
241 { "MYS", "Malaysia" },
242 { "MDV", "Maldives" },
243 { "MLI", "Mali" },
244 { "MLT", "Malta" },
245 { "MHL", "Marshall Islands" },
246 { "MTQ", "Martinique" },
247 { "MRT", "Mauritania" },
248 { "MUS", "Mauritius" },
249 { "MYT", "Mayotte" },
250 { "MEX", "Mexico" },
251 { "FSM", "Micronesia" },
252 { "MDA", "Moldova" },
253 { "MCO", "Monaco" },
254 { "MNG", "Mongolia" },
255 { "MNE", "Montenegro" },
256 { "MSR", "Montserrat" },
257 { "MAR", "Morocco" },
258 { "MOZ", "Mozambique" },
259 { "MMR", "Myanmar" },
260 { "NAM", "Namibia" },
261 { "NRU", "Nauru" },
262 { "NPL", "Nepal" },
263 { "NLD", "Netherlands" },
264 { "NCL", "New Caledonia" },
265 { "NZL", "New Zealand" },
266 { "NIC", "Nicaragua" },
267 { "NER", "Niger" },
268 { "NGA", "Nigeria" },
269 { "NIU", "Niue" },
270 { "NFK", "Norfolk Island" },
271 { "MNP", "Northern Mariana Islands" },
272 { "NOR", "Norway" },
273 { "OMN", "Oman" },
274 { "PAK", "Pakistan" },
275 { "PLW", "Palau" },
276 { "PSE", "Palestine, State of" },
277 { "PAN", "Panama" },
278 { "PNG", "Papua New Guinea" },
279 { "PRY", "Paraguay" },
280 { "PER", "Peru" },
281 { "PHL", "Philippines" },
282 { "PCN", "Pitcairn" },
283 { "POL", "Poland" },
284 { "PRT", "Portugal" },
285 { "PRI", "Puerto Rico" },
286 { "QAT", "Qatar" },
287 { "REU", "Réunion" },
288 { "ROU", "Romania" },
289 { "RUS", "Russian Federation" },
290 { "RWA", "Rwanda" },
291 { "BLM", "Saint Barthélemy" },
292 { "SHN", "Saint Helena" },
293 { "KNA", "Saint Kitts and Nevis" },
294 { "LCA", "Saint Lucia" },
295 { "MAF", "Saint Martin" },
296 { "SPM", "Saint Pierre and Miquelon" },
297 { "VCT", "Saint Vincent and the Grenadines" },
298 { "WSM", "Samoa" },
299 { "SMR", "San Marino" },
300 { "STP", "Sao Tome and Principe" },
301 { "SAU", "Saudi Arabia" },
302 { "SEN", "Senegal" },
303 { "SRB", "Serbia" },
304 { "SYC", "Seychelles" },
305 { "SLE", "Sierra Leone" },
306 { "SGP", "Singapore" },
307 { "SXM", "Sint Maarten" },
308 { "SVK", "Slovakia" },
309 { "SVN", "Slovenia" },
310 { "SLB", "Solomon Islands" },
311 { "SOM", "Somalia" },
312 { "ZAF", "South Africa" },
313 { "SGS", "South Georgia and the South Sandwich Islands" },
314 { "SSD", "South Sudan" },
315 { "ESP", "Spain" },
316 { "LKA", "Sri Lanka" },
317 { "SDN", "Sudan" },
318 { "SUR", "Suriname" },
319 { "SJM", "Svalbard and Jan Mayen" },
320 { "SWE", "Sweden" },
321 { "CHE", "Switzerland" },
322 { "SYR", "Syrian Arab Republic" },
323 { "TWN", "Taiwan" },
324 { "TJK", "Tajikistan" },
325 { "TZA", "Tanzania" },
326 { "THA", "Thailand" },
327 { "TLS", "Timor-Leste" },
328 { "TGO", "Togo" },
329 { "TKL", "Tokelau" },
330 { "TON", "Tonga" },
331 { "TTO", "Trinidad and Tobago" },
332 { "TUN", "Tunisia" },
333 { "TUR", "Turkey" },
334 { "TKM", "Turkmenistan" },
335 { "TCA", "Turks and Caicos Islands" },
336 { "TUV", "Tuvalu" },
337 { "UGA", "Uganda" },
338 { "UKR", "Ukraine" },
339 { "ARE", "United Arab Emirates" },
340 { "GBR", "UK" },
341 { "UMI", "United States Minor Outlying Islands" },
342 { "USA", "United States of America" },
343 { "URY", "Uruguay" },
344 { "UZB", "Uzbekistan" },
345 { "VUT", "Vanuatu" },
346 { "VEN", "Venezuela" },
347 { "VNM", "Viet Nam" },
348 { "VGB", "British Virgin Islands" },
349 { "VIR", "U.S. Virgin Islands" },
350 { "WLF", "Wallis and Futuna" },
351 { "ESH", "Western Sahara" },
352 { "YEM", "Yemen" },
353 { "ZMB", "Zambia" },
354 { "ZWE", "Zimbabwe" }
355};
356
357static airport_t *apt_dat_lookup(airportdb_t *db, const char *ident);
358static void apt_dat_insert(airportdb_t *db, airport_t *arpt);
359static void free_airport(airport_t *arpt);
360
361static bool_t load_airport(airport_t *arpt);
362static void load_rwy_info(runway_t *rwy);
363
364static arpt_index_t *create_arpt_index(airportdb_t *db, const airport_t *arpt);
365
366static void
367recreate_icao_iata_tables(airportdb_t *db, unsigned cap)
368{
369 ASSERT(db != NULL);
370
371 htbl2_empty(&db->icao_index, sizeof (arpt_index_t), NULL, NULL);
372 htbl2_destroy(&db->icao_index);
373 htbl2_empty(&db->iata_index, sizeof (arpt_index_t), NULL, NULL);
374 htbl2_destroy(&db->iata_index);
375
376 htbl2_create(&db->icao_index, MAX(P2ROUNDUP(cap), 16),
377 AIRPORTDB_ICAO_LEN, sizeof (arpt_index_t), B_TRUE);
378 htbl2_create(&db->iata_index, MAX(P2ROUNDUP(cap), 16),
379 AIRPORTDB_IATA_LEN, sizeof (arpt_index_t), B_TRUE);
380}
381
382/*
383 * Given an arbitrary geographical position, returns the geo_table tile
384 * coordinate which the input position corresponds to. If div_by_10 is
385 * true, the coordinate is not in whole 1-degree resolution, but in 10-degree
386 * resolution. This is used in the data cache to select the subdirectory.
387 */
388static geo_pos2_t
389geo_pos2tile_pos(geo_pos2_t pos, bool_t div_by_10)
390{
391 if (div_by_10)
392 return (GEO_POS2(floor(pos.lat / 10) * 10,
393 floor(pos.lon / 10) * 10));
394 else
395 return (GEO_POS2(floor(pos.lat), floor(pos.lon)));
396}
397
398/*
399 * AVL tree comparator for airports based on their unique ident code.
400 */
401static int
402airport_compar(const void *a, const void *b)
403{
404 const airport_t *aa = a, *ab = b;
405 int res = strcmp(aa->ident, ab->ident);
406 if (res < 0)
407 return (-1);
408 if (res > 0)
409 return (1);
410 return (0);
411}
412
413/*
414 * AVL tree comparator for tile_t's based on latitude and longitude.
415 */
416static int
417tile_compar(const void *a, const void *b)
418{
419 const tile_t *ta = a, *tb = b;
420
421 if (ta->pos.lat < tb->pos.lat) {
422 return (-1);
423 } else if (ta->pos.lat == tb->pos.lat) {
424 if (ta->pos.lon < tb->pos.lon)
425 return (-1);
426 else if (ta->pos.lon == tb->pos.lon)
427 return (0);
428 else
429 return (1);
430 } else {
431 return (1);
432 }
433}
434
435/*
436 * AVL tree comparator for runway_t's based on the joint runway ID.
437 */
438static int
439runway_compar(const void *a, const void *b)
440{
441 const runway_t *ra = a, *rb = b;
442 int res = strcmp(ra->joint_id, rb->joint_id);
443 /* check to match runway ID reversals */
444 if (res != 0 && strcmp(ra->joint_id, rb->rev_joint_id) == 0)
445 return (0);
446 if (res < 0)
447 return (-1);
448 else if (res == 0)
449 return (0);
450 else
451 return (1);
452}
453
454static int
455ramp_start_compar(const void *a, const void *b)
456{
457 const ramp_start_t *rs_a = a, *rs_b = b;
458 int res = strcmp(rs_a->name, rs_b->name);
459 if (res < 0)
460 return (-1);
461 if (res > 0)
462 return (1);
463 return (0);
464}
465
466/*
467 * Retrieves the geo table tile which contains position `pos'. If create is
468 * B_TRUE, if the tile doesn't exit, it will be created.
469 * Returns the table tile (if it exists) and a boolean (in created_p if
470 * non-NULL) informing whether the table tile was created in this call
471 * (if create == B_TRUE).
472 */
473static tile_t *
474geo_table_get_tile(airportdb_t *db, geo_pos2_t pos, bool_t create,
475 bool_t *created_p)
476{
477 pos.lat = floor(pos.lat);
478 pos.lon = floor(pos.lon);
479
480 bool_t created = B_FALSE;
481 tile_t srch = { .pos = pos };
482 tile_t *tile;
483 avl_index_t where;
484
485 ASSERT(db != NULL);
486 ASSERT(!IS_NULL_GEO_POS(pos));
487
488 tile = avl_find(&db->geo_table, &srch, &where);
489 if (tile == NULL && create) {
490 tile = safe_malloc(sizeof (*tile));
491 tile->pos = pos;
492 avl_create(&tile->arpts, airport_compar, sizeof (airport_t),
493 offsetof(airport_t, tile_node));
494 avl_insert(&db->geo_table, tile, where);
495 created = B_TRUE;
496 }
497 if (created_p != NULL)
498 *created_p = created;
499
500 return (tile);
501}
502
503/*
504 * Given a runway threshold vector, direction vector, width, length and
505 * threshold longitudinal displacement, prepares a bounding box which
506 * encompasses that runway.
507 */
508static vect2_t *
509make_rwy_bbox(vect2_t thresh_v, vect2_t dir_v, double width, double len,
510 double long_displ)
511{
512 vect2_t *bbox;
513 vect2_t len_displ_v;
514
515 ASSERT(!IS_NULL_VECT(thresh_v));
516 ASSERT(!IS_NULL_VECT(dir_v));
517 ASSERT(!isnan(width));
518 ASSERT(!isnan(len));
519 ASSERT(!isnan(long_displ));
520
521 bbox = safe_malloc(sizeof (*bbox) * 5);
522
523 /*
524 * Displace the 'a' point from the runway threshold laterally
525 * by 1/2 width to the right.
526 */
527 bbox[0] = vect2_add(thresh_v, vect2_set_abs(vect2_norm(dir_v, B_TRUE),
528 width / 2));
529 /* pull it back by `long_displ' */
530 bbox[0] = vect2_add(bbox[0], vect2_set_abs(vect2_neg(dir_v),
531 long_displ));
532
533 /* do the same for the `d' point, but displace to the left */
534 bbox[3] = vect2_add(thresh_v, vect2_set_abs(vect2_norm(dir_v, B_FALSE),
535 width / 2));
536 /* pull it back by `long_displ' */
537 bbox[3] = vect2_add(bbox[3], vect2_set_abs(vect2_neg(dir_v),
538 long_displ));
539
540 /*
541 * points `b' and `c' are along the runway simply as runway len +
542 * long_displ
543 */
544 len_displ_v = vect2_set_abs(dir_v, len + long_displ);
545 bbox[1] = vect2_add(bbox[0], len_displ_v);
546 bbox[2] = vect2_add(bbox[3], len_displ_v);
547
548 bbox[4] = NULL_VECT2;
549
550 return (bbox);
551}
552
553/*
554 * Checks if the numerical runway type `t' is a hard-surface runway.
555 */
556static bool_t
557rwy_is_hard(rwy_surf_t surf)
558{
559 /*
560 * We used to only check for a few surface types here, but XP12
561 * added a bunch more undocumented hard surface types, so in
562 * anticipation of further surface types being added, we'll instead
563 * explicitly check for the known soft surface types first.
564 */
565 switch (surf) {
566 case RWY_SURF_GRASS:
567 case RWY_SURF_DIRT:
568 case RWY_SURF_GRAVEL:
569 case RWY_SURF_DRY_LAKEBED:
570 case RWY_SURF_WATER:
571 case RWY_SURF_SNOWICE:
572 return (false);
573 default:
574 return (true);
575 }
576}
577
578/*
579 * Performs a lookup for an airport based on ICAO code in an airportdb_t.
580 * The lookup is case-insensitive, because some data providers sometimes
581 * provided ICAO identifiers in lowercase.
582 */
583static airport_t *
584apt_dat_lookup(airportdb_t *db, const char *ident)
585{
586 airport_t search, *result;
587
588 ASSERT(db != NULL);
589 ASSERT(ident != NULL);
590
591 lacf_strlcpy(search.ident, ident, sizeof (search.ident));
592 strtoupper(search.ident);
593 result = avl_find(&db->apt_dat, &search, NULL);
594 if (result != NULL)
595 load_airport(result);
596
597 return (result);
598}
599
600static void
601apt_dat_insert(airportdb_t *db, airport_t *arpt)
602{
603 avl_index_t where;
604 ASSERT(db != NULL);
605 ASSERT(arpt != NULL);
606 VERIFY(avl_find(&db->apt_dat, arpt, &where) == NULL);
607 avl_insert(&db->apt_dat, arpt, where);
608}
609
610/*
611 * Links an airport into the geo-tile cache. The airport must not have been
612 * geo-linked before. While an airport is geo-linked, its refpt must not be
613 * modified.
614 */
615static void
616geo_link_airport(airportdb_t *db, airport_t *arpt)
617{
618 tile_t *tile;
619 avl_index_t where;
620
621 ASSERT(db != NULL);
622 ASSERT(arpt != NULL);
623
624 tile = geo_table_get_tile(db, GEO3_TO_GEO2(arpt->refpt), B_TRUE, NULL);
625 ASSERT(!arpt->geo_linked);
626 VERIFY(avl_find(&tile->arpts, arpt, &where) == NULL);
627 avl_insert(&tile->arpts, arpt, where);
628 arpt->geo_linked = B_TRUE;
629}
630
631/*
632 * Unlinks an airport from the geo-tile cache. The airport must have been
633 * geo-linked before. After geo-unlinking, the airport's refpt may be modified.
634 */
635static void
636geo_unlink_airport(airportdb_t *db, airport_t *arpt)
637{
638 tile_t *tile;
639
640 ASSERT(arpt != NULL);
641 ASSERT(arpt->geo_linked);
642 tile = geo_table_get_tile(db, GEO3_TO_GEO2(arpt->refpt), B_TRUE, NULL);
643 ASSERT(avl_find(&tile->arpts, arpt, NULL) == arpt);
644 avl_remove(&tile->arpts, arpt);
645 arpt->geo_linked = B_FALSE;
646}
647
648/*
649 * Some airports appear in apt.dat files, but not in the Airports.txt, but
650 * apt.dat doesn't tell us their airport reference point. Thus we do the
651 * next best thing and auto-compute the lat/lon as the arithmetic mean of
652 * the lat/lon of the first runway's thresholds.
653 */
654static void
655airport_auto_refpt(airport_t *arpt)
656{
657 runway_t *rwy;
658 geo_pos3_t p1, p2;
659
660 ASSERT(arpt != NULL);
661
662 rwy = avl_first(&arpt->rwys);
663 ASSERT(isnan(arpt->refpt.lat) && isnan(arpt->refpt.lon));
664 ASSERT(!arpt->load_complete);
665 ASSERT(!arpt->geo_linked);
666 ASSERT(!isnan(arpt->refpt.elev));
667 ASSERT(rwy != NULL);
668
669 p1 = rwy->ends[0].thr;
670 p2 = rwy->ends[1].thr;
671 /* Just to make sure there are no airports on the date line. */
672 ASSERT(fabs(p1.lon - p2.lon) < 90);
673 arpt->refpt.lat = (p1.lat + p2.lat) / 2;
674 arpt->refpt.lon = (p1.lon + p2.lon) / 2;
675 arpt->refpt_m.lat = arpt->refpt.lat;
676 arpt->refpt_m.lon = arpt->refpt.lon;
677 ASSERT(is_valid_lat(arpt->refpt.lat) && is_valid_lon(arpt->refpt.lon));
678}
679
680static char *
681apt_dat_cache_dir(const airportdb_t *db, geo_pos2_t pos, const char *suffix)
682{
683 char lat_lon[16];
684
685 ASSERT(db != NULL);
686 ASSERT(!IS_NULL_GEO_POS(pos));
687
688 pos = geo_pos2tile_pos(pos, B_TRUE);
689 snprintf(lat_lon, sizeof (lat_lon), TILE_NAME_FMT, pos.lat, pos.lon);
690
691 if (suffix != NULL)
692 return (mkpathname(db->cachedir, lat_lon, suffix, NULL));
693 else
694 return (mkpathname(db->cachedir, lat_lon, NULL));
695}
696
697/*
698 * Locates all apt.dat files used by X-Plane to display scenery. It consults
699 * scenery_packs.ini to determine which scenery packs are currently enabled
700 * and together with the default apt.dat returns them in a list sorted
701 * numerically in preference order (lowest index for highest priority).
702 * If the as_keys argument is true, the returned list is instead indexed
703 * by the apt.dat file name and the values are the preference order of that
704 * apt.dat (starting from 1 for highest priority and increasing with lowering
705 * priority).
706 * The apt.dat filenames are full filesystem paths.
707 */
708static void
709find_all_apt_dats(const airportdb_t *db, list_t *list)
710{
711 char *fname;
712 FILE *scenery_packs_ini;
714
715 ASSERT(db != NULL);
716 ASSERT(list != NULL);
717
718 fname = mkpathname(db->xpdir, "Custom Scenery", "scenery_packs.ini",
719 NULL);
720 scenery_packs_ini = fopen(fname, "r");
721 free(fname);
722 fname = NULL;
723
724 if (scenery_packs_ini != NULL) {
725 char *line = NULL;
726 size_t linecap = 0;
727
728 while (!feof(scenery_packs_ini)) {
729 char *scn_name;
730
731 if (getline(&line, &linecap, scenery_packs_ini) <= 0)
732 continue;
733 strip_space(line);
734 if (strstr(line, "SCENERY_PACK ") != line)
735 continue;
736 scn_name = &line[13];
737 strip_space(scn_name);
738 fix_pathsep(scn_name);
739 e = safe_malloc(sizeof (*e));
740 e->fname = mkpathname(db->xpdir, scn_name,
741 "Earth nav data", "apt.dat", NULL);
743 }
744 fclose(scenery_packs_ini);
745 free(line);
746 }
747 e = safe_malloc(sizeof (*e));
748 /* append the default apt.dat in XP11 */
749 e->fname = mkpathname(db->xpdir, "Resources", "default scenery",
750 "default apt dat", "Earth nav data", "apt.dat", NULL);
751 if (!file_exists(e->fname, NULL)) {
752 lacf_free(e->fname);
753 /* Try the default apt.dat in XP12 */
754 e->fname = mkpathname(db->xpdir, "Global Scenery",
755 "Global Airports", "Earth nav data", "apt.dat", NULL);
756 }
758}
759
760/*
761 * This actually performs the final insertion of an airport into the database.
762 * It inserts it into the flat apt_dat and into the geo_table.
763 */
764static void
765read_apt_dat_insert(airportdb_t *db, airport_t *arpt)
766{
767 ASSERT(db != NULL);
768
769 if (arpt == NULL)
770 return;
771 if (avl_numnodes(&arpt->rwys) != 0) {
772 ASSERT(!isnan(arpt->refpt.lat) && !isnan(arpt->refpt.lon));
773 apt_dat_insert(db, arpt);
774 geo_link_airport(db, arpt);
775 } else {
776 free_airport(arpt);
777 }
778}
779
780static void
781normalize_name(iconv_t *cd_p, char *str_in, char *str_out, size_t cap)
782{
783 ASSERT(cd_p != NULL);
784 ASSERT(str_in != NULL);
785 ASSERT(str_out != NULL);
786
787 unsigned l = strlen(str_in) + 1;
788 char *str_conv = safe_calloc(l, sizeof (*str_conv));
789 char *conv_in = str_in, *conv_out = str_conv;
790 size_t conv_in_sz = strlen(str_in);
791 size_t conv_out_sz = l;
792
793 iconv(*cd_p, &conv_in, &conv_in_sz, &conv_out, &conv_out_sz);
794 for (size_t i = 0, j = 0; str_conv[i] != '\0' && j + 1 < cap; i++) {
795 if (str_conv[i] != '\'' && str_conv[i] != '`' &&
796 str_conv[i] != '^' && str_conv[i] != '\\' &&
797 str_conv[i] != '"') {
798 str_out[j++] = str_conv[i];
799 }
800 }
801 free(str_conv);
802}
803
804static char *
805concat_comps(char **comps, size_t count)
806{
807 char *str = NULL;
808 size_t cap = 0;
809
810 ASSERT(comps != NULL);
811
812 for (size_t i = 0; i < count; i++) {
813 strip_space(comps[i]);
814 append_format(&str, &cap, "%s%s",
815 comps[i], i + 1 < count ? " " : "");
816 }
817 return (str);
818}
819
820/*
821 * Parses an airport line in apt.dat. The default apt.dat spec only supplies
822 * the identifier and field elevation on this line. Our extended format which
823 * we use in the data cache also adds the TA, TL and reference point LAT &
824 * LON to this. If the apt.dat being parsed is a standard (non-extended) one,
825 * the additional info is inferred later on from other sources during the
826 * airport data cache creation process.
827 */
828static airport_t *
829parse_apt_dat_1_line(airportdb_t *db, const char *line, iconv_t *cd_p,
830 airport_t **dup_arpt_p)
831{
832 /*
833 * pre-allocate the buffer to be large enough that most names are
834 * already gonna fit in there without too much reallocation.
835 */
836 char *name = NULL;
837 const char *new_ident;
839 size_t ncomps;
840 char **comps = strsplit(line, " ", B_TRUE, &ncomps);
841 airport_t *arpt = NULL;
842
843 ASSERT(db != NULL);
844 ASSERT(line != NULL);
845
846 if (dup_arpt_p != NULL)
847 *dup_arpt_p = NULL;
848
849 ASSERT(strcmp(comps[0], "1") == 0);
850 if (ncomps < 5)
851 goto out;
852
853 new_ident = comps[4];
854 pos.elev = atof(comps[1]);
855 if (!is_valid_elev(pos.elev))
856 /* Small GA fields might not have valid identifiers. */
857 goto out;
858 name = concat_comps(&comps[5], ncomps - 5);
859 if (name == NULL)
860 name = safe_strdup("");
861 arpt = apt_dat_lookup(db, new_ident);
862 if (arpt != NULL) {
863 /*
864 * This airport was already known from a previously loaded
865 * apt.dat. Avoid overwriting its data.
866 */
867 if (dup_arpt_p != NULL)
868 *dup_arpt_p = arpt;
869 arpt = NULL;
870 goto out;
871 }
872 arpt = safe_calloc(1, sizeof (*arpt));
873 avl_create(&arpt->rwys, runway_compar, sizeof (runway_t),
874 offsetof(runway_t, node));
875 list_create(&arpt->freqs, sizeof (freq_info_t),
876 offsetof(freq_info_t, node));
877 lacf_strlcpy(arpt->ident, new_ident, sizeof (arpt->ident));
878 strtoupper(arpt->ident);
879 /*
880 * Legacy scenery doesn't include '1302' metainfo lines with
881 * the ICAO code listed separately, so for those we just assume
882 * that the code listed in the ident here is the ICAO code.
883 */
884 strlcpy(arpt->icao, arpt->ident, sizeof (arpt->icao));
885 const char *cc = extract_icao_country_code(arpt->icao);
886 if (cc != NULL) {
887 lacf_strlcpy(arpt->cc, cc, sizeof (arpt->cc));
888 } else {
889 lacf_strlcpy(arpt->cc, "ZZ", sizeof (arpt->cc));
890 }
891
892 avl_create(&arpt->ramp_starts, ramp_start_compar,
893 sizeof (ramp_start_t), offsetof(ramp_start_t, node));
894
895 /*
896 * Unfortunately, X-Plane's scenery authors put all kinds of
897 * weird chars into their airport names. So we employ libiconv
898 * to hopefully transliterate that junk away as much as possible.
899 */
900 if (cd_p != NULL) {
901 LACF_DESTROY(arpt->name_orig);
902 arpt->name_orig = safe_strdup(name);
903 normalize_name(cd_p, name, arpt->name, sizeof (arpt->name));
904 strtoupper(arpt->name);
905 } else {
906 /*
907 * iconv is NOT used when reading our own apt.dat cache.
908 * So for those cases, we can just verbatim copy the airport
909 * name directly without charset issues.
910 */
911 lacf_strlcpy(arpt->name, name, sizeof (arpt->name));
912 }
913
914 arpt->refpt = pos;
915 arpt->refpt_m = GEO3_FT2M(pos);
916out:
917 free_strlist(comps, ncomps);
918 free(name);
919 return (arpt);
920}
921
922/*
923 * This is the matching function that attempts to determine if a VGSI
924 * (row code '21' in apt.dat) belongs to a specific runway. Returns the
925 * lateral displacement (in meters) from the runway centerline if the
926 * VGSI matches the runway or a huge number (1e10) otherwise.
927 */
928static double
929runway_vgsi_fuzzy_match(runway_t *rwy, int end, vgsi_t type, vect2_t pos_v,
930 double true_hdg)
931{
932 runway_end_t *re = &rwy->ends[end], *ore = &rwy->ends[!end];
933 vect2_t thr2light_v = vect2_sub(pos_v, re->thr_v);
934 vect2_t thr2thr_v = vect2_sub(ore->thr_v, re->thr_v);
935 vect2_t thr2thr_uv = vect2_unit(thr2thr_v, NULL);
936 vect2_t thr2thr_norm_uv = vect2_norm(thr2thr_uv, B_TRUE);
937 double lat_displ = vect2_dotprod(thr2light_v, thr2thr_norm_uv),
938 lon_displ = vect2_dotprod(thr2light_v, thr2thr_uv);
939
940 ASSERT(rwy != NULL);
941
942 /*
943 * The checks we perform are:
944 * 1) the lateral displacement from the runway centerline must be
945 * no more than 2x the runway width (VGSI_LAT_DISPL_FACT).
946 * 2) the longitudinal displacement must be sit between the thresholds
947 * 3) the true heading of the light fixture must be within 5 degrees
948 * of true runway heading (VGSI_HDG_MATCH_THRESH).
949 * 4) if the VGSI is a left PAPI, it must be on the left
950 * 5) if the VGSI is a right PAPI, it must be on the right
951 */
952 if (fabs(lat_displ) > VGSI_LAT_DISPL_FACT * rwy->width ||
953 lon_displ < 0 || lon_displ > rwy->length ||
954 fabs(rel_hdg(re->hdg, true_hdg)) > VGSI_HDG_MATCH_THRESH ||
955 (lat_displ > 0 && type == VGSI_PAPI_4L) ||
956 (lat_displ < 0 && type == VGSI_PAPI_4R))
957 return (1e10);
958 return (lat_displ);
959}
960
961static void
962find_nearest_runway_to_vgsi(airport_t *arpt, vgsi_t type, vect2_t pos_v,
963 double true_hdg, runway_t **rwy, runway_end_t **re, runway_end_t **ore)
964{
965 double max_displ = 100000;
966
967 ASSERT(arpt != NULL);
968 ASSERT(rwy != NULL);
969 ASSERT(re != NULL);
970 ASSERT(ore != NULL);
971 /*
972 * Runway unknown. Let's try to do a more fuzzy search.
973 * We will look for the closest runway from which we are
974 * displaced no more than 2x the runway's width. We also
975 * check that the sense of the displacement is kept (left
976 * PAPI on the left side of the runway and vice versa).
977 */
978 for (runway_t *crwy = avl_first(&arpt->rwys); crwy != NULL;
979 crwy = AVL_NEXT(&arpt->rwys, crwy)) {
980 double displ;
981 if ((displ = runway_vgsi_fuzzy_match(crwy, 0,
982 type, pos_v, true_hdg)) < max_displ) {
983 *rwy = crwy;
984 *re = &crwy->ends[0];
985 *ore = &crwy->ends[1];
986 max_displ = displ;
987 } else if ((displ = runway_vgsi_fuzzy_match(crwy, 1,
988 type, pos_v, true_hdg)) < max_displ) {
989 *rwy = crwy;
990 *re = &crwy->ends[1];
991 *ore = &crwy->ends[0];
992 max_displ = displ;
993 }
994 }
995}
996
997/*
998 * Row codes `21' denote lighting objects. We detect if the object is a
999 * PAPI or VASI and use it to compute the GPA and TCH.
1000 */
1001static void
1002parse_apt_dat_21_line(airport_t *arpt, const char *line)
1003{
1004 char **comps;
1005 size_t ncomps;
1006 vgsi_t type;
1007 geo_pos2_t pos;
1008 double gpa, tch, displ, true_hdg;
1009 const char *rwy_id;
1010 runway_t *rwy = NULL;
1011 runway_end_t *re = NULL, *ore = NULL;
1012 vect2_t pos_v, thr2light_v, thr2thr_v;
1013
1014 ASSERT(arpt != NULL);
1015 ASSERT(line != NULL);
1016
1017 /* Construct the airport fpp to compute the thresholds */
1018 if (!load_airport(arpt))
1019 return;
1020
1021 comps = strsplit(line, " ", B_TRUE, &ncomps);
1022 ASSERT(strcmp(comps[0], "21") == 0);
1023 if (ncomps < 7)
1024 /* No need to report, sometimes the rwy_ID is missing. */
1025 goto out;
1026 type = atoi(comps[3]);
1027 if (type < VGSI_VASI || type > VGSI_PAPI_3C || type == VGSI_PAPI_20DEG)
1028 goto out;
1029 pos = GEO_POS2(atof(comps[1]), atof(comps[2]));
1030 pos_v = geo2fpp(pos, &arpt->fpp);
1031 true_hdg = atof(comps[4]);
1032 if (!is_valid_hdg(true_hdg))
1033 goto out;
1034 gpa = atof(comps[5]);
1035 if (isnan(gpa) || gpa <= 0.0 || gpa > RWY_GPA_LIMIT)
1036 goto out;
1037 rwy_id = comps[6];
1038
1039 /*
1040 * Locate the associated runway. The VGSI line should denote which
1041 * runway it belongs to.
1042 */
1043 for (rwy = avl_first(&arpt->rwys); rwy != NULL;
1044 rwy = AVL_NEXT(&arpt->rwys, rwy)) {
1045 if (strcmp(rwy->ends[0].id, rwy_id) == 0) {
1046 re = &rwy->ends[0];
1047 ore = &rwy->ends[1];
1048 break;
1049 } else if (strcmp(rwy->ends[1].id, rwy_id) == 0) {
1050 ore = &rwy->ends[0];
1051 re = &rwy->ends[1];
1052 break;
1053 }
1054 }
1055 if (rwy == NULL) {
1056 find_nearest_runway_to_vgsi(arpt, type, pos_v, true_hdg,
1057 &rwy, &re, &ore);
1058 if (rwy == NULL)
1059 goto out;
1060 }
1061 /*
1062 * We can compute the longitudinal displacement along the associated
1063 * runway of the light from the runway threshold.
1064 */
1065 thr2light_v = vect2_sub(pos_v, re->thr_v);
1066 thr2thr_v = vect2_sub(ore->thr_v, re->thr_v);
1067 displ = vect2_dotprod(thr2light_v, vect2_unit(thr2thr_v, NULL));
1068 /*
1069 * Check that the VGSI sits somewhere between the two thresholds
1070 * and that it's aligned properly. Some scenery is broken like that!
1071 * This condition will only fail if we didn't use the matching in
1072 * find_nearest_runway_to_vgsi, because that function already
1073 * perform these checks.
1074 */
1075 if (displ < 0 || displ > rwy->length ||
1076 fabs(rel_hdg(true_hdg, re->hdg)) > VGSI_HDG_MATCH_THRESH) {
1077 rwy = NULL;
1078 re = NULL;
1079 ore = NULL;
1080 /* Fallback check - try to match it to ANY runway */
1081 find_nearest_runway_to_vgsi(arpt, type, pos_v, true_hdg,
1082 &rwy, &re, &ore);
1083 if (rwy == NULL)
1084 goto out;
1085 thr2light_v = vect2_sub(pos_v, re->thr_v);
1086 thr2thr_v = vect2_sub(ore->thr_v, re->thr_v);
1087 displ = vect2_dotprod(thr2light_v, vect2_unit(thr2thr_v,
1088 NULL));
1089 }
1090 /* Finally, given the displacement and GPA, compute the TCH. */
1091 tch = MET2FEET(sin(DEG2RAD(gpa)) * displ);
1092 ASSERT(tch >= 0.0);
1093 if (TCH_IS_VALID(tch)) {
1094 re->gpa = gpa;
1095 re->tch = tch;
1096 }
1097out:
1098 free_strlist(comps, ncomps);
1099}
1100
1101/*
1102 * Validates the data parsed from an apt.dat for a runway end:
1103 * 1) it has a valid runway identifier
1104 * 2) it has a valid threshold lat x lon
1105 * 3) its latitude is within our latitude limits
1106 * 4) it has a valid elevation (or no known elevation)
1107 * 5) it has a non-negative threshold displacement value
1108 * 6) it has a non-negative blastpath length value
1109 * 7) it has a valid (or zero) glidepath angle
1110 * 8) it has a valid (or zero) threshold clearing height
1111 */
1112static bool_t
1113validate_rwy_end(const runway_end_t *re, char error_descr[128])
1114{
1115 ASSERT(re != NULL);
1116 ASSERT(error_descr != NULL);
1117#define VALIDATE(cond, ...) \
1118 do { \
1119 if (!(cond)) { \
1120 snprintf(error_descr, 128, __VA_ARGS__); \
1121 return (B_FALSE); \
1122 } \
1123 } while (0)
1124 VALIDATE(is_valid_rwy_ID(re->id), "Runway ID \"%s\" invalid", re->id);
1125 VALIDATE(is_valid_lat(re->thr.lat), "Latitude \"%g\" is invalid",
1126 re->thr.lat);
1127 VALIDATE(is_valid_lon(re->thr.lon), "Longitude \"%g\" is invalid",
1128 re->thr.lon);
1129 VALIDATE(isnan(re->thr.elev) || is_valid_elev(re->thr.elev),
1130 "Threshold elevation \"%g\" is invalid", re->thr.elev);
1131 VALIDATE(re->displ >= 0.0, "Displacement \"%g\" is invalid", re->displ);
1132 VALIDATE(re->blast >= 0.0, "Blastpad \"%g\" is invalid", re->displ);
1133 VALIDATE(re->gpa >= 0.0 && re->gpa < RWY_GPA_LIMIT,
1134 "GPA \"%g\" is invalid", re->gpa);
1135 VALIDATE(re->tch >= 0.0 && re->tch < RWY_TCH_LIMIT,
1136 "TCH \"%g\" is invalid", re->tch);
1137#undef VALIDATE
1138 return (B_TRUE);
1139}
1140
1141static void
1142parse_apt_dat_freq_line(airport_t *arpt, char *line, bool_t use833)
1143{
1144 char **comps;
1145 size_t ncomps;
1146 freq_info_t *freq;
1147
1148 ASSERT(arpt != NULL);
1149 ASSERT(line != NULL);
1150 /*
1151 * Remove spurious underscores and dashes that some sceneries insist
1152 * on using in frequency names. Do this before the strsplit pass, so
1153 * we subdivide at those boundaries.
1154 */
1155 for (size_t i = 0, n = strlen(line); i < n; i++) {
1156 if (line[i] == '_' || line[i] == '-')
1157 line[i] = ' ';
1158 }
1159
1160 comps = strsplit(line, " ", B_TRUE, &ncomps);
1161 if (ncomps < 3)
1162 goto out;
1163 freq = safe_calloc(1, sizeof (*freq));
1164 /*
1165 * When `use833' is provided, the line types start at 1050 instead
1166 * of 50. Also, the frequencies are specified in thousands of Hertz,
1167 * not tens of thousands.
1168 */
1169 freq->type = atoi(comps[0]) - (use833 ? 1050 : 50);
1170 freq->freq = atoll(comps[1]) * (use833 ? 1000 : 10000);
1171 for (size_t i = 2; i < ncomps; i++) {
1172 strtoupper(comps[i]);
1173 /*
1174 * Some poorly written apt.dats include the airport identifier
1175 * in the frequency name (e.g. "LZIB ATIS" for the ATIS
1176 * frequency at airport LZIB). This is redundant and just
1177 * wastes space, so remove that. And some even more stupidly
1178 * contain the word "frequency" - DOH!
1179 */
1180 if ((strcmp(comps[i], arpt->icao) == 0 ||
1181 strcmp(comps[i], "FREQUENCY") == 0) && ncomps > 3) {
1182 continue;
1183 }
1184 if (freq->name[0] != '\0') {
1185 strncat(&freq->name[strlen(freq->name)], " ",
1186 sizeof (freq->name) - strlen(freq->name) - 1);
1187 }
1188 strncat(&freq->name[strlen(freq->name)], comps[i],
1189 sizeof (freq->name) - strlen(freq->name) - 1);
1190 }
1191 list_insert_tail(&arpt->freqs, freq);
1192
1193out:
1194 free_strlist(comps, ncomps);
1195}
1196
1197/*
1198 * Parses an apt.dat runway line. Standard apt.dat runway lines simply
1199 * denote the runway's surface type, width (in meters) the position of
1200 * each threshold (lateral only, no elevation) and displacement parameters.
1201 * Our data cache features three additional special fields: GPA, TCH and
1202 * elevation (in meters) of each end. When parsing a stock apt.dat, these
1203 * extra parameters are inferred from other sources in the data cache
1204 * creation process.
1205 */
1206static void
1207parse_apt_dat_100_line(airport_t *arpt, const char *line, bool_t hard_surf_only)
1208{
1209 char **comps;
1210 size_t ncomps;
1211 runway_t *rwy;
1212 avl_index_t where;
1213 char error_descr[128];
1214
1215 ASSERT(arpt != NULL);
1216 ASSERT(line != NULL);
1217
1218 comps = strsplit(line, " ", B_TRUE, &ncomps);
1219 ASSERT(strcmp(comps[0], "100") == 0);
1220 if (ncomps < 8 + 9 + 5 ||
1221 (hard_surf_only && !rwy_is_hard(atoi(comps[2]))))
1222 goto out;
1223
1224 rwy = safe_calloc(1, sizeof (*rwy));
1225
1226 rwy->arpt = arpt;
1227 rwy->width = atof(comps[1]);
1228 rwy->surf = atoi(comps[2]);
1229
1230 copy_rwy_ID(comps[8 + 0], rwy->ends[0].id);
1231 rwy->ends[0].thr = GEO_POS3(atof(comps[8 + 1]), atof(comps[8 + 2]),
1232 arpt->refpt.elev);
1233 rwy->ends[0].thr_m = GEO3_FT2M(rwy->ends[0].thr);
1234 rwy->ends[0].displ = atof(comps[8 + 3]);
1235 rwy->ends[0].blast = atof(comps[8 + 4]);
1236
1237 copy_rwy_ID(comps[8 + 9 + 0], rwy->ends[1].id);
1238 rwy->ends[1].thr = GEO_POS3(atof(comps[8 + 9 + 1]),
1239 atof(comps[8 + 9 + 2]), arpt->refpt.elev);
1240 rwy->ends[1].thr_m = GEO3_FT2M(rwy->ends[1].thr);
1241 rwy->ends[1].displ = atof(comps[8 + 9 + 3]);
1242 rwy->ends[1].blast = atof(comps[8 + 9 + 4]);
1243
1244 /*
1245 * ARINC 424 says in field reference 5.67 that if no explicit TCH is
1246 * specified, 50 feet shall be assumed. The GPA cannot be assumed
1247 * this easily and unfortunately field 5.226 from ARINC 424 isn't in
1248 * X-Plane 11's navdata, so we instead parse it in a later step from
1249 * instrument approach procedures (X-Plane 11) or from an Airports.txt
1250 * (X-Plane 10), falling back to VGSI triangulation in the scenery if
1251 * those methods fail. We won't provide vertical approach monitoring
1252 * unless both GPA & TCH are non-zero.
1253 */
1254 rwy->ends[0].tch = 50;
1255 rwy->ends[1].tch = 50;
1256
1257 snprintf(rwy->joint_id, sizeof (rwy->joint_id), "%s%s",
1258 rwy->ends[0].id, rwy->ends[1].id);
1259 snprintf(rwy->rev_joint_id, sizeof (rwy->rev_joint_id), "%s%s",
1260 rwy->ends[1].id, rwy->ends[0].id);
1261
1262 /* Our extended data cache format */
1263 if (ncomps >= 28 && strstr(comps[22], "GPA1:") == comps[22] &&
1264 strstr(comps[23], "GPA2:") == comps[23] &&
1265 strstr(comps[24], "TCH1:") == comps[24] &&
1266 strstr(comps[25], "TCH2:") == comps[25] &&
1267 strstr(comps[26], "TELEV1:") == comps[26] &&
1268 strstr(comps[27], "TELEV2:") == comps[27]) {
1269 rwy->ends[0].gpa = atof(&comps[22][5]);
1270 rwy->ends[1].gpa = atof(&comps[23][5]);
1271 rwy->ends[0].tch = atof(&comps[24][5]);
1272 rwy->ends[1].tch = atof(&comps[25][5]);
1273 rwy->ends[0].thr.elev = atof(&comps[26][7]);
1274 rwy->ends[1].thr.elev = atof(&comps[27][7]);
1275 rwy->ends[0].thr_m.elev = FEET2MET(rwy->ends[0].thr.elev);
1276 rwy->ends[1].thr_m.elev = FEET2MET(rwy->ends[1].thr.elev);
1277 }
1278
1279 /* Validate the runway ends individually. */
1280 if (!validate_rwy_end(&rwy->ends[0], error_descr) ||
1281 !validate_rwy_end(&rwy->ends[1], error_descr)) {
1282 free(rwy);
1283 goto out;
1284 }
1285 /*
1286 * Are the runway ends sufficiently far apart? Protects against runways
1287 * with overlapping thresholds, which results in a NAN runway hdg.
1288 */
1289 if (vect3_dist(geo2ecef_ft(rwy->ends[0].thr, &wgs84),
1290 geo2ecef_ft(rwy->ends[1].thr, &wgs84)) < MIN_RWY_LEN) {
1291 free(rwy);
1292 goto out;
1293 }
1294 /* Duplicate runway present? */
1295 if (avl_find(&arpt->rwys, rwy, &where) != NULL) {
1296 free(rwy);
1297 goto out;
1298 }
1299 avl_insert(&arpt->rwys, rwy, where);
1300 if (arpt->load_complete) {
1301 /* do a supplemental runway info load */
1302 load_rwy_info(rwy);
1303 } else if (isnan(arpt->refpt.lat) || isnan(arpt->refpt.lon)) {
1304 arpt->refpt.lat = NAN;
1305 arpt->refpt.lon = NAN;
1306 arpt->refpt_m.lat = NAN;
1307 arpt->refpt_m.lon = NAN;
1308 airport_auto_refpt(arpt);
1309 }
1310out:
1311 free_strlist(comps, ncomps);
1312}
1313
1314static bool_t
1315is_normal_gate_name(const char *str)
1316{
1317 ASSERT(str != NULL);
1318 for (size_t i = 0, n = strlen(str); i < n; i++) {
1319 if ((str[i] < 'A' || str[i] > 'Z') &&
1320 (str[i] < '0' || str[i] > '9')) {
1321 return (B_FALSE);
1322 }
1323 }
1324 return (B_TRUE);
1325}
1326
1327static void
1328parse_apt_dat_1300_line(airport_t *arpt, const char *line,
1329 bool_t normalize_name)
1330{
1331 char **comps;
1332 size_t n_comps;
1333 ramp_start_t srch = {};
1334 ramp_start_t *rs = NULL;
1335 avl_index_t where;
1336
1337 ASSERT(arpt != NULL);
1338 ASSERT(line != NULL);
1339
1340 comps = strsplit(line, " ", B_TRUE, &n_comps);
1341 if (n_comps < 7)
1342 goto out;
1343 if (!normalize_name) {
1344 unsigned l = 0;
1345 for (size_t i = 6; i < n_comps; i++) {
1346 lacf_strlcpy(&srch.name[l], comps[i],
1347 sizeof (srch.name) - l);
1348 l = MIN(l + strlen(comps[i]), sizeof (srch.name) - 1);
1349 if (i + 1 < n_comps) {
1350 lacf_strlcpy(&srch.name[l], " ",
1351 sizeof (srch.name) - l);
1352 l = MIN(l + 1, sizeof (srch.name) - 1);
1353 }
1354 }
1355 } else {
1356 for (size_t i = 6; i < n_comps; i++) {
1357 if (is_normal_gate_name(comps[i])) {
1358 strlcpy(srch.name, comps[i],
1359 sizeof (srch.name));
1360 break;
1361 }
1362 }
1363 if (srch.name[0] == '\0')
1364 goto out;
1365 }
1366 rs = avl_find(&arpt->ramp_starts, &srch, &where);
1367 if (rs != NULL)
1368 goto out;
1369 rs = safe_calloc(1, sizeof (*rs));
1370 lacf_strlcpy(rs->name, srch.name, sizeof (rs->name));
1371 rs->pos = GEO_POS2(atof(comps[1]), atof(comps[2]));
1372 rs->hdgt = atof(comps[3]);
1373 if (!is_valid_lat(rs->pos.lat) || !is_valid_lon(rs->pos.lon) ||
1374 !is_valid_hdg(rs->hdgt)) {
1375 free(rs);
1376 goto out;
1377 }
1378 if (strcmp(comps[4], "gate") == 0)
1379 rs->type = RAMP_START_GATE;
1380 else if (strcmp(comps[4], "hangar") == 0)
1381 rs->type = RAMP_START_HANGAR;
1382 else if (strcmp(comps[4], "tie-down") == 0)
1383 rs->type = RAMP_START_TIEDOWN;
1384 else
1385 rs->type = RAMP_START_MISC;
1386
1387 avl_insert(&arpt->ramp_starts, rs, where);
1388out:
1389 free_strlist(comps, n_comps);
1390}
1391
1392static opt_float
1393extract_TA_TL_ft(char *const*comps, size_t n_comps)
1394{
1395 ASSERT(comps != NULL);
1396 ASSERT3U(n_comps, >=, 3);
1397 /*
1398 * This field comes extremely mangled in the apt.dat data.
1399 * It seems WED performs no input validation on this, so users
1400 * are allowed to throw just any old junk in there.
1401 */
1402 ASSERT(strlen(comps[2]) != 0);
1403 // sometimes there's a 'm' suffix as a separate word
1404 bool metric = false;
1405 if (n_comps >= 4) {
1406 ASSERT(strlen(comps[3]) != 0);
1407 metric = (tolower(comps[3][0]) == 'm');
1408 // sometimes the units are appended to the end of the number
1409 } else if (tolower(comps[2][strlen(comps[2]) - 1]) == 'm') {
1410 metric = true;
1411 }
1412 char *buf = NULL;
1413 bool is_fl = false;
1414 // sometimes they write 'FL 123', so read the next field
1415 if (lacf_strcasecmp(comps[2], "FL") == 0 && n_comps > 3) {
1416 buf = safe_strdup(comps[3]);
1417 is_fl = true;
1418 } else {
1419 buf = safe_strdup(comps[2]);
1420 }
1421 // strip a leading 'FL'
1422 if (lacf_strcasestr(buf, "FL") == buf) {
1423 memmove(buf, &buf[2], strlen(&buf[2]) + 1);
1424 is_fl = true;
1425 }
1426 // go through the buffer and drop any piece of non-numeric junk
1427 // in there, to hopefully reconstitute something parseable
1428 for (unsigned i = 0, n = strlen(buf); i < n; i++) {
1429 if (!isdigit(buf[i])) {
1430 memmove(&buf[i], &buf[i + 1], strlen(&buf[i + 1]) + 1);
1431 }
1432 }
1433 float alt = 0;
1434 if (sscanf(buf, "%f", &alt) == 1) {
1435 if (is_fl || (alt < 600 && !metric)) {
1436 alt *= 100;
1437 }
1438 // sometimes they write '21700 m' when they meant 21700 ft
1439 // in a metric country (China), so ignore the 'm' tag there
1440 if (metric && MET2FEET(alt) < 60000) {
1441 alt = MET2FEET(alt);
1442 }
1443 return (SOME(alt));
1444 } else {
1445 free(buf);
1446 return (NONE(float));
1447 }
1448}
1449
1450/*
1451 * Often times payware and custom airports lack a lot of the meta info
1452 * that stock X-Plane airports contain. Normally we want to skip re-parsing
1453 * stock airports in the presence of a custom one, however, we do want the
1454 * extra meta info out of the stock dataset. To that end, if we hit a
1455 * duplicate in the stock dataset, we try to use it fill in any precending
1456 * custom airport.
1457 */
1458static void
1459fill_dup_arpt_info(airport_t *arpt, const char *line, int row_code)
1460{
1461 ASSERT(arpt != NULL);
1462 ASSERT(line != NULL);
1463
1464 if (row_code == 1302) {
1465 size_t ncomps;
1466 char **comps = strsplit(line, " ", B_TRUE, &ncomps);
1467
1468 if (ncomps < 2) {
1469 free_strlist(comps, ncomps);
1470 return;
1471 }
1472
1473 if (strcmp(comps[1], "iata_code") == 0 && ncomps >= 3 &&
1474 is_valid_iata_code(comps[2]) &&
1475 !is_valid_iata_code(arpt->iata)) {
1476 lacf_strlcpy(arpt->iata, comps[2], sizeof (arpt->iata));
1477 } else if (strcmp(comps[1], "transition_alt") == 0 &&
1478 ncomps >= 3 && arpt->TA == 0) {
1479 IF_LET(float, TA_ft, extract_TA_TL_ft(comps, ncomps))
1480 arpt->TA = TA_ft;
1481 arpt->TA_m = FEET2MET(TA_ft);
1482 IF_LET_END
1483 } else if (strcmp(comps[1], "transition_level") == 0 &&
1484 ncomps >= 3 && arpt->TL == 0) {
1485 IF_LET(float, TL_ft, extract_TA_TL_ft(comps, ncomps))
1486 arpt->TL = TL_ft;
1487 arpt->TL_m = FEET2MET(TL_ft);
1488 IF_LET_END
1489 } else if (strcmp(comps[1], "region_code") == 0 &&
1490 ncomps >= 3 && strcmp(comps[2], "-") != 0) {
1491 lacf_strlcpy(arpt->cc, comps[2], sizeof (arpt->cc));
1492 } else if (strcmp(comps[1], "country") == 0 &&
1493 ncomps >= 3 && strcmp(comps[2], "-") != 0) {
1494 LACF_DESTROY(arpt->country);
1495 arpt->country = concat_comps(&comps[2], ncomps - 2);
1496 } else if (strcmp(comps[1], "city") == 0 &&
1497 ncomps >= 3 && strcmp(comps[2], "-") != 0) {
1498 LACF_DESTROY(arpt->city);
1499 arpt->city = concat_comps(&comps[2], ncomps - 2);
1500 }
1501 free_strlist(comps, ncomps);
1502 }
1503}
1504
1505static char *
1506iso3166_cc3_to_name(const char *cc3)
1507{
1508 ASSERT(cc3 != NULL);
1509
1510 for (size_t i = 0; i < ARRAY_NUM_ELEM(iso3166_codes); i++) {
1511 if (strcmp(cc3, iso3166_codes[i].code) == 0)
1512 return (safe_strdup(iso3166_codes[i].name));
1513 }
1514 return (NULL);
1515}
1516
1517static void
1518parse_attr_country(char **comps, size_t n_comps, int version, airport_t *arpt)
1519{
1520 ASSERT(comps != NULL);
1521 ASSERT(arpt != NULL);
1522
1523 LACF_DESTROY(arpt->country);
1524 arpt->cc3[0] = '\0';
1525
1526 if (n_comps == 0)
1527 return;
1528 if (version < 1200) {
1529 arpt->country = concat_comps(comps, n_comps);
1530 } else {
1531 if (strlen(comps[0]) == 3 && isupper(comps[0][0]) &&
1532 isupper(comps[0][1]) && isupper(comps[0][2])) {
1533 arpt->country = iso3166_cc3_to_name(comps[0]);
1534 }
1535 if (arpt->country == NULL)
1536 arpt->country = concat_comps(comps, n_comps);
1537 }
1538}
1539
1540/*
1541 * Parses an apt.dat (either from regular scenery or from CACHE_DIR) to
1542 * cache the airports contained in it.
1543 */
1544static void
1545read_apt_dat(airportdb_t *db, const char *apt_dat_fname, bool_t fail_ok,
1546 iconv_t *cd_p, bool_t fill_in_dups)
1547{
1548 FILE *apt_dat_f;
1549 airport_t *arpt = NULL, *dup_arpt = NULL;
1550 char *line = NULL;
1551 size_t linecap = 0;
1552 int line_num = 0, version = 0;
1553 char **comps;
1554 size_t ncomps;
1555
1556 ASSERT(db != NULL);
1557 ASSERT(apt_dat_fname != NULL);
1558
1559 apt_dat_f = fopen(apt_dat_fname, "r");
1560 if (apt_dat_f == NULL) {
1561 if (!fail_ok)
1562 logMsg("Can't open %s: %s", apt_dat_fname,
1563 strerror(errno));
1564 return;
1565 }
1566
1567 while (!feof(apt_dat_f)) {
1568 int row_code;
1569
1570 line_num++;
1571 if (getline(&line, &linecap, apt_dat_f) <= 0)
1572 continue;
1573 strip_space(line);
1574
1575 if (sscanf(line, "%d", &row_code) != 1)
1576 continue;
1577 /* Read the version header */
1578 if (line_num == 2) {
1579 version = row_code;
1580 continue;
1581 }
1582 /*
1583 * Finish the current airport on an empty line or a new
1584 * airport line.
1585 */
1586 if (strlen(line) == 0 || row_code == 1 || row_code == 16 ||
1587 row_code == 17) {
1588 if (arpt != NULL)
1589 read_apt_dat_insert(db, arpt);
1590 arpt = NULL;
1591 dup_arpt = NULL;
1592 }
1593 if (row_code == 1) {
1594 arpt = parse_apt_dat_1_line(db, line, cd_p,
1595 fill_in_dups ? &dup_arpt : NULL);
1596 }
1597 if (arpt == NULL) {
1598 if (dup_arpt != NULL)
1599 fill_dup_arpt_info(dup_arpt, line, row_code);
1600 continue;
1601 }
1602
1603 switch (row_code) {
1604 case 21:
1605 parse_apt_dat_21_line(arpt, line);
1606 break;
1607 case 50 ... 56:
1608 parse_apt_dat_freq_line(arpt, line, B_FALSE);
1609 break;
1610 case 100:
1611 parse_apt_dat_100_line(arpt, line, db->ifr_only);
1612 break;
1613 case 1050 ... 1056:
1614 parse_apt_dat_freq_line(arpt, line, B_TRUE);
1615 break;
1616 case 1300:
1617 parse_apt_dat_1300_line(arpt, line,
1618 db->normalize_gate_names);
1619 break;
1620 case 1302:
1621 comps = strsplit(line, " ", B_TRUE, &ncomps);
1622 /*
1623 * '1302' lines are meta-info lines introduced since
1624 * X-Plane 11. This line can contain varying numbers
1625 * of components, but we only care when it's 3.
1626 */
1627 if (ncomps < 3) {
1628 free_strlist(comps, ncomps);
1629 continue;
1630 }
1631 /* Necessary check prior to modifying the refpt. */
1632 ASSERT(!arpt->geo_linked);
1633 /*
1634 * X-Plane 11 introduced these to remove the need
1635 * for an Airports.txt.
1636 */
1637 if (strcmp(comps[1], "icao_code") == 0 &&
1638 is_valid_icao_code(comps[2])) {
1639 lacf_strlcpy(arpt->icao, comps[2],
1640 sizeof (arpt->icao));
1641 } else if (strcmp(comps[1], "iata_code") == 0 &&
1642 is_valid_iata_code(comps[2])) {
1643 lacf_strlcpy(arpt->iata, comps[2],
1644 sizeof (arpt->iata));
1645 } else if (strcmp(comps[1], "country") == 0) {
1646 parse_attr_country(&comps[2], ncomps - 2,
1647 version, arpt);
1648 } else if (strcmp(comps[1], "city") == 0) {
1649 LACF_DESTROY(arpt->city);
1650 arpt->city = concat_comps(&comps[2],
1651 ncomps - 2);
1652 } else if (strcmp(comps[1], "name_orig") == 0) {
1653 LACF_DESTROY(arpt->name_orig);
1654 arpt->name_orig = concat_comps(&comps[2],
1655 ncomps - 2);
1656 } else if (strcmp(comps[1], "transition_alt") == 0) {
1657 IF_LET(float, TA_ft, extract_TA_TL_ft(comps,
1658 ncomps))
1659 arpt->TA = TA_ft;
1660 arpt->TA_m = FEET2MET(TA_ft);
1661 IF_LET_END
1662 } else if (strcmp(comps[1], "transition_level") == 0) {
1663 IF_LET(float, TL_ft, extract_TA_TL_ft(comps,
1664 ncomps))
1665 arpt->TL = TL_ft;
1666 arpt->TL_m = FEET2MET(TL_ft);
1667 IF_LET_END
1668 } else if (strcmp(comps[1], "datum_lat") == 0) {
1669 double lat = atof(comps[2]);
1670 if (is_valid_lat(lat)) {
1671 arpt->refpt.lat = lat;
1672 arpt->refpt_m.lat = lat;
1673 } else {
1674 free_airport(arpt);
1675 arpt = NULL;
1676 }
1677 } else if (strcmp(comps[1], "datum_lon") == 0) {
1678 double lon = atof(comps[2]);
1679 if (is_valid_lon(lon)) {
1680 arpt->refpt.lon = lon;
1681 arpt->refpt_m.lon = lon;
1682 }
1683 } else if (strcmp(comps[1], "region_code") == 0 &&
1684 strcmp(comps[1], "-") != 0) {
1685 lacf_strlcpy(arpt->cc, comps[2],
1686 sizeof (arpt->cc));
1687 }
1688
1689 free_strlist(comps, ncomps);
1690 break;
1691 }
1692 }
1693
1694 if (arpt != NULL)
1695 read_apt_dat_insert(db, arpt);
1696
1697 free(line);
1698 fclose(apt_dat_f);
1699}
1700
1701static bool_t
1702write_apt_dat(const airportdb_t *db, const airport_t *arpt)
1703{
1704 char lat_lon[16];
1705 char *fname;
1706 FILE *fp;
1707 geo_pos2_t p;
1708 bool_t exists;
1709
1710 ASSERT(db != NULL);
1711 ASSERT(arpt != NULL);
1712
1713 p = geo_pos2tile_pos(GEO3_TO_GEO2(arpt->refpt), B_FALSE);
1714 snprintf(lat_lon, sizeof (lat_lon), TILE_NAME_FMT, p.lat, p.lon);
1715 fname = apt_dat_cache_dir(db, GEO3_TO_GEO2(arpt->refpt), lat_lon);
1716
1717 exists = file_exists(fname, NULL);
1718 fp = fopen(fname, "a");
1719 if (fp == NULL) {
1720 logMsg("Error writing file %s: %s", fname, strerror(errno));
1721 return (B_FALSE);
1722 }
1723 if (!exists) {
1724 fprintf(fp, "I\n"
1725 "1200 libacfutils airportdb version %d\n"
1726 "\n", ARPTDB_CACHE_VERSION);
1727 }
1728 ASSERT(!IS_NULL_GEO_POS(arpt->refpt));
1729
1730 fprintf(fp, "1 %.0f 0 0 %s %s\n"
1731 "1302 datum_lat %f\n"
1732 "1302 datum_lon %f\n",
1733 arpt->refpt.elev, arpt->ident, arpt->name, arpt->refpt.lat,
1734 arpt->refpt.lon);
1735 if (arpt->name_orig != NULL)
1736 fprintf(fp, "1302 name_orig %s\n", arpt->name_orig);
1737 if (arpt->icao[0] != '\0')
1738 fprintf(fp, "1302 icao_code %s\n", arpt->icao);
1739 if (arpt->iata[0] != '\0')
1740 fprintf(fp, "1302 iata_code %s\n", arpt->iata);
1741 if (arpt->country != NULL)
1742 fprintf(fp, "1302 country %s\n", arpt->country);
1743 if (arpt->city != NULL)
1744 fprintf(fp, "1302 city %s\n", arpt->city);
1745 if (arpt->TA != 0)
1746 fprintf(fp, "1302 transition_alt %.0f\n", arpt->TA);
1747 if (arpt->TL != 0)
1748 fprintf(fp, "1302 transition_level %.0f\n", arpt->TL);
1749 if (*arpt->cc != 0)
1750 fprintf(fp, "1302 region_code %s\n", arpt->cc);
1751 for (const runway_t *rwy = avl_first(&arpt->rwys); rwy != NULL;
1752 rwy = AVL_NEXT(&arpt->rwys, rwy)) {
1753 ASSERT(!isnan(rwy->ends[0].gpa));
1754 ASSERT(!isnan(rwy->ends[1].gpa));
1755 ASSERT(!isnan(rwy->ends[0].tch));
1756 ASSERT(!isnan(rwy->ends[1].tch));
1757 ASSERT(!isnan(rwy->ends[0].thr.elev));
1758 ASSERT(!isnan(rwy->ends[1].thr.elev));
1759 fprintf(fp, "100 %.2f %d 0 0 0 0 0 "
1760 "%s %f %f %.1f %.1f 0 0 0 0 "
1761 "%s %f %f %.1f %.1f "
1762 "GPA1:%.02f GPA2:%.02f TCH1:%.0f TCH2:%.0f "
1763 "TELEV1:%.0f TELEV2:%.0f\n",
1764 rwy->width, rwy->surf,
1765 rwy->ends[0].id, rwy->ends[0].thr.lat,
1766 rwy->ends[0].thr.lon, rwy->ends[0].displ,
1767 rwy->ends[0].blast,
1768 rwy->ends[1].id, rwy->ends[1].thr.lat,
1769 rwy->ends[1].thr.lon, rwy->ends[1].displ,
1770 rwy->ends[1].blast,
1771 rwy->ends[0].gpa, rwy->ends[1].gpa,
1772 rwy->ends[0].tch, rwy->ends[1].tch,
1773 rwy->ends[0].thr.elev, rwy->ends[1].thr.elev);
1774 }
1775 for (const ramp_start_t *rs = avl_first(&arpt->ramp_starts);
1776 rs != NULL; rs = AVL_NEXT(&arpt->ramp_starts, rs)) {
1777 static const char *type2name[] = {
1778 [RAMP_START_GATE] = "gate",
1779 [RAMP_START_HANGAR] = "hangar",
1780 [RAMP_START_TIEDOWN] = "tie-down",
1781 [RAMP_START_MISC] = "misc",
1782 };
1783 fprintf(fp, "1300 %f %f %.2f %s all %s\n",
1784 rs->pos.lat, rs->pos.lon, rs->hdgt,
1785 type2name[rs->type], rs->name);
1786 }
1787 for (const freq_info_t *freq = list_head(&arpt->freqs); freq != NULL;
1788 freq = list_next(&arpt->freqs, freq)) {
1789 /*
1790 * We always emit the frequency info using the new
1791 * 8.33kHz-aware row code format.
1792 */
1793 fprintf(fp, "%d %ld %s\n", freq->type + 1050,
1794 (unsigned long)floor(freq->freq / 1000), freq->name);
1795 }
1796 fprintf(fp, "\n");
1797 fclose(fp);
1798 free(fname);
1799
1800 return (B_TRUE);
1801}
1802
1803static bool_t
1804load_arinc424_arpt_data(const char *filename, airport_t *arpt)
1805{
1806 char *line = NULL;
1807 size_t linecap = 0;
1808 FILE *fp;
1809 int line_num = 0;
1810
1811 ASSERT(filename != NULL);
1812 ASSERT(arpt != NULL);
1813
1814 /* airport already seen in previous version of the database, skip */
1815 if (arpt->in_navdb)
1816 return (B_TRUE);
1817
1818 fp = fopen(filename, "r");
1819 if (fp == NULL) {
1820 logMsg("Can't open %s: %s", filename, strerror(errno));
1821 return (B_FALSE);
1822 }
1823
1824 arpt->in_navdb = B_TRUE;
1825
1826 while (!feof(fp)) {
1827 line_num++;
1828 if (getline(&line, &linecap, fp) <= 0)
1829 continue;
1830 if (strstr(line, "APPCH:") == line) {
1831 /*
1832 * Extract the runway TCH and GPA from instrument
1833 * approach lines.
1834 */
1835 char **comps;
1836 char rwy_id[4];
1837 size_t ncomps;
1838 runway_t *rwy;
1839 float gpa;
1840
1841 arpt->have_iaps = B_TRUE;
1842
1843 comps = strsplit(line + 6, ",", B_FALSE, &ncomps);
1844 if (strstr(comps[4], "RW") != comps[4] ||
1845 sscanf(comps[28], "%f", &gpa) != 1 ||
1846 gpa >= 0 || gpa < RWY_GPA_LIMIT * -100)
1847 goto out_appch;
1848 copy_rwy_ID(comps[4] + 2, rwy_id);
1849 /*
1850 * The database has this in 0.01 deg steps, stored
1851 * negative (i.e. "3.5 degrees" is "-350" in the DB).
1852 */
1853 gpa /= -100.0;
1854 for (rwy = avl_first(&arpt->rwys); rwy != NULL;
1855 rwy = AVL_NEXT(&arpt->rwys, rwy)) {
1856 runway_end_t *re;
1857
1858 if (strcmp(rwy->ends[0].id, rwy_id) == 0)
1859 re = &rwy->ends[0];
1860 else if (strcmp(rwy->ends[1].id, rwy_id) == 0)
1861 re = &rwy->ends[1];
1862 else
1863 continue;
1864 /*
1865 * Ovewrite pre-existing data, which may have
1866 * come from VGSI auto-computation. This data
1867 * should be more reliable & accurate.
1868 */
1869 re->gpa = gpa;
1870 break;
1871 }
1872out_appch:
1873 free_strlist(comps, ncomps);
1874 } else if (strstr(line, "RWY:") == line) {
1875 /*
1876 * Extract runway threshold elevation from runway
1877 * lines.
1878 */
1879 char **comps;
1880 char rwy_id[4];
1881 size_t ncomps;
1882 runway_t *rwy;
1883
1884 comps = strsplit(line + 4, ",", B_FALSE, &ncomps);
1885 if (ncomps != 8)
1886 goto out_rwy;
1887 for (size_t i = 0; i < ncomps; i++)
1888 strip_space(comps[i]);
1889 if (strstr(comps[0], "RW") != comps[0])
1890 goto out_rwy;
1891 copy_rwy_ID(comps[0] + 2, rwy_id);
1892 for (rwy = avl_first(&arpt->rwys); rwy != NULL;
1893 rwy = AVL_NEXT(&arpt->rwys, rwy)) {
1894 runway_end_t *re;
1895 int telev, tch;
1896
1897 if (strcmp(rwy->ends[0].id, rwy_id) == 0)
1898 re = &rwy->ends[0];
1899 else if (strcmp(rwy->ends[1].id, rwy_id) == 0)
1900 re = &rwy->ends[1];
1901 else
1902 continue;
1903 if (sscanf(comps[3], "%d", &telev) == 1 &&
1904 is_valid_elev(telev))
1905 re->thr.elev = telev;
1906 if (sscanf(comps[7], "%d", &tch) == 1 &&
1907 tch > 0 && tch < RWY_TCH_LIMIT)
1908 re->tch = tch;
1909 break;
1910 }
1911out_rwy:
1912 free_strlist(comps, ncomps);
1913 }
1914 }
1915
1916 fclose(fp);
1917
1918 free(line);
1919 return (B_TRUE);
1920}
1921
1922static bool_t
1923load_CIFP_file(airportdb_t *db, const char *dirpath, const char *filename)
1924{
1925 airport_t *arpt;
1926 char *filepath;
1927 char ident[8];
1928 bool_t res;
1929
1930 ASSERT(db != NULL);
1931 ASSERT(dirpath != NULL);
1932 ASSERT(filename != NULL);
1933
1934 /* the filename must end in ".dat" */
1935 if (strlen(filename) < 4 ||
1936 strcmp(&filename[strlen(filename) - 4], ".dat") != 0) {
1937 return (B_FALSE);
1938 }
1939 lacf_strlcpy(ident, filename, sizeof (ident));
1940 ident[strlen(filename) - 4] = '\0';
1941 arpt = apt_dat_lookup(db, ident);
1942 if (arpt == NULL)
1943 return (B_FALSE);
1944 filepath = mkpathname(dirpath, filename, NULL);
1945 res = load_arinc424_arpt_data(filepath, arpt);
1946 free(filepath);
1947
1948 return (res);
1949}
1950
1951/*
1952 * Loads all ARINC424-formatted procedures files from a CIFP directory
1953 * in the new X-Plane 11 navdata. This has to be OS-specific, because
1954 * directory enumeration isn't portable.
1955 */
1956#if IBM
1957
1958static bool_t
1959load_CIFP_dir(airportdb_t *db, const char *dirpath)
1960{
1961 int dirpath_len = strlen(dirpath);
1962 TCHAR *dirnameT = safe_calloc(dirpath_len + 1, sizeof (*dirnameT));
1963 TCHAR *srchnameT = safe_calloc(dirpath_len + 4, sizeof (*srchnameT));
1964 WIN32_FIND_DATA find_data;
1965 HANDLE h_find;
1966
1967 ASSERT(db != NULL);
1968 ASSERT(dirpath != NULL);
1969
1970 MultiByteToWideChar(CP_UTF8, 0, dirpath, -1, dirnameT, dirpath_len + 1);
1971 StringCchPrintf(srchnameT, dirpath_len + 4, TEXT("%s\\*"), dirnameT);
1972 h_find = FindFirstFile(srchnameT, &find_data);
1973 if (h_find == INVALID_HANDLE_VALUE) {
1974 goto errout;
1975 }
1976 do {
1977 if (wcscmp(find_data.cFileName, TEXT(".")) == 0 ||
1978 wcscmp(find_data.cFileName, TEXT("..")) == 0) {
1979 continue;
1980 }
1981 unsigned l = wcslen(find_data.cFileName) + 1;
1982 char *filename = safe_calloc(l, sizeof (*filename));
1983 WideCharToMultiByte(CP_UTF8, 0, find_data.cFileName, -1,
1984 filename, l, NULL, NULL);
1985 (void) load_CIFP_file(db, dirpath, filename);
1986 free(filename);
1987 } while (FindNextFile(h_find, &find_data));
1988
1989 FindClose(h_find);
1990 free(dirnameT);
1991 free(srchnameT);
1992 return (B_TRUE);
1993errout:
1994 free(dirnameT);
1995 free(srchnameT);
1996 return (B_FALSE);
1997}
1998
1999#else /* !IBM */
2000
2001static bool_t
2002load_CIFP_dir(airportdb_t *db, const char *dirpath)
2003{
2004 DIR *dp = opendir(dirpath);
2005 struct dirent *de;
2006
2007 ASSERT(db != NULL);
2008 ASSERT(dirpath != NULL);
2009
2010 if (dp == NULL)
2011 return (B_FALSE);
2012
2013 while ((de = readdir(dp)) != NULL) {
2014 if (strcmp(de->d_name, ".") == 0 ||
2015 strcmp(de->d_name, "..") == 0)
2016 continue;
2017 (void) load_CIFP_file(db, dirpath, de->d_name);
2018 }
2019
2020 closedir(dp);
2021
2022 return (B_TRUE);
2023}
2024
2025#endif /* !IBM */
2026
2027/*
2028 * Initiates the supplemental information loading from X-Plane 11 navdata.
2029 * Here we try to determine, for runways which lacked that info in apt.dat,
2030 * the runway's threshold elevation and the GPA/TCH (based on a nearby
2031 * ILS GS antenna).
2032 */
2033static bool_t
2034load_xp11_navdata(airportdb_t *db)
2035{
2036 bool_t isdir;
2037 char *dirpath;
2038 bool_t success = B_FALSE;
2039
2040 ASSERT(db != NULL);
2041
2042 dirpath = mkpathname(db->xpdir, "Custom Data", "CIFP", NULL);
2043 if (file_exists(dirpath, &isdir) && isdir) {
2044 if (!load_CIFP_dir(db, dirpath))
2045 logMsg("%s: error parsing navdata, falling "
2046 "back to default data.", dirpath);
2047 }
2048 free(dirpath);
2049
2050 dirpath = mkpathname(db->xpdir, "Resources", "default data", "CIFP",
2051 NULL);
2052 success = load_CIFP_dir(db, dirpath);
2053 if (!success) {
2054 logMsg("%s: error parsing navdata, please check your install",
2055 dirpath);
2056 }
2057 free(dirpath);
2058
2059 return (success);
2060}
2061
2062/*
2063 * Checks to make sure our data cache is up to the newest version.
2064 */
2065static bool_t
2066check_cache_version(const airportdb_t *db, int app_version)
2067{
2068 char *version_str;
2069 int version = -1;
2070
2071 ASSERT(db != NULL);
2072
2073 if ((version_str = file2str(db->cachedir, "version", NULL)) != NULL) {
2074 version = atoi(version_str);
2075 free(version_str);
2076 }
2077 /*
2078 * If the caller provided an app_version number, also check that.
2079 * Otherwise ignore it.
2080 */
2081 if (app_version == 0)
2082 version &= 0xffff;
2083 return (version == (ARPTDB_CACHE_VERSION | (app_version << 16)));
2084}
2085
2097bool_t
2098adb_airportdb_xp_airac_cycle(const char *xpdir, int *cycle)
2099{
2100 int linenum = 0;
2101 char *line = NULL;
2102 size_t linecap = 0;
2103 char *filename;
2104 FILE *fp;
2105 bool_t success = B_FALSE;
2106
2107 ASSERT(xpdir != NULL);
2108 ASSERT(cycle != NULL);
2109
2110 /* First try 'Custom Data', then 'default data' */
2111 filename = mkpathname(xpdir, "Custom Data", "earth_nav.dat", NULL);
2112 fp = fopen(filename, "r");
2113 if (fp == NULL) {
2114 free(filename);
2115 filename = mkpathname(xpdir, "Resources", "default data",
2116 "earth_nav.dat", NULL);
2117 fp = fopen(filename, "r");
2118 if (fp == NULL)
2119 goto out;
2120 }
2121
2122 while (!feof(fp)) {
2123 const char *word_start;
2124
2125 /* Early abort if the header of the file was passed */
2126 if (linenum++ > 20)
2127 break;
2128 if (getline(&line, &linecap, fp) <= 0 ||
2129 (strstr(line, "1100 ") != line &&
2130 strstr(line, "1150 ") != line &&
2131 strstr(line, "1200 ") != line) ||
2132 (word_start = strstr(line, " data cycle ")) == NULL) {
2133 continue;
2134 }
2135 /* constant is length of " data cycle " string */
2136 success = (sscanf(word_start + 12, "%d", cycle) == 1);
2137 if (success)
2138 break;
2139 }
2140 free(line);
2141 fclose(fp);
2142out:
2143 free(filename);
2144
2145 return (success);
2146}
2147
2148/*
2149 * Grabs the AIRAC cycle from the X-Plane navdata and compares it to the
2150 * info we have in our cache. Returns true if the cycles match or false
2151 * otherwise (update to cache needed).
2152 */
2153static bool_t
2154check_airac_cycle(airportdb_t *db)
2155{
2156 char *cycle_str;
2157 int db_cycle = -1, xp_cycle = -1;
2158
2159 ASSERT(db != NULL);
2160
2161 if ((cycle_str = file2str(db->cachedir, "airac_cycle", NULL)) != NULL) {
2162 db_cycle = atoi(cycle_str);
2163 free(cycle_str);
2164 }
2165 if (!airportdb_xp11_airac_cycle(db->xpdir, &xp_cycle)) {
2166 if ((cycle_str = file2str(db->xpdir, "Custom Data", "GNS430",
2167 "navdata", "cycle_info.txt", NULL)) == NULL)
2168 cycle_str = file2str(db->xpdir, "Resources", "GNS430",
2169 "navdata", "cycle_info.txt", NULL);
2170 if (cycle_str != NULL) {
2171 char *sep = strstr(cycle_str, "AIRAC cycle");
2172 if (sep != NULL)
2173 sep = strstr(&sep[11], ": ");
2174 if (sep != NULL) {
2175 xp_cycle = atoi(&sep[2]);
2176 }
2177 free(cycle_str);
2178 }
2179 }
2180
2181 db->xp_airac_cycle = xp_cycle;
2182
2183 return (db_cycle == xp_cycle);
2184}
2185
2186static bool_t
2187read_apt_dats_list(const airportdb_t *db, list_t *list)
2188{
2189 FILE *fp;
2190 char *filename;
2191 char *line = NULL;
2192 size_t cap = 0;
2193
2194 ASSERT(db != NULL);
2195 ASSERT(list != NULL);
2196
2197 filename = mkpathname(db->cachedir, "apt_dats", NULL);
2198 fp = fopen(filename, "r");
2199 free(filename);
2200 if (fp == NULL)
2201 return (B_FALSE);
2202
2203 while (!feof(fp)) {
2204 apt_dats_entry_t *entry;
2205
2206 if (getline(&line, &cap, fp) <= 0)
2207 continue;
2208 strip_space(line);
2209 entry = safe_malloc(sizeof (*entry));
2210 entry->fname = strdup(line);
2211 list_insert_tail(list, entry);
2212 }
2213
2214 free(line);
2215 fclose(fp);
2216
2217 return (B_TRUE);
2218}
2219
2220static void
2221destroy_apt_dats_list(list_t *list)
2222{
2224 ASSERT(list != NULL);
2225 while ((e = list_head(list)) != NULL) {
2226 list_remove(list, e);
2227 free(e->fname);
2228 free(e);
2229 }
2231}
2232
2233static bool_t
2234cache_up_to_date(airportdb_t *db, list_t *xp_apt_dats, int app_version)
2235{
2236 list_t db_apt_dats;
2237 bool_t result = B_TRUE;
2238 apt_dats_entry_t *xp_e, *db_e;
2239 bool_t vers_ok, cycle_ok;
2240
2241 ASSERT(db != NULL);
2242 ASSERT(xp_apt_dats != NULL);
2243 /*
2244 * We need to call both of these functions because check_airac_cycle
2245 * establishes what AIRAC cycle X-Plane uses and modifies `db', so
2246 * we'll need it later on when recreating the cache.
2247 */
2248 vers_ok = check_cache_version(db, app_version);
2249 cycle_ok = check_airac_cycle(db);
2250 if (!vers_ok || !cycle_ok)
2251 return (B_FALSE);
2252
2253 list_create(&db_apt_dats, sizeof (apt_dats_entry_t),
2254 offsetof(apt_dats_entry_t, node));
2255 read_apt_dats_list(db, &db_apt_dats);
2256 for (xp_e = list_head(xp_apt_dats), db_e = list_head(&db_apt_dats);
2257 xp_e != NULL && db_e != NULL; xp_e = list_next(xp_apt_dats, xp_e),
2258 db_e = list_next(&db_apt_dats, db_e)) {
2259 if (strcmp(xp_e->fname, db_e->fname) != 0) {
2260 result = B_FALSE;
2261 break;
2262 }
2263 }
2264 if (db_e != NULL || xp_e != NULL)
2265 result = B_FALSE;
2266 destroy_apt_dats_list(&db_apt_dats);
2267
2268 return (result);
2269}
2270
2271static arpt_index_t *
2272create_arpt_index(airportdb_t *db, const airport_t *arpt)
2273{
2274 arpt_index_t *idx = safe_calloc(1, sizeof (*idx));
2275
2276 ASSERT(db != NULL);
2277 ASSERT(arpt != NULL);
2278
2279 lacf_strlcpy(idx->ident, arpt->ident, sizeof (idx->ident));
2280 lacf_strlcpy(idx->icao, arpt->icao, sizeof (idx->icao));
2281 if (arpt->iata[0] != '\0')
2282 lacf_strlcpy(idx->iata, arpt->iata, sizeof (idx->iata));
2283 else
2284 lacf_strlcpy(idx->iata, "-", sizeof (idx->iata));
2285 if (arpt->cc[0] != '\0')
2286 lacf_strlcpy(idx->cc, arpt->cc, sizeof (idx->cc));
2287 else
2288 lacf_strlcpy(idx->cc, "ZZ", sizeof (idx->cc));
2289 idx->pos = TO_GEO3_32(arpt->refpt);
2290 for (const runway_t *rwy = avl_first(&arpt->rwys); rwy != NULL;
2291 rwy = AVL_NEXT(&arpt->rwys, rwy)) {
2292 if (rwy_is_hard(rwy->surf)) {
2293 idx->max_rwy_len = MAX(idx->max_rwy_len,
2294 MET2FEET(rwy->ends[0].land_len));
2295 idx->max_rwy_len = MAX(idx->max_rwy_len,
2296 MET2FEET(rwy->ends[1].land_len));
2297 }
2298 }
2299 idx->TA = arpt->TA;
2300 idx->TL = arpt->TL;
2301
2302 avl_add(&db->arpt_index, idx);
2303 if (idx->icao[0] != '\0') {
2304 htbl2_set(&db->icao_index, idx->icao, sizeof (idx->icao),
2305 idx, sizeof (*idx));
2306 }
2307 if (idx->iata[0] != '\0') {
2308 htbl2_set(&db->iata_index, idx->iata, sizeof (idx->iata),
2309 idx, sizeof (*idx));
2310 }
2311 return (idx);
2312}
2313
2314static bool_t
2315read_index_dat(airportdb_t *db)
2316{
2317 char *index_filename;
2318 FILE *index_file;
2319 char *line = NULL;
2320 size_t line_cap = 0;
2321 size_t num_lines = 0;
2322
2323 ASSERT(db != NULL);
2324 index_filename = mkpathname(db->cachedir, "index.dat", NULL);
2325 index_file = fopen(index_filename, "r");
2326
2327 if (index_file == NULL)
2328 return (B_FALSE);
2329
2330 if (!db->override_settings) {
2331 conf_t *conf;
2332 char *filename = mkpathname(db->cachedir, "settings.conf",
2333 NULL);
2334
2335 if (file_exists(filename, NULL) &&
2336 (conf = conf_read_file(filename, NULL)) != NULL) {
2337 conf_get_b(conf, "ifr_only", &db->ifr_only);
2338 conf_get_b(conf, "normalize_gate_names",
2339 &db->normalize_gate_names);
2340 conf_free(conf);
2341 }
2342 LACF_DESTROY(filename);
2343 }
2344 while (lacf_getline(&line, &line_cap, index_file) > 0)
2345 num_lines++;
2346 rewind(index_file);
2347
2348 recreate_icao_iata_tables(db, num_lines);
2349
2350 while (lacf_getline(&line, &line_cap, index_file) > 0) {
2351 arpt_index_t *idx = safe_calloc(1, sizeof (*idx));
2352 avl_index_t where;
2353
2354 if (sscanf(line, "%7s %7s %3s %2s %f %f %f %hu %hu %hu",
2355 idx->ident, idx->icao, idx->iata, idx->cc,
2356 &idx->pos.lat, &idx->pos.lon, &idx->pos.elev,
2357 &idx->max_rwy_len, &idx->TA, &idx->TL) != 10) {
2358 free(idx);
2359 continue;
2360 }
2361 if (avl_find(&db->arpt_index, idx, &where) == NULL) {
2362 avl_insert(&db->arpt_index, idx, where);
2363 htbl2_set(&db->icao_index, idx->icao,
2364 sizeof (idx->icao), idx, sizeof (*idx));
2365 if (strcmp(idx->iata, "-") != 0) {
2366 htbl2_set(&db->iata_index, idx->iata,
2367 sizeof (idx->iata), idx, sizeof (*idx));
2368 }
2369 } else {
2370 logMsg("WARNING: found duplicate airport ident %s "
2371 "in index. Skipping it. This shouldn't happen "
2372 "unless the index is damaged.", idx->ident);
2373 free(idx);
2374 }
2375 }
2376 free(line);
2377 fclose(index_file);
2378 free(index_filename);
2379
2380 return (B_TRUE);
2381}
2382
2383static bool_t
2384write_index_dat(const arpt_index_t *idx, FILE *index_file)
2385{
2386 ASSERT(idx != NULL);
2387 ASSERT(index_file != NULL);
2388 return (fprintf(index_file, "%s\t%s\t%s\t%s\t%f\t%f\t%.0f\t"
2389 "%hu\t%hu\t%hu\n",
2390 idx->ident, idx->icao[0] != '\0' ? idx->icao : "-",
2391 idx->iata[0] != '\0' ? idx->iata : "-",
2392 idx->cc[0] != '\0' ? idx->cc : "-",
2393 idx->pos.lat, idx->pos.lon, idx->pos.elev,
2394 idx->max_rwy_len, idx->TA, idx->TL) > 0);
2395}
2396
2397static bool_t
2398recreate_cache_skeleton(airportdb_t *db, list_t *apt_dat_files, int app_version)
2399{
2400 char *filename;
2401 FILE *fp;
2402 bool_t exists, isdir;
2403
2404 ASSERT(db != NULL);
2405 ASSERT(apt_dat_files != NULL);
2406
2407 exists = file_exists(db->cachedir, &isdir);
2408 if ((exists && ((isdir && !remove_directory(db->cachedir)) ||
2409 (!isdir && !remove_file(db->cachedir, B_FALSE)))) ||
2410 !create_directory_recursive(db->cachedir))
2411 return (B_FALSE);
2412
2413 filename = mkpathname(db->cachedir, "version", NULL);
2414 fp = fopen(filename, "w");
2415 if (fp == NULL) {
2416 logMsg("Error writing new airport database, can't open "
2417 "%s for writing: %s", filename, strerror(errno));
2418 free(filename);
2419 return (B_FALSE);
2420 }
2421 fprintf(fp, "%d", (app_version << 16) | ARPTDB_CACHE_VERSION);
2422 fclose(fp);
2423 free(filename);
2424
2425 filename = mkpathname(db->cachedir, "airac_cycle", NULL);
2426 fp = fopen(filename, "w");
2427 if (fp == NULL) {
2428 logMsg("Error writing new airport database, can't open "
2429 "%s for writing: %s", filename, strerror(errno));
2430 free(filename);
2431 return (B_FALSE);
2432 }
2433 fprintf(fp, "%d", db->xp_airac_cycle);
2434 fclose(fp);
2435 free(filename);
2436
2437 filename = mkpathname(db->cachedir, "apt_dats", NULL);
2438 fp = fopen(filename, "w");
2439 if (fp == NULL) {
2440 logMsg("Error writing new airport database, can't open "
2441 "%s for writing: %s", filename, strerror(errno));
2442 free(filename);
2443 return (B_FALSE);
2444 }
2445 for (apt_dats_entry_t *e = list_head(apt_dat_files); e != NULL;
2446 e = list_next(apt_dat_files, e))
2447 fprintf(fp, "%s\n", e->fname);
2448 fclose(fp);
2449 free(filename);
2450
2451 if (db->override_settings) {
2452 conf_t *conf = conf_create_empty();
2453
2454 conf_set_b(conf, "ifr_only", db->ifr_only);
2455 conf_set_b(conf, "normalize_gate_names",
2456 db->normalize_gate_names);
2457 filename = mkpathname(db->cachedir, "settings.conf", NULL);
2458 conf_write_file(conf, filename);
2459 conf_free(conf);
2460 }
2461
2462 return (B_TRUE);
2463}
2464
2498bool_t
2499adb_recreate_cache(airportdb_t *db, int app_version)
2500{
2501 list_t apt_dat_files;
2502 bool_t success = B_TRUE;
2503 char *index_filename = NULL;
2504 FILE *index_file = NULL;
2505 iconv_t cd;
2506 char *prev_locale = NULL, *saved_locale = NULL;
2507
2508 ASSERT(db != NULL);
2509
2510 list_create(&apt_dat_files, sizeof (apt_dats_entry_t),
2511 offsetof(apt_dats_entry_t, node));
2512 find_all_apt_dats(db, &apt_dat_files);
2513 if (cache_up_to_date(db, &apt_dat_files, app_version) &&
2514 read_index_dat(db)) {
2515 goto out;
2516 }
2517 /* This is needed to get iconv transliteration to work correctly */
2518 prev_locale = setlocale(LC_CTYPE, NULL);
2519 if (prev_locale != NULL)
2520 saved_locale = safe_strdup(prev_locale);
2521 setlocale(LC_CTYPE, "");
2522 /* First scan all the provided apt.dat files */
2523 cd = iconv_open("ASCII//TRANSLIT", "UTF-8");
2524 for (apt_dats_entry_t *e = list_head(&apt_dat_files); e != NULL;
2525 e = list_next(&apt_dat_files, e)) {
2526 bool_t fill_in_dups = (list_next(&apt_dat_files, e) == NULL);
2527 read_apt_dat(db, e->fname, B_TRUE, &cd, fill_in_dups);
2528 }
2529 iconv_close(cd);
2530 if (saved_locale != NULL) {
2531 setlocale(LC_CTYPE, saved_locale);
2532 free(saved_locale);
2533 }
2534 if (!load_xp11_navdata(db)) {
2535 success = B_FALSE;
2536 goto out;
2537 }
2538 if (avl_numnodes(&db->apt_dat) == 0) {
2539 logMsg("navdata error: it appears your simulator's "
2540 "navigation database is broken, or your simulator "
2541 "contains no airport scenery. Please reinstall the "
2542 "database and retry.");
2543 success = B_FALSE;
2544 goto out;
2545 }
2546
2547 if (!recreate_cache_skeleton(db, &apt_dat_files, app_version)) {
2548 success = B_FALSE;
2549 goto out;
2550 }
2551 index_filename = mkpathname(db->cachedir, "index.dat", NULL);
2552 index_file = fopen(index_filename, "w");
2553 if (index_file == NULL) {
2554 logMsg("Error creating airport database index file %s: %s",
2555 index_filename, strerror(errno));
2556 success = B_FALSE;
2557 goto out;
2558 }
2559 recreate_icao_iata_tables(db, avl_numnodes(&db->apt_dat));
2560 for (airport_t *arpt = avl_first(&db->apt_dat), *next_arpt;
2561 arpt != NULL; arpt = next_arpt) {
2562 next_arpt = AVL_NEXT(&db->apt_dat, arpt);
2563 ASSERT(arpt->geo_linked);
2564 /*
2565 * If the airport isn't in Airports.txt, we want to dump the
2566 * airport, because we don't have TA/TL info on them. But if
2567 * we are in ifr_only=B_FALSE mode, then accept it anyway.
2568 */
2569 if (!arpt->have_iaps && db->ifr_only) {
2570 geo_unlink_airport(db, arpt);
2571 avl_remove(&db->apt_dat, arpt);
2572 free_airport(arpt);
2573 } else {
2574 arpt_index_t *idx = create_arpt_index(db, arpt);
2575 write_index_dat(idx, index_file);
2576 }
2577 }
2578 for (airport_t *arpt = avl_first(&db->apt_dat); arpt != NULL;
2579 arpt = AVL_NEXT(&db->apt_dat, arpt)) {
2580 char *dirname;
2581
2582 ASSERT(arpt->geo_linked);
2583 ASSERT(avl_numnodes(&arpt->rwys) != 0);
2584
2585 dirname = apt_dat_cache_dir(db, GEO3_TO_GEO2(arpt->refpt),
2586 NULL);
2587 if (!create_directory(dirname) || !write_apt_dat(db, arpt)) {
2588 free(dirname);
2589 success = B_FALSE;
2590 goto out;
2591 }
2592 free(dirname);
2593 }
2594out:
2596 destroy_apt_dats_list(&apt_dat_files);
2597 free(index_filename);
2598 if (index_file != NULL)
2599 fclose(index_file);
2600
2601 return (success);
2602}
2603
2604/*
2605 * The approach proximity bounding box is constructed as follows:
2606 *
2607 * 5500 meters
2608 * |<=======>|
2609 * | |
2610 * d +-_ (c1) |
2611 * | -._3 degrees
2612 * | -_ c
2613 * | +-------------------------------+
2614 * | | ==== ---- ---- ==== |
2615 * x + thr_v-+ ==== - ------> dir_v - - ==== |
2616 * | | ==== ---- ---- ==== |
2617 * | +-------------------------------+
2618 * | _- b
2619 * | _-.
2620 * a +-- (b1)
2621 *
2622 * If there is another parallel runway, we make sure our bounding boxes
2623 * don't overlap. We do this by introducing two additional points, b1 and
2624 * c1, in between a and b or c and d respectively. We essentially shear
2625 * the overlapping excess from the bounding polygon.
2626 */
2627static vect2_t *
2628make_apch_prox_bbox(const runway_t *rwy, int end_i)
2629{
2630 const runway_end_t *end, *oend;
2631 const fpp_t *fpp;
2632 double limit_left = 1000000, limit_right = 1000000;
2633 vect2_t x, a, b, b1, c, c1, d, thr_v, othr_v, dir_v;
2634 vect2_t *bbox = safe_calloc(7, sizeof (vect2_t));
2635 size_t n_pts = 0;
2636
2637 ASSERT(rwy != NULL);
2638 fpp = &rwy->arpt->fpp;
2639 ASSERT(end_i == 0 || end_i == 1);
2640
2641 /*
2642 * By pre-initing the whole array to null vectors, we can make the
2643 * bbox either contain 4, 5 or 6 points, depending on whether
2644 * shearing due to a close parallel runway needs to be applied.
2645 */
2646 for (int i = 0; i < 7; i++)
2647 bbox[i] = NULL_VECT2;
2648
2649 end = &rwy->ends[end_i];
2650 oend = &rwy->ends[!end_i];
2651 thr_v = end->thr_v;
2652 othr_v = oend->thr_v;
2653 dir_v = vect2_sub(othr_v, thr_v);
2654
2655 x = vect2_add(thr_v, vect2_set_abs(vect2_neg(dir_v),
2656 RWY_APCH_PROXIMITY_LON_DISPL));
2657 a = vect2_add(x, vect2_set_abs(vect2_norm(dir_v, B_TRUE),
2658 rwy->width / 2 + RWY_APCH_PROXIMITY_LAT_DISPL));
2659 b = vect2_add(thr_v, vect2_set_abs(vect2_norm(dir_v, B_TRUE),
2660 rwy->width / 2));
2661 c = vect2_add(thr_v, vect2_set_abs(vect2_norm(dir_v, B_FALSE),
2662 rwy->width / 2));
2663 d = vect2_add(x, vect2_set_abs(vect2_norm(dir_v, B_FALSE),
2664 rwy->width / 2 + RWY_APCH_PROXIMITY_LAT_DISPL));
2665
2666 b1 = NULL_VECT2;
2667 c1 = NULL_VECT2;
2668
2669 /*
2670 * If our rwy_id designator contains a L/C/R, then we need to
2671 * look for another parallel runway.
2672 */
2673 if (strlen(end->id) >= 3) {
2674 int my_num_id = atoi(end->id);
2675
2676 for (const runway_t *orwy = avl_first(&rwy->arpt->rwys);
2677 orwy != NULL; orwy = AVL_NEXT(&rwy->arpt->rwys, orwy)) {
2678 const runway_end_t *orwy_end;
2679 vect2_t othr_v, v;
2680 double a, dist;
2681
2682 if (orwy == rwy)
2683 continue;
2684 if (atoi(orwy->ends[0].id) == my_num_id)
2685 orwy_end = &orwy->ends[0];
2686 else if (atoi(orwy->ends[1].id) == my_num_id)
2687 orwy_end = &orwy->ends[1];
2688 else
2689 continue;
2690
2691 /*
2692 * This is a parallel runway, measure the
2693 * distance to it from us.
2694 */
2695 othr_v = geo2fpp(GEO3_TO_GEO2(orwy_end->thr), fpp);
2696 v = vect2_sub(othr_v, thr_v);
2697 if (IS_ZERO_VECT2(v)) {
2698 logMsg("CAUTION: your nav DB is looking very "
2699 "strange: runways %s and %s at %s are on "
2700 "top of each other (coords: %fx%f)",
2701 end->id, orwy_end->id, rwy->arpt->icao,
2702 orwy_end->thr.lat, orwy_end->thr.lon);
2703 continue;
2704 }
2705 a = rel_hdg(dir2hdg(dir_v), dir2hdg(v));
2706 dist = fabs(sin(DEG2RAD(a)) * vect2_abs(v));
2707
2708 if (a < 0)
2709 limit_left = MIN(dist / 2, limit_left);
2710 else
2711 limit_right = MIN(dist / 2, limit_right);
2712 }
2713 }
2714
2715 if (limit_left < RWY_APCH_PROXIMITY_LAT_DISPL) {
2716 c1 = vect2vect_isect(vect2_sub(d, c), c, vect2_neg(dir_v),
2717 vect2_add(thr_v, vect2_set_abs(vect2_norm(dir_v, B_FALSE),
2718 limit_left)), B_FALSE);
2719 d = vect2_add(x, vect2_set_abs(vect2_norm(dir_v, B_FALSE),
2720 limit_left));
2721 }
2722 if (limit_right < RWY_APCH_PROXIMITY_LAT_DISPL) {
2723 b1 = vect2vect_isect(vect2_sub(b, a), a, vect2_neg(dir_v),
2724 vect2_add(thr_v, vect2_set_abs(vect2_norm(dir_v, B_TRUE),
2725 limit_right)), B_FALSE);
2726 a = vect2_add(x, vect2_set_abs(vect2_norm(dir_v, B_TRUE),
2727 limit_right));
2728 }
2729
2730 bbox[n_pts++] = a;
2731 if (!IS_NULL_VECT(b1))
2732 bbox[n_pts++] = b1;
2733 bbox[n_pts++] = b;
2734 bbox[n_pts++] = c;
2735 if (!IS_NULL_VECT(c1))
2736 bbox[n_pts++] = c1;
2737 bbox[n_pts++] = d;
2738
2739 return (bbox);
2740}
2741
2742/*
2743 * Prepares a runway's bounding box vector coordinates using the airport
2744 * coord fpp transform.
2745 */
2746static void
2747load_rwy_info(runway_t *rwy)
2748{
2749 ASSERT(rwy != NULL);
2750 ASSERT(rwy->arpt->load_complete);
2751 /*
2752 * RAAS runway proximity entry bounding box is defined as:
2753 *
2754 * 1000ft 1000ft
2755 * |<======>| |<======>|
2756 * | | | |
2757 * ---- d +-------------------------------------------------+ c
2758 * 1.5x ^ | | | |
2759 * rwy | | | | |
2760 * width | | +-------------------------------+ |
2761 * v | | ==== ---- ---- ==== | |
2762 * -------|-thresh-x ==== - - - - - - - - - - ==== | |
2763 * ^ | | ==== ---- ---- ==== | |
2764 * 1.5x | | +-------------------------------+ |
2765 * rwy | | |
2766 * width v | |
2767 * ---- a +-------------------------------------------------+ b
2768 */
2769 vect2_t dt1v = geo2fpp(GEO3_TO_GEO2(rwy->ends[0].thr), &rwy->arpt->fpp);
2770 vect2_t dt2v = geo2fpp(GEO3_TO_GEO2(rwy->ends[1].thr), &rwy->arpt->fpp);
2771 double displ1 = rwy->ends[0].displ;
2772 double displ2 = rwy->ends[1].displ;
2773 double blast1 = rwy->ends[0].blast;
2774 double blast2 = rwy->ends[1].blast;
2775
2776 vect2_t dir_v = vect2_sub(dt2v, dt1v);
2777 double dlen = vect2_abs(dir_v);
2778 double hdg1 = dir2hdg(dir_v);
2779 double hdg2 = dir2hdg(vect2_neg(dir_v));
2780
2781 vect2_t t1v = vect2_add(dt1v, vect2_set_abs(dir_v, displ1));
2782 vect2_t t2v = vect2_add(dt2v, vect2_set_abs(vect2_neg(dir_v), displ2));
2783 double len = vect2_abs(vect2_sub(t2v, t1v));
2784
2785 double prox_lon_bonus1 = MAX(displ1, RWY_PROXIMITY_LON_DISPL - displ1);
2786 double prox_lon_bonus2 = MAX(displ2, RWY_PROXIMITY_LON_DISPL - displ2);
2787
2788 rwy->ends[0].thr_v = t1v;
2789 rwy->ends[1].thr_v = t2v;
2790 rwy->ends[0].dthr_v = dt1v;
2791 rwy->ends[1].dthr_v = dt2v;
2792 rwy->ends[0].hdg = hdg1;
2793 rwy->ends[1].hdg = hdg2;
2794 rwy->ends[0].land_len = vect2_abs(vect2_sub(dt2v, t1v));
2795 rwy->ends[1].land_len = vect2_abs(vect2_sub(dt1v, t2v));
2796 rwy->length = len;
2797
2798 ASSERT(rwy->rwy_bbox == NULL);
2799
2800 rwy->rwy_bbox = make_rwy_bbox(t1v, dir_v, rwy->width, len, 0);
2801 rwy->tora_bbox = make_rwy_bbox(dt1v, dir_v, rwy->width, dlen, 0);
2802 rwy->asda_bbox = make_rwy_bbox(dt1v, dir_v, rwy->width,
2803 dlen + blast2, blast1);
2804 rwy->prox_bbox = make_rwy_bbox(t1v, dir_v, RWY_PROXIMITY_LAT_FRACT *
2805 rwy->width, len + prox_lon_bonus2, prox_lon_bonus1);
2806
2807 rwy->ends[0].apch_bbox = make_apch_prox_bbox(rwy, 0);
2808 rwy->ends[1].apch_bbox = make_apch_prox_bbox(rwy, 1);
2809}
2810
2811static void
2812unload_rwy_info(runway_t *rwy)
2813{
2814 ASSERT(rwy != NULL);
2815 ASSERT(rwy->rwy_bbox != NULL);
2816
2817 free(rwy->rwy_bbox);
2818 rwy->rwy_bbox = NULL;
2819 free(rwy->tora_bbox);
2820 rwy->tora_bbox = NULL;
2821 free(rwy->asda_bbox);
2822 rwy->asda_bbox = NULL;
2823 free(rwy->prox_bbox);
2824 rwy->prox_bbox = NULL;
2825
2826 free(rwy->ends[0].apch_bbox);
2827 rwy->ends[0].apch_bbox = NULL;
2828 free(rwy->ends[1].apch_bbox);
2829 rwy->ends[1].apch_bbox = NULL;
2830}
2831
2832/*
2833 * Given an airport, loads the information of the airport into a more readily
2834 * workable (but more verbose) format. This function prepares a flat plane
2835 * transform centered on the airport's reference point and pre-computes all
2836 * relevant points for the airport in that space.
2837 * Returns true if the operation succeeded, false otherwise. The airport needs
2838 * to have an airport reference point defined before this will succeed.
2839 */
2840static bool_t
2841load_airport(airport_t *arpt)
2842{
2843 ASSERT(arpt != NULL);
2844
2845 if (arpt->load_complete)
2846 return (B_TRUE);
2847
2848 if (isnan(arpt->refpt.lat) || isnan(arpt->refpt.lon) ||
2849 isnan(arpt->refpt.elev))
2850 return (B_FALSE);
2851
2852 /* must go ahead of load_rwy_info to not trip an assertion */
2853 arpt->load_complete = B_TRUE;
2854
2855 arpt->fpp = ortho_fpp_init(GEO3_TO_GEO2(arpt->refpt), 0, &wgs84,
2856 B_FALSE);
2857 arpt->ecef = geo2ecef_ft(arpt->refpt, &wgs84);
2858
2859 for (runway_t *rwy = avl_first(&arpt->rwys); rwy != NULL;
2860 rwy = AVL_NEXT(&arpt->rwys, rwy))
2861 load_rwy_info(rwy);
2862
2863 return (B_TRUE);
2864}
2865
2866static void
2867unload_airport(airport_t *arpt)
2868{
2869 ASSERT(arpt != NULL);
2870 if (!arpt->load_complete)
2871 return;
2872 for (runway_t *rwy = avl_first(&arpt->rwys); rwy != NULL;
2873 rwy = AVL_NEXT(&arpt->rwys, rwy))
2874 unload_rwy_info(rwy);
2875 arpt->load_complete = B_FALSE;
2876}
2877
2878static void
2879free_airport(airport_t *arpt)
2880{
2881 void *cookie;
2882 runway_t *rwy;
2883 freq_info_t *freq;
2884 ramp_start_t *rs;
2885
2886 ASSERT(arpt != NULL);
2887 if (arpt->load_complete)
2888 unload_airport(arpt);
2889
2890 cookie = NULL;
2891 while ((rs = avl_destroy_nodes(&arpt->ramp_starts, &cookie)) != NULL)
2892 free(rs);
2893 avl_destroy(&arpt->ramp_starts);
2894
2895 cookie = NULL;
2896 while ((rwy = avl_destroy_nodes(&arpt->rwys, &cookie)) != NULL)
2897 free(rwy);
2898 avl_destroy(&arpt->rwys);
2899
2900 while ((freq = list_remove_head(&arpt->freqs)) != NULL)
2901 free(freq);
2902 list_destroy(&arpt->freqs);
2903 ASSERT(!list_link_active(&arpt->cur_arpts_node));
2904 LACF_DESTROY(arpt->name_orig);
2905 LACF_DESTROY(arpt->city);
2906 LACF_DESTROY(arpt->country);
2907 ZERO_FREE(arpt);
2908}
2909
2914static void
2915find_nearest_airports_tile(airportdb_t *db, vect3_t ecef,
2916 geo_pos2_t tile_coord, list_t *l)
2917{
2918 tile_t *tile;
2919
2920 ASSERT(db != NULL);
2921 ASSERT(l != NULL);
2922 tile = geo_table_get_tile(db, tile_coord, B_FALSE, NULL);
2923
2924 if (tile == NULL)
2925 return;
2926 for (airport_t *arpt = avl_first(&tile->arpts); arpt != NULL;
2927 arpt = AVL_NEXT(&tile->arpts, arpt)) {
2928 vect3_t arpt_ecef = geo2ecef_ft(arpt->refpt, &wgs84);
2929 if (vect3_abs(vect3_sub(ecef, arpt_ecef)) < db->load_limit) {
2930 list_insert_tail(l, arpt);
2931 VERIFY(load_airport(arpt));
2932 }
2933 }
2934}
2935
2952list_t *
2954{
2955 vect3_t ecef;
2956 list_t *l;
2957
2958 ASSERT(db != NULL);
2959 ASSERT(!IS_NULL_GEO_POS(my_pos));
2960 ecef = geo2ecef_ft(GEO_POS3(my_pos.lat, my_pos.lon, 0), &wgs84);
2961
2962 l = safe_malloc(sizeof (*l));
2963 list_create(l, sizeof (airport_t), offsetof(airport_t, cur_arpts_node));
2964 for (int i = -1; i <= 1; i++) {
2965 for (int j = -1; j <= 1; j++)
2966 find_nearest_airports_tile(db, ecef,
2967 GEO_POS2(my_pos.lat + i, my_pos.lon + j), l);
2968 }
2969
2970 return (l);
2971}
2972
2984void
2986{
2987 ASSERT(l != NULL);
2988 for (airport_t *a = list_head(l); a != NULL; a = list_head(l))
2989 list_remove(l, a);
2990 ZERO_FREE(l);
2991}
2992
2993static void
2994load_airports_in_tile(airportdb_t *db, geo_pos2_t tile_pos)
2995{
2996 bool_t created;
2997 char *cache_dir, *fname;
2998 char lat_lon[16];
2999
3000 ASSERT(db != NULL);
3001 ASSERT(!IS_NULL_GEO_POS(tile_pos));
3002
3003 (void) geo_table_get_tile(db, tile_pos, B_TRUE, &created);
3004 if (!created)
3005 return;
3006
3007 tile_pos = geo_pos2tile_pos(tile_pos, B_FALSE);
3008 cache_dir = apt_dat_cache_dir(db, tile_pos, NULL);
3009 snprintf(lat_lon, sizeof (lat_lon), TILE_NAME_FMT,
3010 tile_pos.lat, tile_pos.lon);
3011 fname = mkpathname(cache_dir, lat_lon, NULL);
3012 if (file_exists(fname, NULL))
3013 read_apt_dat(db, fname, B_FALSE, NULL, B_FALSE);
3014 free(cache_dir);
3015 free(fname);
3016}
3017
3018static void
3019free_tile(airportdb_t *db, tile_t *tile, bool_t do_remove)
3020{
3021 void *cookie = NULL;
3022 airport_t *arpt;
3023
3024 ASSERT(db != NULL);
3025 ASSERT(tile != NULL);
3026
3027 while ((arpt = avl_destroy_nodes(&tile->arpts, &cookie)) != NULL) {
3028 avl_remove(&db->apt_dat, arpt);
3029 free_airport(arpt);
3030 }
3031 avl_destroy(&tile->arpts);
3032
3033 if (do_remove)
3034 avl_remove(&db->geo_table, tile);
3035 ZERO_FREE(tile);
3036}
3037
3048void
3050{
3051 ASSERT(db != NULL);
3052 db->load_limit = limit;
3053}
3054
3065void
3066load_nearest_airport_tiles(airportdb_t *db, geo_pos2_t my_pos)
3067{
3068 ASSERT(db != NULL);
3069 ASSERT(!IS_NULL_GEO_POS(my_pos));
3070
3071 for (int i = -1; i <= 1; i++) {
3072 for (int j = -1; j <= 1; j++)
3073 load_airports_in_tile(db, GEO_POS2(my_pos.lat + i,
3074 my_pos.lon + j));
3075 }
3076}
3077
3078static double
3079lon_delta(double x, double y)
3080{
3081 double u = MAX(x, y), d = MIN(x, y);
3082
3083 if (u - d <= 180)
3084 return (fabs(u - d));
3085 else
3086 return (fabs((180 - u) - (-180 - d)));
3087}
3088
3089static void
3090unload_distant_airport_tiles_i(airportdb_t *db, tile_t *tile, geo_pos2_t my_pos)
3091{
3092 ASSERT(db != NULL);
3093 ASSERT(tile != NULL);
3094 if (IS_NULL_GEO_POS(my_pos) ||
3095 fabs(tile->pos.lat - floor(my_pos.lat)) > 1 ||
3096 lon_delta(tile->pos.lon, floor(my_pos.lon)) > 1)
3097 free_tile(db, tile, B_TRUE);
3098}
3099
3113void
3115{
3116 tile_t *tile, *next_tile;
3117
3118 ASSERT(db != NULL);
3119 /* my_pos can be NULL_GEO_POS2 */
3120
3121 for (tile = avl_first(&db->geo_table); tile != NULL; tile = next_tile) {
3122 next_tile = AVL_NEXT(&db->geo_table, tile);
3123 unload_distant_airport_tiles_i(db, tile, my_pos);
3124 }
3125
3126 if (IS_NULL_GEO_POS(my_pos)) {
3127 ASSERT(avl_numnodes(&db->geo_table) == 0);
3128 ASSERT(avl_numnodes(&db->apt_dat) == 0);
3129 }
3130}
3131
3132static int
3133arpt_index_compar(const void *a, const void *b)
3134{
3135 const arpt_index_t *ia = a, *ib = b;
3136 int res = strcmp(ia->ident, ib->ident);
3137
3138 if (res < 0)
3139 return (-1);
3140 if (res > 0)
3141 return (1);
3142 return (0);
3143}
3144
3153void
3154airportdb_create(airportdb_t *db, const char *xpdir, const char *cachedir)
3155{
3156 VERIFY(db != NULL);
3157 VERIFY(xpdir != NULL);
3158 VERIFY(cachedir != NULL);
3159
3160 db->inited = B_TRUE;
3161 db->xpdir = strdup(xpdir);
3162 db->cachedir = strdup(cachedir);
3163 db->load_limit = ARPT_LOAD_LIMIT;
3164 db->ifr_only = B_TRUE;
3165 db->normalize_gate_names = B_FALSE;
3166
3167 mutex_init(&db->lock);
3168
3169 avl_create(&db->apt_dat, airport_compar, sizeof (airport_t),
3170 offsetof(airport_t, apt_dat_node));
3171 avl_create(&db->geo_table, tile_compar, sizeof (tile_t),
3172 offsetof(tile_t, node));
3173 avl_create(&db->arpt_index, arpt_index_compar,
3174 sizeof (arpt_index_t), offsetof(arpt_index_t, node));
3175 /*
3176 * Just some defaults - we'll resize the tables later when
3177 * we actually read the index file.
3178 */
3179 htbl2_create(&db->icao_index, 16, AIRPORTDB_ICAO_LEN,
3180 sizeof (arpt_index_t), B_TRUE);
3181 htbl2_create(&db->iata_index, 16, AIRPORTDB_IATA_LEN,
3182 sizeof (arpt_index_t), B_TRUE);
3183}
3184
3189void
3191{
3192 tile_t *tile;
3193 void *cookie;
3194 arpt_index_t *idx;
3195
3196 ASSERT(db != NULL);
3197 if (!db->inited)
3198 return;
3199
3200 cookie = NULL;
3201 while ((idx = avl_destroy_nodes(&db->arpt_index, &cookie)) != NULL)
3202 free(idx);
3203 avl_destroy(&db->arpt_index);
3204
3205 /* airports are freed in the free_tile function */
3206 cookie = NULL;
3207 while ((tile = avl_destroy_nodes(&db->geo_table, &cookie)) != NULL)
3208 free_tile(db, tile, B_FALSE);
3209 avl_destroy(&db->geo_table);
3210 avl_destroy(&db->apt_dat);
3211
3212 htbl2_empty(&db->icao_index, sizeof (arpt_index_t), NULL, NULL);
3213 htbl2_destroy(&db->icao_index);
3214 htbl2_empty(&db->iata_index, sizeof (arpt_index_t), NULL, NULL);
3215 htbl2_destroy(&db->iata_index);
3216
3217 mutex_destroy(&db->lock);
3218
3219 free(db->xpdir);
3220 free(db->cachedir);
3221 memset(db, 0, sizeof (*db));
3222}
3223
3230void
3232{
3233 ASSERT(db != NULL);
3234 mutex_enter(&db->lock);
3235}
3236
3242void
3244{
3245 ASSERT(db != NULL);
3246 mutex_exit(&db->lock);
3247}
3248
3261airport_t *
3263{
3264 arpt_index_t *idx;
3265 arpt_index_t srch = {};
3266
3267 ASSERT(db != NULL);
3268 ASSERT(ident != NULL);
3269
3270 lacf_strlcpy(srch.ident, ident, sizeof (srch.ident));
3271 idx = avl_find(&db->arpt_index, &srch, NULL);
3272 if (idx == NULL)
3273 return (NULL);
3274 return (adb_airport_lookup(db, ident, TO_GEO2(idx->pos)));
3275}
3276
3277static void
3278airport_lookup_htbl_multi(airportdb_t *db, const list_t *list,
3279 void (*found_cb)(airport_t *airport, void *userinfo), void *userinfo)
3280{
3281 ASSERT(db != NULL);
3282 ASSERT(list != NULL);
3283
3284 for (void *mv = list_head(list); mv != NULL;
3285 mv = list_next(list, mv)) {
3286 arpt_index_t *idx = htbl2_value_multi(mv, sizeof (*idx));
3287
3288 if (found_cb != NULL) {
3289 airport_t *apt = adb_airport_lookup(db, idx->ident,
3290 TO_GEO2(idx->pos));
3291 /*
3292 * Although we should NEVER hit a state where this
3293 * lookup fails, the function might need to perform
3294 * I/O to read the tile's apt.dat, which brings the
3295 * possibility of a failed read. Since users' drives
3296 * can be all kinds of garbage, we can't hard-assert
3297 * here due to potential I/O issues.
3298 */
3299 if (apt != NULL) {
3300 found_cb(apt, userinfo);
3301 } else {
3302 logMsg("WARNING: airport database index is "
3303 "damaged: index contains ICAO %s, but "
3304 "the associated database tile doesn't "
3305 "appear to contain this airport.",
3306 idx->icao);
3307 }
3308 }
3309 }
3310}
3311
3327size_t
3329 void (*found_cb)(airport_t *airport, void *userinfo), void *userinfo)
3330{
3331 const list_t *list;
3332 char icao_srch[AIRPORTDB_ICAO_LEN] = {};
3333
3334 ASSERT(db != NULL);
3335 ASSERT(icao != NULL);
3336
3337 strlcpy(icao_srch, icao, sizeof (icao_srch));
3338 list = htbl2_lookup_multi(&db->icao_index, icao_srch,
3339 sizeof (icao_srch));
3340 if (list != NULL) {
3341 airport_lookup_htbl_multi(db, list, found_cb, userinfo);
3342 return (list_count(list));
3343 } else {
3344 return (0);
3345 }
3346}
3347
3354size_t
3356 void (*found_cb)(airport_t *airport, void *userinfo), void *userinfo)
3357{
3358 const list_t *list;
3359 char iata_srch[AIRPORTDB_IATA_LEN] = {};
3360
3361 ASSERT(db != NULL);
3362 ASSERT(iata != NULL);
3363
3364 strlcpy(iata_srch, iata, sizeof (iata_srch));
3365 list = htbl2_lookup_multi(&db->iata_index, iata_srch,
3366 sizeof (iata_srch));
3367 if (list != NULL) {
3368 airport_lookup_htbl_multi(db, list, found_cb, userinfo);
3369 return (list_count(list));
3370 } else {
3371 return (0);
3372 }
3373}
3374
3381airport_t *
3382adb_airport_lookup(airportdb_t *db, const char *icao, geo_pos2_t pos)
3383{
3384 ASSERT(db != NULL);
3385 ASSERT(icao != NULL);
3386 load_airports_in_tile(db, pos);
3387 return (apt_dat_lookup(db, icao));
3388}
3389
3390static void
3391save_arpt_cb(airport_t *airport, void *userinfo)
3392{
3393 airport_t **out_arpt = userinfo;
3394 ASSERT(airport != NULL);
3395 *out_arpt = airport;
3396}
3397
3410airport_t *
3412{
3413 airport_t *found = NULL;
3414 ASSERT(db != NULL);
3415 ASSERT(icao != NULL);
3416 (void)airport_lookup_by_icao(db, icao, save_arpt_cb, &found);
3417 return (found);
3418}
3419
3442size_t
3444 void (*found_cb)(const arpt_index_t *idx, void *userinfo), void *userinfo)
3445{
3446 ASSERT(db != NULL);
3447
3448 if (found_cb != NULL) {
3449 for (const arpt_index_t *idx = avl_first(&db->arpt_index);
3450 idx != NULL; idx = AVL_NEXT(&db->arpt_index, idx)) {
3451 found_cb(idx, userinfo);
3452 }
3453 }
3454
3455 return (avl_numnodes(&db->arpt_index));
3456}
3457
3476bool_t
3477adb_airport_find_runway(airport_t *arpt, const char *rwy_id, runway_t **rwy_p,
3478 unsigned *end_p)
3479{
3480 ASSERT(arpt != NULL);
3481 ASSERT(rwy_id != NULL);
3482 ASSERT(rwy_p != NULL);
3483 *rwy_p = NULL;
3484 ASSERT(end_p != NULL);
3485
3486 for (runway_t *rwy = avl_first(&arpt->rwys); rwy != NULL;
3487 rwy = AVL_NEXT(&arpt->rwys, rwy)) {
3488 for (unsigned i = 0; i < 2; i++) {
3489 if (strcmp(rwy->ends[i].id, rwy_id) == 0) {
3490 *rwy_p = rwy;
3491 *end_p = i;
3492 return (B_TRUE);
3493 }
3494 }
3495 }
3496
3497 return (B_FALSE);
3498}
3499
3500airport_t *
3501adb_matching_airport_in_tile_with_TATL(airportdb_t *db, geo_pos2_t pos,
3502 const char *search_icao)
3503{
3504 tile_t *tile;
3505 char const *search_cc;
3506
3507 ASSERT(db != NULL);
3508 ASSERT(search_icao != NULL);
3509 search_cc = extract_icao_country_code(search_icao);
3510
3511 load_airports_in_tile(db, pos);
3512 tile = geo_table_get_tile(db, pos, B_FALSE, NULL);
3513 if (tile == NULL)
3514 return (NULL);
3515
3516 for (airport_t *arpt = avl_first(&tile->arpts); arpt != NULL;
3517 arpt = AVL_NEXT(&tile->arpts, arpt)) {
3518 /*
3519 * Because the passed in ICAO code might be invalid or of an
3520 * unknown country, if that is the case and we can't extract
3521 * the country code, we'll just try to do the best job we can
3522 * and grab any airport in the tile with a TA/TL value.
3523 */
3524 if ((arpt->TA != 0 || arpt->TL != 0) && (search_cc == NULL ||
3525 search_cc == extract_icao_country_code(arpt->icao)))
3526 return (arpt);
3527 }
3528
3529 return (NULL);
3530}
size_t adb_airport_lookup_by_iata(airportdb_t *db, const char *iata, void(*found_cb)(airport_t *airport, void *userinfo), void *userinfo)
Definition airportdb.c:3355
airport_t * adb_airport_lookup(airportdb_t *db, const char *icao, geo_pos2_t pos)
Definition airportdb.c:3382
airport_t * adb_airport_lookup_by_ident(airportdb_t *db, const char *ident)
Definition airportdb.c:3262
airport_t * adb_airport_lookup_global(airportdb_t *db, const char *icao)
Definition airportdb.c:3411
void airportdb_lock(airportdb_t *db)
Definition airportdb.c:3231
bool_t adb_airportdb_xp_airac_cycle(const char *xpdir, int *cycle)
Definition airportdb.c:2098
bool_t adb_recreate_cache(airportdb_t *db, int app_version)
Definition airportdb.c:2499
size_t adb_airport_index_walk(const airportdb_t *db, void(*found_cb)(const arpt_index_t *idx, void *userinfo), void *userinfo)
Definition airportdb.c:3443
list_t * adb_find_nearest_airports(airportdb_t *db, geo_pos2_t my_pos)
Definition airportdb.c:2953
void adb_free_nearest_airport_list(list_t *l)
Definition airportdb.c:2985
void adb_set_airport_load_limit(airportdb_t *db, double limit)
Definition airportdb.c:3049
void airportdb_create(airportdb_t *db, const char *xpdir, const char *cachedir)
Definition airportdb.c:3154
bool_t adb_airport_find_runway(airport_t *arpt, const char *rwy_id, runway_t **rwy_p, unsigned *end_p)
Definition airportdb.c:3477
void adb_unload_distant_airport_tiles(airportdb_t *db, geo_pos2_t my_pos)
Definition airportdb.c:3114
size_t adb_airport_lookup_by_icao(airportdb_t *db, const char *icao, void(*found_cb)(airport_t *airport, void *userinfo), void *userinfo)
Definition airportdb.c:3328
void airportdb_unlock(airportdb_t *db)
Definition airportdb.c:3243
void airportdb_destroy(airportdb_t *db)
Definition airportdb.c:3190
#define VERIFY(x)
Definition assert.h:78
#define ASSERT3U(x, op, y)
Definition assert.h:210
#define ASSERT(x)
Definition assert.h:208
void * avl_destroy_nodes(avl_tree_t *tree, void **cookie)
Definition avl.c:938
void * avl_first(const avl_tree_t *tree)
Definition avl.c:172
uintptr_t avl_index_t
Definition avl.h:119
void avl_remove(avl_tree_t *tree, void *node)
Definition avl.c:660
void avl_add(avl_tree_t *tree, void *node)
Definition avl.c:621
void avl_insert(avl_tree_t *tree, void *node, avl_index_t where)
Definition avl.c:471
void avl_create(avl_tree_t *tree, int(*compar)(const void *, const void *), size_t size, size_t offset)
Definition avl.c:867
#define AVL_NEXT(tree, node)
Definition avl.h:212
unsigned long avl_numnodes(const avl_tree_t *tree)
Definition avl.c:903
void avl_destroy(avl_tree_t *tree)
Definition avl.c:890
void * avl_find(const avl_tree_t *tree, const void *node, avl_index_t *where)
Definition avl.c:244
void conf_set_b(conf_t *conf, const char *key, bool_t value)
Definition conf.c:1081
bool_t conf_get_b(const conf_t *conf, const char *key, bool_t *value)
Definition conf.c:850
conf_t * conf_read_file(const char *filename, int *errline)
Definition conf.c:174
void conf_free(conf_t *conf)
Definition conf.c:142
bool_t conf_write_file(const conf_t *conf, const char *filename)
Definition conf.c:449
conf_t * conf_create_empty(void)
Definition conf.c:87
#define ARRAY_NUM_ELEM(_array)
Definition core.h:171
void lacf_free(void *buf)
Definition core.c:49
#define LACF_DESTROY(ptr)
Definition core.h:158
vect2_t vect2vect_isect(vect2_t da, vect2_t oa, vect2_t db, vect2_t ob, bool_t confined)
Definition geom.c:1171
vect2_t vect2_add(vect2_t a, vect2_t b)
Definition geom.c:310
vect2_t geo2fpp(geo_pos2_t pos, const fpp_t *fpp)
Definition geom.c:1749
vect2_t vect2_sub(vect2_t a, vect2_t b)
Definition geom.c:341
#define IS_ZERO_VECT2(a)
Definition geom.h:247
double vect2_abs(vect2_t a)
Definition geom.c:151
#define IS_NULL_VECT(a)
Definition geom.h:228
#define NULL_GEO_POS2
Definition geom.h:224
#define GEO3_FT2M(g)
Definition geom.h:274
#define GEO_POS3(lat, lon, elev)
Definition geom.h:166
vect2_t vect2_neg(vect2_t v)
Definition geom.c:587
#define IS_NULL_GEO_POS(a)
Definition geom.h:239
double dir2hdg(vect2_t dir)
Definition geom.c:1371
#define GEO_POS2(lat, lon)
Definition geom.h:164
#define GEO3_TO_GEO2(v)
Definition geom.h:272
vect3_t vect3_sub(vect3_t a, vect3_t b)
Definition geom.c:323
#define NULL_GEO_POS3
Definition geom.h:220
vect2_t vect2_set_abs(vect2_t a, double abs)
Definition geom.c:227
double vect3_dist(vect3_t a, vect3_t b)
Definition geom.c:133
#define TO_GEO2(geo2)
Definition geom.h:181
vect2_t vect2_norm(vect2_t v, bool_t right)
Definition geom.c:536
double vect3_abs(vect3_t a)
Definition geom.c:115
fpp_t ortho_fpp_init(geo_pos2_t center, double rot, const ellip_t *ellip, bool_t allow_inv)
Definition geom.c:1711
const ellip_t wgs84
Definition geom.c:38
#define DEG2RAD(d)
Definition geom.h:156
#define NULL_VECT2
Definition geom.h:214
double vect2_dotprod(vect2_t a, vect2_t b)
Definition geom.c:404
vect3_t geo2ecef_ft(geo_pos3_t pos, const ellip_t *ellip)
Definition geom.c:884
vect2_t vect2_unit(vect2_t a, double *l)
Definition geom.c:272
#define TO_GEO3_32(geo3)
Definition geom.h:184
char ** strsplit(const char *input, const char *sep, bool_t skip_empty, size_t *num)
Definition helpers.c:650
static ssize_t lacf_getline(char **lineptr, size_t *n, FILE *stream)
Definition helpers.h:511
void fix_pathsep(char *str)
Definition helpers.c:892
char * mkpathname(const char *comp,...)
Definition helpers.c:833
void strtoupper(char *str)
Definition helpers.c:783
void copy_rwy_ID(const char *src, char dst[4])
Definition helpers.c:411
char * file2str(const char *comp,...)
Definition helpers.c:1036
bool_t remove_directory(const char *dirname)
Definition helpers.c:1382
bool_t is_valid_icao_code(const char *icao)
Definition helpers.c:312
const char * extract_icao_country_code(const char *icao)
Definition helpers.c:358
static bool_t is_valid_elev(double elev)
Definition helpers.h:123
static bool_t is_valid_hdg(double hdg)
Definition helpers.h:169
bool_t create_directory(const char *dirname)
Definition helpers.c:1261
static int lacf_strcasecmp(const char *s1, const char *s2)
Definition helpers.h:634
void append_format(char **str, size_t *sz, const char *format,...)
Definition helpers.c:731
void lacf_strlcpy(char *dest, const char *src, size_t cap)
Definition helpers.c:1667
void free_strlist(char **comps, size_t num)
Definition helpers.c:703
bool_t create_directory_recursive(const char *dirname)
Definition helpers.c:1292
bool_t is_valid_rwy_ID(const char *rwy_ID)
Definition helpers.c:379
static bool_t is_valid_lat(double lat)
Definition helpers.h:93
bool_t remove_file(const char *filename, bool_t notfound_ok)
Definition helpers.c:1459
bool_t file_exists(const char *path, bool_t *isdir)
Definition helpers.c:1222
#define rel_hdg(hdg1, hdg2)
Definition helpers.h:198
static bool_t is_valid_lon(double lon)
Definition helpers.h:111
bool_t is_valid_iata_code(const char *iata)
Definition helpers.c:336
static char * lacf_strcasestr(const char *haystack, const char *needle)
Definition helpers.h:645
#define P2ROUNDUP(x)
Definition helpers.h:736
int list_link_active(const list_node_t *)
Definition list.c:525
void list_destroy(list_t *)
Definition list.c:136
void * list_head(const list_t *)
Definition list.c:292
void list_create(list_t *, size_t, size_t)
Definition list.c:113
void * list_next(const list_t *, const void *)
Definition list.c:344
size_t list_count(const list_t *)
Definition list.c:543
void list_remove(list_t *, void *)
Definition list.c:226
void * list_remove_head(list_t *)
Definition list.c:251
void list_insert_tail(list_t *, void *)
Definition list.c:213
#define logMsg(...)
Definition log.h:112
#define NONE(type_name)
Constructs a new optional value in the NONE state.
Definition optional.h:996
#define SOME(expr)
Constructs a new optional value in the SOME state.
Definition optional.h:967
#define IF_LET(vartype, varname, opt)
Allows constructing Rust-like if-let statements in C.
Definition optional.h:1507
static void strip_space(char *line)
#define ZERO_FREE(ptr)
Definition safe_alloc.h:253
static char * safe_strdup(const char *str2)
Definition safe_alloc.h:201
static void * safe_calloc(size_t nmemb, size_t size)
Definition safe_alloc.h:71
static void * safe_malloc(size_t size)
Definition safe_alloc.h:56
Definition airportdb.c:97
char cc[4]
Definition airportdb.h:326
char iata[4]
Definition airportdb.h:324
char ident[8]
Definition airportdb.h:320
uint16_t TA
Definition airportdb.h:329
uint16_t TL
Definition airportdb.h:330
char icao[8]
Definition airportdb.h:322
uint16_t max_rwy_len
Definition airportdb.h:328
geo_pos3_32_t pos
Definition airportdb.h:327
Definition conf.c:41
Definition geom.h:473
uint64_t freq
Definition airportdb.h:256
freq_type_t type
Definition airportdb.h:255
char name[32]
Definition airportdb.h:257
double lat
Definition geom.h:50
double lon
Definition geom.h:51
float elev
Definition geom.h:61
float lat
Definition geom.h:59
float lon
Definition geom.h:60
double lat
Definition geom.h:41
double elev
Definition geom.h:43
double lon
Definition geom.h:42
An optional type for wrapping a non-NAN float value.
Definition optional.h:755
ramp_start_type_t type
Definition airportdb.h:183
geo_pos2_t pos
Definition airportdb.h:181
char name[32]
Definition airportdb.h:180
Definition geom.h:89
static void mutex_destroy(mutex_t *mtx)
Definition thread.h:499
static void mutex_enter(mutex_t *mtx)
Definition thread.h:530
static void mutex_exit(mutex_t *mtx)
Definition thread.h:556
static void mutex_init(mutex_t *mtx)
Definition thread.h:488