1 /*
2 Gpredict: Real-time satellite tracking and orbit prediction program
3
4 Copyright (C) 2001-2017 Alexandru Csete, OZ9AEC
5 Copyright (C) 2011-2012 Charles Suprin, AA1VS
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, visit http://www.fsf.org/
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include <build-config.h>
23 #endif
24
25 #ifdef HAS_LIBGPS
26 #include <gps.h>
27 #endif
28
29 #include <glib.h>
30 #include <glib/gi18n.h>
31
32 #include "config-keys.h"
33 #include "gpredict-utils.h"
34 #include "locator.h"
35 #include "orbit-tools.h"
36 #include "qth-data.h"
37 #include "sat-log.h"
38 #include "sgpsdp/sgp4sdp4.h"
39 #include "time-tools.h"
40
41 void qth_validate(qth_t * qth);
42
43
44 /**
45 * Read QTH data from file.
46 *
47 * \param filename The file to read from.
48 * \param qth Pointer to a qth_t data structure where the data will be stored.
49 * \return FALSE if an error occurred, TRUE otherwise.
50 *
51 * \note The function uses the new key=value file parser from glib.
52 */
qth_data_read(const gchar * filename,qth_t * qth)53 gint qth_data_read(const gchar * filename, qth_t * qth)
54 {
55 GError *error = NULL;
56 gchar *buff;
57 gchar **buffv;
58
59 qth->data = g_key_file_new();
60 g_key_file_set_list_separator(qth->data, ';');
61
62 /* bail out with error message if data can not be read */
63 g_key_file_load_from_file(qth->data, filename, G_KEY_FILE_KEEP_COMMENTS,
64 &error);
65 if (error != NULL)
66 {
67 g_key_file_free(qth->data);
68 qth->data = NULL;
69
70 sat_log_log(SAT_LOG_LEVEL_ERROR,
71 _("%s: Could not load data from %s (%s)"),
72 __func__, filename, error->message);
73
74 return FALSE;
75 }
76
77 /* send a debug message, then read data */
78 sat_log_log(SAT_LOG_LEVEL_DEBUG, _("%s: QTH data: %s"), __func__,
79 filename);
80
81 /*** FIXME: should check that strings are UTF-8? */
82 /* QTH Name */
83 buff = g_path_get_basename(filename);
84 buffv = g_strsplit(buff, ".qth", 0);
85 qth->name = g_strdup(buffv[0]);
86
87 g_free(buff);
88 g_strfreev(buffv);
89
90 /* QTH location */
91 qth->loc = g_key_file_get_string(qth->data, QTH_CFG_MAIN_SECTION,
92 QTH_CFG_LOC_KEY, &error);
93 if (error != NULL)
94 {
95 sat_log_log(SAT_LOG_LEVEL_INFO,
96 _("%s: QTH has no location (%s)."), __func__,
97 error->message);
98
99 qth->loc = g_strdup("");
100 g_clear_error(&error);
101 }
102
103 /* QTH description */
104 qth->desc = g_key_file_get_string(qth->data, QTH_CFG_MAIN_SECTION,
105 QTH_CFG_DESC_KEY, &error);
106 if ((qth->desc == NULL) || (error != NULL))
107 {
108 sat_log_log(SAT_LOG_LEVEL_INFO, _("%s: QTH has no description."),
109 __func__);
110
111 qth->desc = g_strdup("");
112 g_clear_error(&error);
113 }
114
115 /* Weather station */
116 qth->wx = g_key_file_get_string(qth->data, QTH_CFG_MAIN_SECTION,
117 QTH_CFG_WX_KEY, &error);
118 if ((qth->wx == NULL) || (error != NULL))
119 {
120 sat_log_log(SAT_LOG_LEVEL_INFO, _("%s: QTH has no weather station."),
121 __func__);
122
123 qth->wx = g_strdup("");
124 g_clear_error(&error);
125 }
126
127 /* QTH Latitude */
128 buff = g_key_file_get_string(qth->data, QTH_CFG_MAIN_SECTION,
129 QTH_CFG_LAT_KEY, &error);
130 if (error != NULL)
131 {
132 sat_log_log(SAT_LOG_LEVEL_ERROR,
133 _("%s: Error reading QTH latitude (%s)."), __func__,
134 error->message);
135
136 g_clear_error(&error);
137
138 if (buff != NULL)
139 g_free(buff);
140
141 qth->lat = 0.0;
142 }
143 else
144 {
145 qth->lat = g_ascii_strtod(buff, NULL);
146 g_free(buff);
147 }
148
149 /* QTH Longitude */
150 buff = g_key_file_get_string(qth->data, QTH_CFG_MAIN_SECTION,
151 QTH_CFG_LON_KEY, &error);
152 if (error != NULL)
153 {
154 sat_log_log(SAT_LOG_LEVEL_ERROR,
155 _("%s: Error reading QTH longitude (%s)."), __func__,
156 error->message);
157
158 g_clear_error(&error);
159
160 if (buff != NULL)
161 g_free(buff);
162
163 qth->lon = 0.0;
164 }
165 else
166 {
167 qth->lon = g_ascii_strtod(buff, NULL);
168 g_free(buff);
169 }
170
171 /* QTH Altitude */
172 qth->alt = g_key_file_get_integer(qth->data, QTH_CFG_MAIN_SECTION,
173 QTH_CFG_ALT_KEY, &error);
174 if (error != NULL)
175 {
176 sat_log_log(SAT_LOG_LEVEL_ERROR,
177 _("%s: Error reading QTH altitude (%s)."), __func__,
178 error->message);
179
180 g_clear_error(&error);
181
182 if (buff != NULL)
183 g_free(buff);
184
185 qth->alt = 0;
186 }
187
188 /* QTH Type */
189 qth->type = g_key_file_get_integer(qth->data, QTH_CFG_MAIN_SECTION,
190 QTH_CFG_TYPE_KEY, &error);
191 if (error != NULL)
192 {
193 sat_log_log(SAT_LOG_LEVEL_INFO,
194 _("%s: Error reading QTH type (%s)."), __func__,
195 error->message);
196
197 g_clear_error(&error);
198 qth->type = QTH_STATIC_TYPE;
199 }
200
201 #if HAS_LIBGPS
202 /* GPSD Port */
203 qth->gpsd_port = g_key_file_get_integer(qth->data,
204 QTH_CFG_MAIN_SECTION,
205 QTH_CFG_GPSD_PORT_KEY, &error);
206 if (error != NULL)
207 {
208 sat_log_log(SAT_LOG_LEVEL_ERROR,
209 _("%s: Error reading GPSD port (%s)."), __func__,
210 error->message);
211
212 g_clear_error(&error);
213 qth->gpsd_port = 2947;
214 }
215
216 /* GPSD Server */
217 qth->gpsd_server = g_key_file_get_string(qth->data,
218 QTH_CFG_MAIN_SECTION,
219 QTH_CFG_GPSD_SERVER_KEY, &error);
220 if ((qth->gpsd_server == NULL) || (error != NULL))
221 {
222 sat_log_log(SAT_LOG_LEVEL_INFO, _("%s: QTH has no GPSD Server."),
223 __func__);
224
225 qth->gpsd_server = g_strdup("");
226 g_clear_error(&error);
227 }
228 #endif
229
230 /* set QRA based on data */
231 if (longlat2locator(qth->lon, qth->lat, qth->qra, 2) != RIG_OK)
232 {
233 sat_log_log(SAT_LOG_LEVEL_INFO,
234 _("%s: Could not set QRA for %s at %f, %f."),
235 __func__, qth->name, qth->lon, qth->lat);
236 }
237
238 qth_validate(qth);
239
240 /* Now, send debug message and return */
241 sat_log_log(SAT_LOG_LEVEL_INFO,
242 _("%s: QTH data: %s, %.4f, %.4f, %d"),
243 __func__, qth->name, qth->lat, qth->lon, qth->alt);
244
245 return TRUE;
246 }
247
248
249 /**
250 * Save the QTH data to a file.
251 *
252 * \param filename The file to save to.
253 * \param qth Pointer to a qth_t data structure from which the data will be read.
254 */
qth_data_save(const gchar * filename,qth_t * qth)255 gint qth_data_save(const gchar * filename, qth_t * qth)
256 {
257 gchar *buff;
258 gint ok = 1;
259
260 qth->data = g_key_file_new();
261 g_key_file_set_list_separator(qth->data, ';');
262
263 /* description */
264 if (qth->desc && (g_utf8_strlen(qth->desc, -1) > 0))
265 {
266 g_key_file_set_string(qth->data, QTH_CFG_MAIN_SECTION,
267 QTH_CFG_DESC_KEY, qth->desc);
268 }
269
270 /* location */
271 if (qth->loc && (g_utf8_strlen(qth->loc, -1) > 0))
272 {
273 g_key_file_set_string(qth->data, QTH_CFG_MAIN_SECTION, QTH_CFG_LOC_KEY,
274 qth->loc);
275 }
276
277 /* latitude */
278 /* buff = g_strdup_printf ("%.4f", qth->lat); */
279 buff = g_malloc(10);
280 buff = g_ascii_dtostr(buff, 9, qth->lat);
281 g_key_file_set_string(qth->data, QTH_CFG_MAIN_SECTION, QTH_CFG_LAT_KEY,
282 buff);
283 g_free(buff);
284
285 /* longitude */
286 /* buff = g_strdup_printf ("%.4f", qth->lon); */
287 buff = g_malloc(10);
288 buff = g_ascii_dtostr(buff, 9, qth->lon);
289 g_key_file_set_string(qth->data, QTH_CFG_MAIN_SECTION, QTH_CFG_LON_KEY,
290 buff);
291 g_free(buff);
292
293 /* altitude */
294 g_key_file_set_integer(qth->data, QTH_CFG_MAIN_SECTION, QTH_CFG_ALT_KEY,
295 qth->alt);
296
297 /* weather station */
298 if (qth->wx && (g_utf8_strlen(qth->wx, -1) > 0))
299 {
300 g_key_file_set_string(qth->data, QTH_CFG_MAIN_SECTION, QTH_CFG_WX_KEY,
301 qth->wx);
302 }
303
304 /* qth type */
305 /* static, gpsd... */
306 g_key_file_set_integer(qth->data, QTH_CFG_MAIN_SECTION, QTH_CFG_TYPE_KEY,
307 qth->type);
308
309 #if HAS_LIBGPS
310 /* gpsd server */
311 if (qth->gpsd_server && (g_utf8_strlen(qth->gpsd_server, -1) > 0))
312 {
313 g_key_file_set_string(qth->data,
314 QTH_CFG_MAIN_SECTION, QTH_CFG_GPSD_SERVER_KEY,
315 qth->gpsd_server);
316 }
317 /* gpsd port */
318 g_key_file_set_integer(qth->data, QTH_CFG_MAIN_SECTION,
319 QTH_CFG_GPSD_PORT_KEY, qth->gpsd_port);
320 #endif
321
322 /* saving code */
323 ok = !(gpredict_save_key_file(qth->data, filename));
324
325 return ok;
326 }
327
qth_data_free(qth_t * qth)328 void qth_data_free(qth_t * qth)
329 {
330 /* stop any updating */
331 qth_data_update_stop(qth);
332
333 if (qth->name)
334 {
335 g_free(qth->name);
336 qth->name = NULL;
337 }
338
339 if (qth->loc)
340 {
341 g_free(qth->loc);
342 qth->loc = NULL;
343 }
344
345 if (qth->desc)
346 {
347 g_free(qth->desc);
348 qth->desc = NULL;
349 }
350
351 if (qth->qra)
352 {
353 g_free(qth->qra);
354 qth->qra = NULL;
355 }
356
357 if (qth->wx)
358 {
359 g_free(qth->wx);
360 qth->wx = NULL;
361 }
362
363 if (qth->data)
364 {
365 g_key_file_free(qth->data);
366 qth->data = NULL;
367 }
368
369 g_free(qth);
370 }
371
372 /**
373 * Update the qth data by whatever method is appropriate.
374 *
375 * \param qth the qth data structure to update
376 * \param qth the time at which the qth is to be computed. this may be ignored by gps updates.
377 */
qth_data_update(qth_t * qth,gdouble t)378 gboolean qth_data_update(qth_t * qth, gdouble t)
379 {
380 #ifndef HAS_LIBGPS
381 (void)qth;
382 (void)t;
383 return FALSE;
384 #else
385 gboolean retval = FALSE;
386 guint num_loops = 0;
387
388 if (qth->type != QTH_GPSD_TYPE)
389 return FALSE;
390
391 if (((t - qth->gpsd_update) > 30.0 / 86400.0) &&
392 (t - qth->gpsd_connected > 30.0 / 86400.0))
393 {
394 /* if needed restart the gpsd interface */
395 qth_data_update_stop(qth);
396 qth_data_update_init(qth);
397 qth->gpsd_connected = t;
398 }
399
400 if (qth->gps_data != NULL)
401 {
402 switch (GPSD_API_MAJOR_VERSION)
403 {
404 case 4:
405 #if GPSD_API_MAJOR_VERSION==4
406 while (gps_waiting(qth->gps_data) == TRUE)
407 {
408 /* this is a watchdog in case there is a problem with the gpsd code. */
409 /* if the server was up and has failed then gps_waiting in 2.92 confirmed */
410 /* will return 1 (supposedly fixed in later versions.) */
411 /* if we do not do this as a while loop, the gpsd packets can backup */
412 /* and no longer be in sync with the gps receiver */
413 num_loops++;
414 if (num_loops > 1000)
415 {
416 retval = FALSE;
417 break;
418 }
419 if (gps_poll(qth->gps_data) == 0)
420 {
421 /* handling packet_set inline with
422 http://gpsd.berlios.de/client-howto.html
423 */
424 if (qth->gps_data->set & PACKET_SET)
425 {
426 if (qth->gps_data->fix.mode >= MODE_2D)
427 {
428 if (qth->lat != qth->gps_data->fix.latitude)
429 {
430 qth->lat = qth->gps_data->fix.latitude;
431 retval = TRUE;
432 }
433 if (qth->lon != qth->gps_data->fix.longitude)
434 {
435 qth->lon = qth->gps_data->fix.longitude;
436 retval = TRUE;
437 }
438 }
439
440 if (qth->gps_data->fix.mode == MODE_3D)
441 {
442 if (qth->alt != qth->gps_data->fix.altitude)
443 {
444 qth->alt = qth->gps_data->fix.altitude;
445 retval = TRUE;
446 }
447 }
448 else
449 {
450 if (qth->alt != 0)
451 {
452 qth->alt = 0;
453 retval = TRUE;
454 }
455 }
456 }
457 }
458 }
459 #endif
460 break;
461 case 5:
462 #if GPSD_API_MAJOR_VERSION==5
463 while (gps_waiting(qth->gps_data, 0) == 1)
464 {
465 /* see comment from above */
466 /* hopefully not needed but does not hurt anything. */
467 num_loops++;
468 if (num_loops > 1000)
469 {
470 retval = FALSE;
471 break;
472 }
473 if (gps_read(qth->gps_data) == 0)
474 {
475 /* handling packet_set inline with
476 http://gpsd.berlios.de/client-howto.html
477 */
478 if (qth->gps_data->set & PACKET_SET)
479 {
480 if (qth->gps_data->fix.mode >= MODE_2D)
481 {
482 if (qth->lat != qth->gps_data->fix.latitude)
483 {
484 qth->lat = qth->gps_data->fix.latitude;
485 retval = TRUE;
486 }
487 if (qth->lon != qth->gps_data->fix.longitude)
488 {
489 qth->lon = qth->gps_data->fix.longitude;
490 retval = TRUE;
491 }
492 }
493
494 if (qth->gps_data->fix.mode == MODE_3D)
495 {
496 if (qth->alt != qth->gps_data->fix.altitude)
497 {
498 qth->alt = qth->gps_data->fix.altitude;
499 retval = TRUE;
500 }
501 }
502 else
503 {
504 if (qth->alt != 0)
505 {
506 qth->alt = 0;
507 retval = TRUE;
508 }
509 }
510 }
511 }
512 }
513 #endif
514 break;
515 default:
516 break;
517 }
518 if (retval == TRUE)
519 {
520 qth->gpsd_update = t;
521 }
522 }
523
524 qth_validate(qth);
525 if (longlat2locator(qth->lon, qth->lat, qth->qra, 2) != RIG_OK)
526 {
527 sat_log_log(SAT_LOG_LEVEL_ERROR,
528 _("%s: Could not set QRA for %s at %f, %f."),
529 __func__, qth->name, qth->lon, qth->lat);
530 }
531
532 return retval;
533 #endif // HAS_LIBGPS
534 }
535
536 /**
537 * Initialize whatever structures inside the qth_t stucture for later updates.
538 *
539 * \param qth the qth data structure to update
540 *
541 * Initial intention of this is to open sockets and ports to gpsd
542 * and other like services to update the qth position.
543 */
qth_data_update_init(qth_t * qth)544 gboolean qth_data_update_init(qth_t * qth)
545 {
546 #ifdef HAS_LIBGPS
547 char *port;
548 #endif
549 gboolean retval = FALSE;
550
551
552 if (qth->type != QTH_GPSD_TYPE)
553 return FALSE;
554
555 /** FIXME: refactor */
556
557 #ifdef HAS_LIBGPS
558 switch (GPSD_API_MAJOR_VERSION)
559 {
560 case 4:
561 #if GPSD_API_MAJOR_VERSION==4
562 /* open the connection to gpsd and start the stream */
563 qth->gps_data = g_try_new0(struct gps_data_t, 1);
564
565 port = g_strdup_printf("%d", qth->gpsd_port);
566 if (gps_open_r(qth->gpsd_server, port, qth->gps_data) == -1)
567 {
568 free(qth->gps_data);
569 qth->gps_data = NULL;
570 sat_log_log(SAT_LOG_LEVEL_ERROR,
571 _("%s: Could not open gpsd at %s:%d"),
572 __func__, qth->gpsd_server, qth->gpsd_port);
573 retval = FALSE;
574 }
575 else
576 {
577 (void)gps_stream(qth->gps_data, WATCH_ENABLE, NULL);
578 retval = TRUE;
579 }
580 g_free(port);
581 #endif
582 break;
583 case 5:
584 #if GPSD_API_MAJOR_VERSION==5
585 /* open the connection to gpsd and start the stream */
586 qth->gps_data = g_try_new0(struct gps_data_t, 1);
587
588 port = g_strdup_printf("%d", qth->gpsd_port);
589 if (gps_open(qth->gpsd_server, port, qth->gps_data) == -1)
590 {
591 free(qth->gps_data);
592 qth->gps_data = NULL;
593 sat_log_log(SAT_LOG_LEVEL_ERROR,
594 _("%s: Could not open gpsd at %s:%d"),
595 __func__, qth->gpsd_server, qth->gpsd_port);
596 retval = FALSE;
597 }
598 else
599 {
600 (void)gps_stream(qth->gps_data, WATCH_ENABLE, NULL);
601 retval = TRUE;
602 }
603 g_free(port);
604 #endif
605 break;
606 default:
607 sat_log_log(SAT_LOG_LEVEL_ERROR,
608 _("%s: Unsupported gpsd api major version (%d)"),
609 __func__, GPSD_API_MAJOR_VERSION);
610
611
612 return FALSE;
613 break;
614 }
615 #else
616 return FALSE;
617 #endif
618
619 return retval;
620 }
621
622 /**
623 * Shutdown and free structures inside the qth_t stucture were used for updates.
624 *
625 * \param qth the qth data structure to update
626 *
627 * Initial intention of this is to open sockets and ports to gpsd
628 * and other like services to update the qth position.
629 */
qth_data_update_stop(qth_t * qth)630 void qth_data_update_stop(qth_t * qth)
631 {
632 if (qth->type != QTH_GPSD_TYPE)
633 return;
634
635 /* close gpsd socket */
636 if (qth->gps_data != NULL)
637 {
638 #ifdef HAS_LIBGPS
639 switch (GPSD_API_MAJOR_VERSION)
640 {
641 case 4:
642 gps_close(qth->gps_data);
643 break;
644 case 5:
645 gps_close(qth->gps_data);
646 break;
647 default:
648 break;
649 }
650 #endif
651 qth->gps_data = NULL;
652 }
653 }
654
655 /**
656 * Load initial values into the qth_t data structure
657 *
658 * \param qth the qth data structure to update
659 */
qth_init(qth_t * qth)660 void qth_init(qth_t * qth)
661 {
662 qth->lat = 0;
663 qth->lon = 0;
664 qth->alt = 0;
665 qth->type = QTH_STATIC_TYPE;
666 qth->gps_data = NULL;
667 qth->name = NULL;
668 qth->loc = NULL;
669 qth->gpsd_port = 0;
670 qth->gpsd_server = NULL;
671 qth->gpsd_update = 0.0;
672 qth->gpsd_connected = 0.0;
673 qth->qra = g_strdup("AA00");
674 }
675
676 /**
677 * Load safe values into the qth_t data structure
678 *
679 * \param qth the qth data structure to update
680 *
681 * This can be used if some operation is suspected of corrupting the structure
682 * or entering invalid data. Originally it is based on code that reset values
683 * if a load/read of a .qth failed.
684 */
qth_safe(qth_t * qth)685 void qth_safe(qth_t * qth)
686 {
687 qth->name = g_strdup(_("Error"));
688 qth->loc = g_strdup(_("Error"));
689 qth->type = QTH_STATIC_TYPE;
690 qth->lat = 0;
691 qth->lon = 0;
692 qth->alt = 0;
693 qth->gps_data = NULL;
694 }
695
696 /**
697 * Copy values from qth structure to qth_small structure
698 *
699 * \param qth the qth data structure to backup
700 * \param qth_small the data structure to store values
701 *
702 * This is intended for copying only the minimal qth data to a structure for
703 * tagging and later comparison.
704 */
qth_small_save(qth_t * qth,qth_small_t * qth_small)705 void qth_small_save(qth_t * qth, qth_small_t * qth_small)
706 {
707 qth_small->lat = qth->lat;
708 qth_small->lon = qth->lon;
709 qth_small->alt = qth->alt;
710 }
711
712 /**
713 * Validate the contents of a qth structure and correct if neccessary
714 *
715 * \param qth the qth data structure to cleanup
716 */
qth_validate(qth_t * qth)717 void qth_validate(qth_t * qth)
718 {
719 /* check that the values are not set to nonsense such as nan or inf.
720 * if so error it and set to zero. */
721 if (!isnormal(qth->lat) && (qth->lat != 0))
722 {
723 sat_log_log(SAT_LOG_LEVEL_ERROR,
724 _("%s: QTH data had bogus lat %f"), __func__, qth->lat);
725 qth->lat = 0.0;
726
727 }
728 if (!isnormal(qth->lon) && (qth->lon != 0))
729 {
730 sat_log_log(SAT_LOG_LEVEL_ERROR,
731 _("%s: QTH data had bogus lon %f"), __func__, qth->lon);
732 qth->lon = 0.0;
733 }
734
735 /* check that qth->lat and qth->lon are in a reasonable range
736 and if not wrap them back */
737 if (fabs(qth->lat) > 90.0 || fabs(qth->lon) > 180.0)
738 {
739 sat_log_log(SAT_LOG_LEVEL_ERROR,
740 _
741 ("%s: File contained bogus QTH data. Correcting: %s, %.4f, %.4f, %d"),
742 __func__, qth->name, qth->lat, qth->lon, qth->alt);
743
744 qth->lat = fmod(qth->lat, 360.0);
745 while ((qth->lat) > 180.0)
746 {
747 qth->lat = qth->lat - 180.0;
748 qth->lon = qth->lon + 180.0;
749 }
750
751 while ((qth->lat) < -180.0)
752 {
753 qth->lat = qth->lat + 180.0;
754 qth->lon = qth->lon + 180.0;
755 }
756 if (qth->lat > 90.0)
757 {
758 qth->lat = 180.0 - qth->lat;
759 qth->lon = qth->lon + 180.0;
760 }
761
762 if (qth->lat < -90.0)
763 {
764 qth->lat = -180.0 - qth->lat;
765 qth->lon = qth->lon + 180.0;
766 }
767
768 qth->lon = fmod(qth->lon + 180.0, 360.0) - 180;
769 if (qth->lon < -180.0)
770 {
771 qth->lon += 360.0;
772 }
773 else if (qth->lon > 180.0)
774 {
775 qth->lon -= 360.0;
776 }
777 }
778 }
779
780 /**
781 * Compute the distance between a location in a qth_t structure and
782 * qth_small_t structure.
783 *
784 * \param qth the qth data structure
785 * \param qth_small the data structure
786 *
787 * This is intended for measuring distance between the current qth and
788 * the position that tagged some data in qth_small.
789 */
qth_small_dist(qth_t * qth,qth_small_t qth_small)790 double qth_small_dist(qth_t * qth, qth_small_t qth_small)
791 {
792 double distance, azimuth;
793
794 qrb(qth->lon, qth->lat, qth_small.lon, qth_small.lat, &distance, &azimuth);
795
796 return (distance);
797 }
798