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