29#include "acfutils/odb.h"
30#include "acfutils/perf.h"
33#include "acfutils/time.h"
35#include "chart_prov_common.h"
37#define DEFAULT_UNLOAD_DELAY 60
39#define DL_TIMEOUT 300L
40#define LOW_SPD_LIM 4096L
41#define LOW_SPD_TIME 30L
43#define REALLOC_STEP (8 << 20)
45#define FAA_DOF_URL "https://aeronav.faa.gov/Obst_Data/DAILY_DOF_CSV.ZIP"
80 unsigned unload_delay;
89 time_t refresh_times[NUM_ODB_REGIONS];
95static void add_obst_to_odb(obst_type_t type,
geo_pos3_t pos,
float agl,
96 obst_light_t light,
unsigned quant,
void *userinfo);
97static odb_tile_t *load_tile(odb_t *odb,
int lat,
int lon,
99static void odb_flush_tiles(odb_t *odb);
102tile_compar(
const void *a,
const void *b)
106 if (ta->lat < tb->lat)
108 if (ta->lat > tb->lat)
110 if (ta->lon < tb->lon)
112 if (ta->lon > tb->lon)
119latlon2path(
int lat,
int lon,
char dname[32])
122 snprintf(dname, 32,
"%+03d%+04d%c%+03d%+04d",
123 (
int)floor(lat / 10.0) * 10, (
int)floor(lon / 10.0) * 10,
127static inline obst_type_t
128dof2type(
const char *type)
130 if (strcmp(type,
"BLDG") == 0)
132 if (strcmp(type,
"TOWER") == 0 || strstr(type,
"TWR") != NULL)
134 if (strcmp(type,
"STACK") == 0)
136 if (strcmp(type,
"RIG") == 0)
138 if (strstr(type,
"POLE") != NULL)
143static inline obst_light_t
144dof2light(
const char *light)
148 return (OBST_LIGHT_RED);
150 return (OBST_LIGHT_STROBE_WR_MED);
152 return (OBST_LIGHT_STROBE_WR_HI);
154 return (OBST_LIGHT_STROBE_W_MED);
156 return (OBST_LIGHT_STROBE_W_HI);
158 return (OBST_LIGHT_FLOOD);
160 return (OBST_LIGHT_DUAL_MED_CAT);
162 return (OBST_LIGHT_SYNC_RED);
164 return (OBST_LIGHT_LIGHTED);
166 return (OBST_LIGHT_NONE);
168 return (OBST_LIGHT_UNK);
172static inline const char *
173type2dof(obst_type_t type)
192light2dof(obst_light_t light)
197 case OBST_LIGHT_STROBE_WR_MED:
199 case OBST_LIGHT_STROBE_WR_HI:
201 case OBST_LIGHT_STROBE_W_MED:
203 case OBST_LIGHT_STROBE_W_HI:
205 case OBST_LIGHT_FLOOD:
207 case OBST_LIGHT_DUAL_MED_CAT:
209 case OBST_LIGHT_SYNC_RED:
211 case OBST_LIGHT_LIGHTED:
213 case OBST_LIGHT_NONE:
221odb_proc_us_dof_impl(
const char *buf,
size_t len, add_obst_cb_t cb,
227 for (
const char *line_start = buf, *line_end = buf;
228 line_start < buf + len; line_start = line_end + 1) {
238 line_end = strchr(line_start,
'\n');
239 if (line_end == NULL)
240 line_end = buf + len;
242 (line_end - line_start) + 1));
246 comps =
strsplit(line,
",", B_FALSE, &n_comps);
248 if (n_comps < 19 || strcmp(comps[0],
"OAS") == 0)
251 quant = atoi(comps[10]);
252 agl = FEET2MET(atof(comps[11]));
253 amsl = FEET2MET(atof(comps[12]));
254 type = dof2type(comps[9]);
255 light = dof2light(comps[13]);
256 pos.
lat = atof(comps[5]);
257 pos.
lon = atof(comps[6]);
258 pos.
elev = amsl - agl;
265 cb(type, pos, agl, light, quant, userinfo);
274odb_proc_us_dof(
const char *path, add_obst_cb_t cb,
void *userinfo)
284 res = odb_proc_us_dof_impl(str, strlen(str), cb, userinfo);
300 memset(tile, 0,
sizeof (*tile));
305odb_init(
const char *xpdir,
const char *cainfo)
311 odb->cache_dir =
mkpathname(xpdir,
"Output",
"caches",
312 "obstacle.db", NULL);
314 odb->cainfo = strdup(cainfo);
315 odb->unload_delay = DEFAULT_UNLOAD_DELAY;
334 if (odb->refresh_run) {
335 odb->refresh_run = B_FALSE;
341 odb_flush_tiles(odb);
351 free(odb->cache_dir);
352 memset(odb, 0,
sizeof (*odb));
357odb_set_unload_delay(odb_t *odb,
unsigned seconds)
360 odb->unload_delay = seconds;
364odb_get_cc_refresh_date_impl(odb_t* odb,
const char *cc)
371 ASSERT_MSG(strlen(cc) == 2 && strchr(cc, DIRSEP) == NULL,
372 "invalid country code \"%s\" passed", cc);
374 str =
file2str(odb->cache_dir, cc,
"refresh.txt", NULL);
385odb_get_cc_refresh_date(odb_t *odb,
const char *cc)
390 if (strcmp(cc,
"US") == 0) {
391 if (odb->refresh_times[ODB_REGION_US] == 0) {
392 odb->refresh_times[ODB_REGION_US] =
393 odb_get_cc_refresh_date_impl(odb, cc);
395 return odb->refresh_times[ODB_REGION_US];
402dl_write(
char *ptr,
size_t size,
size_t nmemb,
void *userdata)
405 size_t bytes = size * nmemb;
411 if (!dl_info->odb->refresh_run)
414 if (dl_info->bufcap < dl_info->bufsz + bytes) {
416 dl_info->bufcap += REALLOC_STEP;
417 }
while (dl_info->bufcap < dl_info->bufsz + bytes);
418 dl_info->buf = realloc(dl_info->buf, dl_info->bufcap);
420 memcpy(&dl_info->buf[dl_info->bufsz], ptr, bytes);
421 dl_info->bufsz += bytes;
427write_tile(odb_t *odb,
odb_tile_t *tile,
const char *cc)
430 char *path, *dirpath, *p;
432 bool_t res = B_FALSE;
438 latlon2path(tile->lat, tile->lon, subpath);
439 path =
mkpathname(odb->cache_dir, cc, subpath, NULL);
440 dirpath = strdup(path);
444 p = strrchr(dirpath, DIRSEP);
450 fp = fopen(path,
"wb");
452 logMsg(
"Error writing obstacle database tile %s: %s",
453 path, strerror (errno));
458 fprintf(fp,
",,US,,,%f,%f,,,%s,%d,%.0f,%.0f,%c,1A,,,,\n",
459 obst->pos.
lat, obst->pos.
lon, type2dof(obst->type),
460 obst->quant, MET2FEET(obst->agl),
461 MET2FEET(obst->pos.
elev + obst->agl),
462 light2dof(obst->light));
476odb_write_tiles(odb_t *odb,
const char *cc)
483 tile =
AVL_NEXT(&odb->tiles, tile)) {
484 if (!write_tile(odb, tile, cc))
490odb_flush_tiles(odb_t *odb)
503write_odb_refresh_date(odb_t *odb,
const char *cc)
507 time_t now = time(NULL);
510 ASSERT(odb->cache_dir != NULL);
513 path =
mkpathname(odb->cache_dir, cc,
"refresh.txt", NULL);
514 fp = fopen(path,
"wb");
516 logMsg(
"Error writing obstacle database refresh file %s: %s",
517 path, strerror(errno));
520 fprintf(fp,
"%ld\n", (
long)now);
527odb_refresh_us_decompress(odb_t *odb,
dl_info_t *dl_info)
535 if (dl_info->bufsz == 0) {
536 logMsg(
"Error updating obstacle database from %s: "
537 "server didn't send any data", FAA_DOF_URL);
540 buf = decompress_zip(dl_info->buf, dl_info->bufsz, &len);
542 char *subpath =
mkpathname(odb->cache_dir,
"US", NULL);
548 odb_proc_us_dof_impl(buf, len, add_obst_to_odb, odb);
555 odb_write_tiles(odb,
"US");
556 odb_flush_tiles(odb);
557 write_odb_refresh_date(odb,
"US");
560 odb->refresh_times[ODB_REGION_US] = time(NULL);
564 logMsg(
"Error updating obstacle database from %s: "
565 "failed to decompress downloaded ZIP file", FAA_DOF_URL);
566 odb->refresh_times[ODB_REGION_US] = -1u;
572odb_refresh_us(odb_t *odb)
583 curl = curl_easy_init();
586 logMsg(
"Downloading new obstacle data from \"%s\" for region \"US\"",
588 curl_easy_setopt(curl, CURLOPT_URL, FAA_DOF_URL);
589 chart_setup_curl(curl, odb->cainfo);
590 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, dl_write);
591 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &dl_info);
592 curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, LOW_SPD_TIME);
593 curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, LOW_SPD_LIM);
594 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
595 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
596 curl_easy_setopt(curl, CURLOPT_TIMEOUT, DL_TIMEOUT);
598 if (odb->proxy != NULL)
599 curl_easy_setopt(curl, CURLOPT_PROXY, odb->proxy);
602 refresh_date = odb_get_cc_refresh_date(odb,
"US");
603 if (refresh_date != 0) {
605 curl_easy_setopt(curl, CURLOPT_TIMEVALUE, (
long)refresh_date);
606 curl_easy_setopt(curl, CURLOPT_TIMECONDITION,
607 (
long)CURL_TIMECOND_IFMODSINCE);
609 res = curl_easy_perform(curl);
611 if (res == CURLE_OK) {
614 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
616 odb_refresh_us_decompress(odb, &dl_info);
617 }
else if (code == 304) {
618 logMsg(
"Obstacle database %s unchanged", FAA_DOF_URL);
620 odb->refresh_times[ODB_REGION_US] = time(NULL);
623 logMsg(
"Error updating obstacle database from %s: "
624 "server responded with code %ld", FAA_DOF_URL,
628 logMsg(
"Error updating obstacle database from %s: %s",
629 FAA_DOF_URL, curl_easy_strerror(res));
630 odb->refresh_times[ODB_REGION_US] = -1u;
633 curl_easy_cleanup(curl);
637 odb->refresh_run = B_FALSE;
642odb_refresh_cc(odb_t *odb,
const char *cc)
644 void (*refresh_op)(odb_t *odb) = NULL;
650 if (!odb->refresh_run) {
651 if (strcmp(cc,
"US") == 0)
652 refresh_op = odb_refresh_us;
653 if (refresh_op != NULL) {
654 odb->refresh_run = B_TRUE;
656 (void (*)(
void *))refresh_op, odb));
665add_tile_obst(obst_type_t type,
geo_pos3_t pos,
float agl,
666 obst_light_t light,
unsigned quant,
void *userinfo)
673 ASSERT(tile->odb != NULL);
686add_obst_to_odb(obst_type_t type,
geo_pos3_t pos,
float agl,
687 obst_light_t light,
unsigned quant,
void *userinfo)
696 tile = load_tile(odb, floor(pos.
lat), floor(pos.
lon), B_FALSE);
697 add_tile_obst(type, pos, agl, light, quant, tile);
702odb_populate_tile_us(odb_t *odb,
odb_tile_t *tile)
710 latlon2path(tile->lat, tile->lon, tilepath);
711 path =
mkpathname(odb->cache_dir,
"US", tilepath, NULL);
712 odb_proc_us_dof(path, add_tile_obst, tile);
717odb_populate_tile(odb_t *odb,
odb_tile_t *tile)
721 odb_populate_tile_us(odb, tile);
725load_tile(odb_t *odb,
int lat,
int lon, bool_t load_from_db)
734 tile =
avl_find(&odb->tiles, &srch, &where);
744 odb_populate_tile(odb, tile);
748 tile->access_t = time(NULL);
754odb_get_obstacles(odb_t *odb,
int lat,
int lon, add_obst_cb_t cb,
764 tile = load_tile(odb, lat, lon, B_TRUE);
767 cb(obst->type, obst->pos, obst->agl, obst->light, obst->quant,
777odb_set_proxy(odb_t *odb,
const char *proxy)
789odb_get_proxy(odb_t *odb,
char *proxy,
size_t cap)
794 ASSERT(proxy != NULL || cap == 0);
797 if (odb->proxy != NULL) {
799 len = strlen(odb->proxy) + 1;
#define ASSERT_MSG(x, fmt,...)
void * avl_destroy_nodes(avl_tree_t *tree, void **cookie)
void * avl_first(const avl_tree_t *tree)
void avl_insert(avl_tree_t *tree, void *node, avl_index_t where)
void avl_create(avl_tree_t *tree, int(*compar)(const void *, const void *), size_t size, size_t offset)
#define AVL_NEXT(tree, node)
void avl_destroy(avl_tree_t *tree)
void * avl_find(const avl_tree_t *tree, const void *node, avl_index_t *where)
void lacf_free(void *buf)
#define LACF_DESTROY(ptr)
char ** strsplit(const char *input, const char *sep, bool_t skip_empty, size_t *num)
char * mkpathname(const char *comp,...)
char * file2str(const char *comp,...)
bool_t remove_directory(const char *dirname)
void lacf_strlcpy(char *dest, const char *src, size_t cap)
void free_strlist(char **comps, size_t num)
bool_t create_directory_recursive(const char *dirname)
static bool_t is_valid_lat(double lat)
static bool_t is_valid_alt_m(double alt_m)
bool_t file_exists(const char *path, bool_t *isdir)
static bool_t is_valid_lon(double lon)
void list_destroy(list_t *)
void * list_head(const list_t *)
void list_create(list_t *, size_t, size_t)
void * list_next(const list_t *, const void *)
void * list_remove_head(list_t *)
void list_insert_tail(list_t *, void *)
static void strip_space(char *line)
static char * safe_strdup(const char *str2)
static void * safe_calloc(size_t nmemb, size_t size)
#define ASSERT_MUTEX_HELD(mtx)
static void thread_set_name(const char *name)
static void mutex_destroy(mutex_t *mtx)
static void thread_join(thread_t *thrp)
static void mutex_enter(mutex_t *mtx)
static void mutex_exit(mutex_t *mtx)
static void mutex_init(mutex_t *mtx)
#define thread_create(thrp, start_proc, arg)