]> git.itanic.dy.fi Git - maemo-mapper/commitdiff
Added basic APRS support - Can be disabled by removing definition of INCLUDE_APRS
authorcamel <rwilliams.uk@gmail.com>
Fri, 19 Dec 2008 18:28:23 +0000 (18:28 +0000)
committercamel <rwilliams.uk@gmail.com>
Fri, 19 Dec 2008 18:28:23 +0000 (18:28 +0000)
Added support for UK OS Grid system

git-svn-id: svn+ssh://garage/var/lib/gforge/svnroot/maemo-mapper/trunk@230 6c538b50-5814-0410-93ad-8bdf4c0149d1

33 files changed:
src/Makefile.am
src/aprs.c [new file with mode: 0644]
src/aprs.h [new file with mode: 0644]
src/aprs_decode.c [new file with mode: 0644]
src/aprs_decode.h [new file with mode: 0644]
src/aprs_display.c [new file with mode: 0644]
src/aprs_display.h [new file with mode: 0644]
src/aprs_kiss.c [new file with mode: 0644]
src/aprs_kiss.h [new file with mode: 0644]
src/aprs_message.c [new file with mode: 0644]
src/aprs_message.h [new file with mode: 0644]
src/cmenu.c
src/data.c
src/data.h
src/defines.h
src/display.c
src/display.h
src/gpsbt.c
src/hashtable.c [new file with mode: 0644]
src/hashtable.h [new file with mode: 0644]
src/hashtable_itr.c [new file with mode: 0644]
src/hashtable_itr.h [new file with mode: 0644]
src/hashtable_private.h [new file with mode: 0644]
src/input.c
src/main.c
src/maps.c
src/menu.c
src/poi.c
src/settings.c
src/settings.h
src/types.h
src/util.c
src/util.h

index b48e018946f766c0ca875aca98a792914e472c2d..e474aa1fbd5fd047f878a4b07f53c0d662ef4004 100644 (file)
@@ -40,6 +40,13 @@ maemo_mapper_SOURCES = \
        path.c \
        poi.c \
        settings.c \
        path.c \
        poi.c \
        settings.c \
-       util.c
+       util.c \
+       aprs_decode.c \
+       aprs.c \
+       hashtable.c \
+       hashtable_itr.c \
+       aprs_display.c \
+       aprs_message.c \
+       aprs_kiss.c
 
 # bin_SCRIPTS = maemo-mapper.sh
 
 # bin_SCRIPTS = maemo-mapper.sh
diff --git a/src/aprs.c b/src/aprs.c
new file mode 100644 (file)
index 0000000..76e62cb
--- /dev/null
@@ -0,0 +1,2121 @@
+/*
+ * 
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Maemo Mapper is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Maemo Mapper.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * 
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ * 
+ *  * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000  Frank Giannandrea
+ * Copyright (C) 2000-2007  The Xastir Group
+ * 
+ */
+
+#ifdef HAVE_CONFIG_H
+#    include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include "data.h"
+
+#include <pthread.h>
+
+#include <libgnomevfs/gnome-vfs.h>
+#include <libgnomevfs/gnome-vfs-inet-connection.h>
+#include <errno.h>
+#include "aprs_display.h"
+
+#ifndef LEGACY
+#    include <hildon/hildon-note.h>
+#    include <hildon/hildon-banner.h>
+#else
+#    include <hildon-widgets/hildon-note.h>
+#    include <hildon-widgets/hildon-banner.h>
+#endif
+
+#include "types.h"
+#include "data.h"
+#include "defines.h"
+
+#include "display.h"
+#include "aprs.h"
+#include "gps.h"
+#include "gpsbt.h"
+#include "path.h"
+#include "util.h"
+
+#include "aprs_decode.h"
+
+static volatile GThread*       _aprs_inet_thread = NULL;
+static volatile GThread*    _aprs_tty_thread = NULL;
+
+static GMutex*                                 _aprs_inet_init_mutex = NULL;
+static GMutex*                         _aprs_tty_init_mutex = NULL;
+
+static gint                            _aprs_rcvr_retry_count = 0;
+
+#define VERSIONFRM     "APRS"
+extern AprsDataRow*                    n_first;  // pointer to first element in name sorted station list
+
+
+
+TWriteBuffer _write_buffer[APRS_PORT_COUNT];
+
+gboolean send_line(gchar* text, gint text_len, TAprsPort port);
+
+static gboolean aprs_handle_error_idle(gchar *error)
+{
+    printf("%s(%s)\n", __PRETTY_FUNCTION__, error);
+
+    /* Ask for re-try. */
+    if(++_aprs_rcvr_retry_count > 2)
+    {
+        GtkWidget *confirm;
+        gchar buffer[BUFFER_SIZE];
+
+        /* Reset retry count. */
+        _aprs_rcvr_retry_count = 0;
+
+        snprintf(buffer, sizeof(buffer), "%s\nRetry?", error);
+        confirm = hildon_note_new_confirmation(GTK_WINDOW(_window), buffer);
+
+        aprs_server_disconnect();
+
+        if(GTK_RESPONSE_OK == gtk_dialog_run(GTK_DIALOG(confirm)))
+        {
+            aprs_server_connect(); /* Try again. */
+        }
+        else
+        {
+            /* Reset Connect to APRS menu item. */
+            gtk_check_menu_item_set_active(
+                    GTK_CHECK_MENU_ITEM(_menu_enable_aprs_inet_item), FALSE);
+        }
+
+        /* Ask user to re-connect. */
+        gtk_widget_destroy(confirm);
+    }
+    else
+    {
+        aprs_server_disconnect();
+        aprs_server_connect(); /* Try again. */
+    }
+
+    g_free(error);
+
+    vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
+    return FALSE;
+}
+
+
+/**
+ * Set the connection state.  This function controls all connection-related
+ * banners.
+ */
+void set_aprs_tty_conn_state(ConnState new_conn_state)
+{
+    printf("%s(%d)\n", __PRETTY_FUNCTION__, new_conn_state);
+
+    switch(_aprs_tty_state = new_conn_state)
+    {
+        case RCVR_OFF:
+        case RCVR_FIXED:
+        case RCVR_UP:  
+          if(_connect_banner)
+            {
+                gtk_widget_destroy(_connect_banner);
+                _connect_banner = NULL;
+            }
+            break;
+        case RCVR_DOWN:
+            if(!_connect_banner)
+                _connect_banner = hildon_banner_show_animation(
+                        _window, NULL, _("Attempting to connect to TNC"));
+            break;
+
+        default: ; /* to quell warning. */
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+/**
+ * Set the connection state.  This function controls all connection-related
+ * banners.
+ */
+void set_aprs_inet_conn_state(ConnState new_conn_state)
+{
+    printf("%s(%d)\n", __PRETTY_FUNCTION__, new_conn_state);
+
+    switch(_aprs_inet_state = new_conn_state)
+    {
+        case RCVR_OFF:
+        case RCVR_FIXED:
+        case RCVR_UP:  
+          if(_connect_banner)
+            {
+                gtk_widget_destroy(_connect_banner);
+                _connect_banner = NULL;
+            }
+            if(_fix_banner)
+            {
+                gtk_widget_destroy(_fix_banner);
+                _fix_banner = NULL;
+            }
+            break;
+        case RCVR_DOWN:
+            if(_fix_banner)
+            {
+                gtk_widget_destroy(_fix_banner);
+                _fix_banner = NULL;
+            }
+            if(!_connect_banner)
+                _connect_banner = hildon_banner_show_animation(
+                        _window, NULL, _("Attempting to connect to APRS server"));
+            break;
+
+        default: ; /* to quell warning. */
+    }
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+
+static gboolean aprs_parse_server_packet(gchar *packet)
+{
+    decode_ax25_line(packet, APRS_PORT_INET);
+
+    g_free(packet);
+
+    return FALSE;
+}
+
+
+void update_aprs_inet_options(gboolean force)
+{
+       // If auto filter is not or we are not connected then stop
+       if(!_aprs_server_auto_filter_on 
+                       || !_aprs_enable 
+                       || !_aprs_inet_enable 
+                       || _aprs_server_auto_filter_km <= 0) return ;
+       
+
+       // Disconnect
+       aprs_server_disconnect();
+       //Re-connect
+       aprs_server_connect();
+       
+}
+
+gchar *create_aprs_inet_options_string()
+{
+       gint current_lat = (gint)round(_gps.lat);
+       gint current_lon = (gint)round(_gps.lon);
+       gchar *filter = NULL;
+       
+       filter = g_strdup_printf("user %s pass %s vers %s v%s filter r/%d/%d/%d \r\n ",
+                       _aprs_mycall, _aprs_inet_server_validation, PACKAGE, VERSION,
+                       current_lat, current_lon, _aprs_server_auto_filter_km );
+       
+       return filter;
+}
+
+
+static void thread_read_server()
+{
+    gchar buf[APRS_BUFFER_SIZE];
+    gchar *buf_curr = buf;
+    gchar *buf_last = buf + sizeof(buf) - 1;
+    GnomeVFSFileSize bytes_read;
+    GnomeVFSResult vfs_result;
+    GnomeVFSInetConnection *iconn = NULL;
+    GnomeVFSSocket *socket = NULL;
+    GThread *my_thread = g_thread_self();
+    gboolean error = FALSE;
+
+    printf("%s(%s)\n", __PRETTY_FUNCTION__, _aprs_server);
+
+    
+    //fprintf(stderr, "Starting thread...\n");
+    
+    /* Lock/Unlock the mutex to ensure that _aprs_inet_thread is done being set. */
+    g_mutex_lock(_aprs_inet_init_mutex);
+    g_mutex_unlock(_aprs_inet_init_mutex);
+    
+    if(!error && my_thread == _aprs_inet_thread && _aprs_inet_thread != NULL)
+    {
+        gint tryno;
+
+        /* Attempt to connect to APRS server. */
+        for(tryno = 0; tryno < 10; tryno++)
+        {
+            /* Create a socket to interact with server. */
+            GTimeVal timeout = { 1000, 0 };
+            gchar *filter = create_aprs_inet_options_string();
+            //fprintf(stderr, filter);              
+
+            if(GNOME_VFS_OK != (vfs_result = gnome_vfs_inet_connection_create(
+                            &iconn,
+                            _aprs_server,
+                            _aprs_server_port,
+                            NULL))
+               || NULL == ( socket = gnome_vfs_inet_connection_to_socket(iconn))
+               || GNOME_VFS_OK != (vfs_result = gnome_vfs_socket_set_timeout(
+                          socket, &timeout, NULL))
+               || GNOME_VFS_OK != (vfs_result = gnome_vfs_socket_write( socket,
+                                               filter, strlen(filter), &bytes_read, NULL))
+              )
+            {
+               g_free(filter);
+                sleep(1);
+            }
+            else
+            {
+               g_free(filter);
+                break;
+            }
+        }
+
+
+        if(!iconn)
+        {
+            g_printerr("Error connecting to APRS server: (%d) %s\n",
+                    vfs_result, gnome_vfs_result_to_string(vfs_result));
+            g_idle_add((GSourceFunc)aprs_handle_error_idle,
+                    g_strdup_printf("%s",
+                    _("Error connecting to APRS server.")));
+            error = TRUE;
+        }
+    }
+    
+
+    if(!error && my_thread == _aprs_inet_thread && _aprs_inet_thread != NULL)
+    {
+       
+        set_aprs_inet_conn_state(RCVR_UP);
+
+        while(my_thread == _aprs_inet_thread && _aprs_inet_thread != NULL)
+        {
+            gchar *eol;
+            
+               
+            vfs_result = gnome_vfs_socket_read( 
+                       socket,
+                    buf,
+                    buf_last - buf_curr,
+                    &bytes_read,
+                    NULL);
+            
+  
+            if(vfs_result != GNOME_VFS_OK)
+            {
+                if(my_thread == _aprs_inet_thread)
+                {
+                    // Error wasn't user-initiated. 
+                    g_idle_add((GSourceFunc)aprs_handle_error_idle,
+                            g_strdup_printf("%s %u",
+                                _("Error reading APRS data."), vfs_result));
+
+                }
+
+                fprintf(stderr, "Read error: %s\n", gnome_vfs_result_to_string(vfs_result));
+                error = TRUE;
+                break;
+            }
+
+            /* Loop through the buffer and read each packet. */
+            buf_curr += bytes_read;
+            *buf_curr = '\0'; /* append a \0 so we can read as string */
+            while(!error && my_thread == _aprs_inet_thread && _aprs_inet_thread != NULL 
+                       && (eol = strchr(buf, '\n')))
+            {
+                /* This is the beginning of a sentence; okay to parse. */
+                *eol = '\0'; /* overwrite \n with \0 */
+
+                if(my_thread == _aprs_inet_thread)
+                       //g_idle_add_full(G_PRIORITY_HIGH, (GSourceFunc)aprs_parse_server_packet, g_strdup(buf), NULL );
+                    g_idle_add((GSourceFunc)aprs_parse_server_packet, g_strdup(buf));
+
+                /* If eol is at or after (buf_curr - 1) */
+                if(eol >= (buf_curr - 1))
+                {
+                    /* Last read was a newline - reset read buffer */
+                    buf_curr = buf;
+                    *buf_curr = '\0';
+                }
+                else
+                {
+                    /* Move the next line to the front of the buffer. */
+                    memmove(buf, eol + 1,
+                            buf_curr - eol); /* include terminating 0 */
+                    /* Subtract _curr so that it's pointing at the new \0. */
+                    buf_curr -= (eol - buf + 1);
+                }
+            }
+            _aprs_rcvr_retry_count = 0;
+   
+            // Send any packets queued
+                       // try to get lock, otherwise try next time
+                       if(g_mutex_trylock (_write_buffer[APRS_PORT_INET].write_lock))
+                       {
+                       // Store the current end pointer as it may change
+                               
+                gint quantity = 0;
+                gchar tmp_write_buffer[MAX_DEVICE_BUFFER];
+                while (_write_buffer[APRS_PORT_INET].write_in_pos != _write_buffer[APRS_PORT_INET].write_out_pos) {
+
+                       tmp_write_buffer[quantity] = _write_buffer[APRS_PORT_INET].device_write_buffer[_write_buffer[APRS_PORT_INET].write_out_pos];
+
+                       _write_buffer[APRS_PORT_INET].write_out_pos++;
+                    if (_write_buffer[APRS_PORT_INET].write_out_pos >= MAX_DEVICE_BUFFER)
+                       _write_buffer[APRS_PORT_INET].write_out_pos = 0;
+
+                    quantity++;
+                }
+
+                if(quantity>0)
+                {
+                       sleep(2);
+                       
+                       GnomeVFSFileSize bytes_read = 0;                                    
+                                       if(GNOME_VFS_OK == gnome_vfs_socket_write( socket,
+                                                       tmp_write_buffer, quantity, &bytes_read, NULL))
+                                       {
+                                               // OK
+                                               //fprintf(stderr, "Send packet success: %s (%u)\n", tmp_write_buffer, quantity);
+                                       }
+                                       else
+                                       {
+                                               // Failed
+                                               fprintf(stderr, "Failed to send packet: %s (%u)\n", tmp_write_buffer, quantity);
+                                       }
+                                       
+                       sleep(1);
+                }
+                               
+                   g_mutex_unlock(_write_buffer[APRS_PORT_INET].write_lock);
+                       }            
+
+        }
+        
+        //fprintf(stderr, "Exiting thread...\n");
+    }
+
+    /* Error, or we're done reading APRS data. */
+
+    /* Clean up. */
+    if(iconn)
+       gnome_vfs_inet_connection_destroy(iconn, NULL);
+        //gnome_vfs_inet_connection_free(iconn, NULL);
+    
+    iconn = NULL;
+    
+    //g_thread_exit(0);
+    
+    
+    printf("%s(): return\n", __PRETTY_FUNCTION__);
+    
+    return;
+}
+
+/**
+ * Disconnect from the receiver.  This method cleans up any and everything
+ * that might be associated with the receiver.
+ */
+void aprs_server_disconnect()
+{
+    gboolean exit_now = FALSE;
+
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    GThread *my_thread = g_thread_self();
+
+    if(my_thread == _aprs_inet_thread)
+    {
+        exit_now = TRUE;
+    }
+
+    g_mutex_lock(_aprs_inet_init_mutex);
+    _aprs_inet_thread = NULL;
+    g_mutex_unlock(_aprs_inet_init_mutex);
+
+    
+    
+    if(_window)
+        set_aprs_inet_conn_state(RCVR_OFF);
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+
+    if(exit_now) 
+    {
+       fprintf(stderr, "Stopping own thread - APRS server\n");
+       exit(0);
+       fprintf(stderr, "Stop Failed\n");
+       
+    }
+}
+
+/**
+ * Connect to the server.
+ * This method assumes that _fd is -1 and _channel is NULL.  If unsure, call
+ * rcvr_disconnect() first.
+ * Since this is an idle function, this function returns whether or not it
+ * should be called again, which is always FALSE.
+ */
+gboolean aprs_server_connect()
+{
+    printf("%s(%d)\n", __PRETTY_FUNCTION__, _aprs_inet_state);
+
+    if(_aprs_inet_enable && _aprs_inet_state == RCVR_OFF)
+    {
+        set_aprs_inet_conn_state(RCVR_DOWN);
+
+        /* Lock/Unlock the mutex to ensure that the thread doesn't
+         * start until _gps_thread is set. */
+        g_mutex_lock(_aprs_inet_init_mutex);
+
+        
+        _aprs_inet_thread = g_thread_create((GThreadFunc)thread_read_server,
+                NULL, TRUE, NULL); /* Joinable. */
+        
+//        g_thread_set_priority(_aprs_inet_thread, G_THREAD_PRIORITY_LOW);
+        
+        g_mutex_unlock(_aprs_inet_init_mutex);
+    }
+
+    vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
+    return FALSE;
+}
+
+void aprs_init()
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    _aprs_inet_init_mutex = g_mutex_new();
+    _aprs_tty_init_mutex  = g_mutex_new();
+    _write_buffer[APRS_PORT_INET].write_lock = g_mutex_new();
+    _write_buffer[APRS_PORT_TTY].write_lock = g_mutex_new();
+
+    vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
+}
+
+void aprs_destroy(gboolean last)
+{
+    static GThread* tmp = NULL;
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(!last)
+    {
+        if(_aprs_inet_thread)
+        {
+            tmp = (GThread*)_aprs_inet_thread;
+            _aprs_inet_thread = NULL;
+        }
+    }
+    else if(tmp)
+        g_thread_join(tmp);
+
+    vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
+}
+
+gboolean select_aprs(gint unitx, gint unity, gboolean quick)
+{      
+    gint x, y;
+    gdouble lat1, lon1, lat2, lon2;
+    static GtkWidget *dialog = NULL;
+    static GtkWidget *list = NULL;
+    static GtkWidget *sw = NULL;
+    static GtkTreeViewColumn *column = NULL;
+    static GtkCellRenderer *renderer = NULL;
+    GtkListStore *store = NULL;
+    GtkTreeIter iter;
+    gboolean selected = FALSE;
+    gint num_stations = 0;
+    AprsStationList *first_station = NULL;
+    AprsStationList *last_station = NULL;
+
+
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    x = unitx - pixel2unit(3 * _draw_width);
+    y = unity + pixel2unit(3 * _draw_width);
+    unit2latlon(x, y, lat1, lon1);
+
+    x = unitx + pixel2unit(3 * _draw_width);
+    y = unity - pixel2unit(3 * _draw_width);
+    unit2latlon(x, y, lat2, lon2);
+    gdouble lat, lon;
+
+    
+    AprsDataRow *p_station = (AprsDataRow *)n_first;
+
+    // Look for all stations in selected area
+    while ( (p_station) != NULL) 
+    { 
+        lat = convert_lat_l2d(p_station->coord_lat);
+        lon = convert_lon_l2d(p_station->coord_lon);
+
+        if ( ( lat2 >= lat && lat >= lat1 ) && (lon2 >= lon && lon >= lon1) )
+        {
+            // This may have been clicked on
+               AprsStationList * p_list_item = (AprsStationList *)malloc(sizeof(AprsStationList));
+
+               p_list_item->station = p_station;
+               p_list_item->next = NULL;
+               
+               if(first_station == NULL)
+               {
+
+                       first_station = p_list_item;
+                       last_station = p_list_item;
+               }
+               else
+               {
+                       last_station->next = p_list_item;
+                       last_station = p_list_item;
+               }
+               
+               num_stations++;
+        }
+
+        (p_station) = (p_station)->n_next;  // Next element in list
+    } // End of while loop
+
+    selected = FALSE;
+    
+    if(num_stations==0)
+    {
+       // No station found, maybe a POI was selected?
+    }
+    else if(num_stations == 1)
+    {
+       // Only one station was found, so display it's info
+       if(first_station->station != NULL)
+       {
+               ShowAprsStationPopup(first_station->station);
+       }
+       selected = TRUE;
+    }
+    else
+    {
+       // Multiple possibilities, therefore ask the user which one
+       
+       // Initialize store. 
+       store = gtk_list_store_new(APRSPOI_NUM_COLUMNS,
+                               G_TYPE_BOOLEAN, //Selected 
+                               G_TYPE_STRING // Callsign
+                        );
+
+       AprsStationList * p_list_item = first_station;
+       
+       while(p_list_item != NULL)
+       {
+               if(p_list_item->station != NULL)
+               {
+                       gtk_list_store_append(store, &iter);
+       
+                       gtk_list_store_set(store, &iter,
+                           APRSPOI_CALLSIGN, g_strdup(p_list_item->station->call_sign),
+                           -1);
+               }
+               p_list_item = p_list_item->next;
+       }
+  
+       
+           if(dialog == NULL)
+           {
+               dialog = gtk_dialog_new_with_buttons(_("Select POI"),
+                       GTK_WINDOW(_window), GTK_DIALOG_MODAL,
+                       GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+                       GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+                       NULL);
+       
+               gtk_window_set_default_size(GTK_WINDOW(dialog), 500, 300);
+       
+               sw = gtk_scrolled_window_new (NULL, NULL);
+               gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+                       GTK_SHADOW_ETCHED_IN);
+               gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+                       GTK_POLICY_NEVER,
+                       GTK_POLICY_AUTOMATIC);
+               gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
+                       sw, TRUE, TRUE, 0);
+       
+               list = gtk_tree_view_new();
+               gtk_container_add(GTK_CONTAINER(sw), list);
+       
+               gtk_tree_selection_set_mode(
+                       gtk_tree_view_get_selection(GTK_TREE_VIEW(list)),
+                       GTK_SELECTION_SINGLE);
+               gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), TRUE);
+
+               renderer = gtk_cell_renderer_text_new();
+               column = gtk_tree_view_column_new_with_attributes(
+                       _("Callsign"), renderer, "text", APRSPOI_CALLSIGN, NULL);
+               gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
+
+           }
+
+           
+           gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));
+           g_object_unref(G_OBJECT(store));
+       
+           gtk_widget_show_all(dialog);
+           
+           if(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog)))
+           {
+               if(gtk_tree_selection_get_selected(
+                    gtk_tree_view_get_selection(GTK_TREE_VIEW(list)),
+                    NULL, &iter))
+               {
+                       // Find the callsign
+                       p_list_item = first_station;
+                       while(p_list_item != NULL)
+                       {
+                               if(p_list_item->station != NULL)
+                               {       
+                                       gchar * callsign = NULL;
+                                   gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
+                                       APRSPOI_CALLSIGN, &(callsign),
+                                       -1);
+       
+                                   if(strcmp(p_list_item->station->call_sign,callsign) == 0)
+                                       {
+                                       gtk_widget_hide(dialog);
+                                       
+                                               ShowAprsStationPopup(p_list_item->station);
+                                               selected = TRUE;
+                                               break;
+                                               
+                                       }
+                               }
+                               
+                               p_list_item = p_list_item->next;
+                       }
+
+               }
+
+           }
+
+       // Ensure it has been closed
+       gtk_widget_hide(dialog);
+    }
+
+
+    // Free the list, but not the stations
+    if(first_station)
+    {
+           AprsStationList * p_list_item = first_station;
+           
+           while(first_station)
+           {
+               // Store pointer to delete contents after next pointer is stored        
+               p_list_item = first_station;
+           
+               // Move pointer to next
+               first_station = p_list_item->next;
+               
+               free(p_list_item);
+           }
+    }
+
+
+    vprintf("%s(): return %d\n", __PRETTY_FUNCTION__, selected);
+    return selected;
+
+}
+
+
+
+//*****************************************************************
+// distance_from_my_station - compute distance from my station and
+//       course with a given call
+//
+// return distance and course
+//
+// Returns 0.0 for distance if station not found in database or the
+// station hasn't sent out a posit yet.
+//*****************************************************************
+
+double distance_from_my_station(char *call_sign, gchar *course_deg, gint course_len) {
+    AprsDataRow *p_station;
+    double distance;
+    float value;
+    double d_lat, d_lon;
+
+    distance = 0.0;
+    p_station = NULL;
+    if (search_station_name(&p_station,call_sign,1)) {
+        // Check whether we have a posit yet for this station
+        if ( (p_station->coord_lat == 0l)
+                && (p_station->coord_lon == 0l) ) {
+            distance = 0.0;
+        }
+        else {
+               d_lat = convert_lat_l2d(p_station->coord_lat);
+               d_lon = convert_lon_l2d(p_station->coord_lon);
+               
+               value = (float)calculate_distance(_gps.lat, _gps.lon, d_lat, d_lon);
+               
+               snprintf(course_deg,  course_len,
+                               "%.01f°",
+                               calculate_bearing(_gps.lat, _gps.lon, d_lat, d_lon));
+               
+            if(_units == UNITS_KM)
+               distance = value * 1.852;           // nautical miles to km
+            else if(_units == UNITS_MI)
+                distance = value * 1.15078;         // nautical miles to miles
+            else if(_units == UNITS_NM)
+               distance = value; 
+            else
+                distance = 0.0; // Should be unreachable
+            
+                
+        }
+    }
+    else {  // Station not found
+        distance = 0.0;
+    }
+
+
+    return(distance);
+}
+
+
+
+void pad_callsign(char *callsignout, char *callsignin) {
+    int i,l;
+
+    l=(int)strlen(callsignin);
+    for(i=0; i<9;i++) {
+        if(i<l) {
+            if(isalnum((int)callsignin[i]) || callsignin[i]=='-') {
+                callsignout[i]=callsignin[i];
+            }
+            else {
+                callsignout[i] = ' ';
+            }
+        }
+        else {
+            callsignout[i] = ' ';
+        }
+    }
+    callsignout[i] = '\0';
+}
+/////////// TX functionality
+
+
+
+// This routine changes callsign chars to proper uppercase chars or
+// numerals, fixes the callsign to six bytes, shifts the letters left by
+// one bit, and puts the SSID number into the proper bits in the seventh
+// byte.  The callsign as processed is ready for inclusion in an
+// AX.25 header.
+//
+void fix_up_callsign(unsigned char *data, int data_size) {
+    unsigned char new_call[8] = "       ";  // Start with seven spaces
+    int ssid = 0;
+    int i;
+    int j = 0;
+    int digipeated_flag = 0;
+
+
+    // Check whether we've digipeated through this callsign yet.
+    if (strstr((const char *)data,"*") != 0) {
+         digipeated_flag++;
+    }
+
+    // Change callsign to upper-case and pad out to six places with
+    // space characters.
+    for (i = 0; i < (int)strlen((const char *)data); i++) {
+        toupper(data[i]);
+
+        if (data[i] == '-') {   // Stop at '-'
+            break;
+        }
+        else if (data[i] == '*') {
+        }
+        else {
+            new_call[j++] = data[i];
+        }
+    }
+    new_call[7] = '\0';
+
+    //fprintf(stderr,"new_call:(%s)\n",new_call);
+
+    // Handle SSID.  'i' should now be pointing at a dash or at the
+    // terminating zero character.
+    if ( (i < (int)strlen((const char *)data)) && (data[i++] == '-') ) {   // We might have an SSID
+        if (data[i] != '\0')
+            ssid = atoi((const char *)&data[i]);
+//            ssid = data[i++] - 0x30;    // Convert from ascii to int
+//        if (data[i] != '\0')
+//            ssid = (ssid * 10) + (data[i] - 0x30);
+    }
+
+//fprintf(stderr,"SSID:%d\t",ssid);
+
+    if (ssid >= 0 && ssid <= 15) {
+        new_call[6] = ssid | 0x30;  // Set 2 reserved bits
+    }
+    else {  // Whacko SSID.  Set it to zero
+        new_call[6] = 0x30;     // Set 2 reserved bits
+    }
+
+    if (digipeated_flag) {
+        new_call[6] = new_call[6] | 0x40; // Set the 'H' bit
+    }
+    // Shift each byte one bit to the left
+    for (i = 0; i < 7; i++) {
+        new_call[i] = new_call[i] << 1;
+        new_call[i] = new_call[i] & 0xfe;
+    }
+
+//fprintf(stderr,"Last:%0x\n",new_call[6]);
+
+    // Write over the top of the input string with the newly
+    // formatted callsign
+    xastir_snprintf((char *)data,
+        data_size,
+        "%s",
+        new_call);
+}
+
+
+
+
+// Create an AX25 frame and then turn it into a KISS packet.  Dump
+// it into the transmit queue.
+//
+void send_ax25_frame(TAprsPort port, gchar *source, gchar *destination, gchar *path, gchar *data) {
+    unsigned char temp_source[15];
+    unsigned char temp_dest[15];
+    unsigned char temp[15];
+    unsigned char control[2], pid[2];
+    unsigned char transmit_txt[MAX_LINE_SIZE*2];
+    unsigned char transmit_txt2[MAX_LINE_SIZE*2];
+    unsigned char c;
+    int i, j;
+    int erd;
+    int write_in_pos_hold;
+
+
+//fprintf(stderr,"KISS String:%s>%s,%s:%s\n",source,destination,path,data);
+
+    // Check whether transmits are disabled globally
+//    if (transmit_disable) {
+//        return;
+//    }    
+
+    // Check whether transmit has been enabled for this interface.
+    // If not, get out while the gettin's good.
+//    if (devices[port].transmit_data != 1) {
+//        return;
+//    }
+
+    transmit_txt[0] = '\0';
+
+    // Format the destination callsign
+    snprintf((char *)temp_dest,
+        sizeof(temp_dest),
+        "%s",
+        destination);
+    fix_up_callsign(temp_dest, sizeof(temp_dest));
+    
+    snprintf((char *)transmit_txt,
+        sizeof(transmit_txt),
+        "%s",
+        temp_dest);
+
+    // Format the source callsign
+    snprintf((char *)temp_source,
+        sizeof(temp_source),
+        "%s",
+        source);
+    fix_up_callsign(temp_source, sizeof(temp_source));
+    
+    strncat((char *)transmit_txt,
+        (char *)temp_source,
+        sizeof(transmit_txt) - strlen((char *)transmit_txt));
+
+    // Break up the path into individual callsigns and send them one
+    // by one to fix_up_callsign().  If we get passed an empty path,
+    // we merely skip this section and no path gets added to
+    // "transmit_txt".
+    j = 0;
+    temp[0] = '\0'; // Start with empty path
+    if ( (path != NULL) && (strlen(path) != 0) ) {
+        while (path[j] != '\0') {
+            i = 0;
+            while ( (path[j] != ',') && (path[j] != '\0') ) {
+                temp[i++] = path[j++];
+            }
+            temp[i] = '\0';
+
+            if (path[j] == ',') {   // Skip over comma
+                j++;
+            }
+
+            fix_up_callsign(temp, sizeof(temp));
+            strncat((char *)transmit_txt,
+                (char *)temp,
+                sizeof(transmit_txt) - strlen((char *)transmit_txt));
+        }
+    }
+
+    // Set the end-of-address bit on the last callsign in the
+    // address field
+    transmit_txt[strlen((const char *)transmit_txt) - 1] |= 0x01;
+
+    // Add the Control byte
+    control[0] = 0x03;
+    control[1] = '\0';
+    strncat((char *)transmit_txt,
+        (char *)control,
+        sizeof(transmit_txt) - strlen((char *)transmit_txt));
+
+    // Add the PID byte
+    pid[0] = 0xf0;
+    pid[1] = '\0';
+    strncat((char *)transmit_txt,
+        (char *)pid,
+        sizeof(transmit_txt) - strlen((char *)transmit_txt));
+
+    // Append the information chars
+    strncat((char *)transmit_txt,
+        data,
+        sizeof(transmit_txt) - strlen((char *)transmit_txt));
+
+    //fprintf(stderr,"%s\n",transmit_txt);
+
+    // Add the KISS framing characters and do the proper escapes.
+    j = 0;
+    transmit_txt2[j++] = KISS_FEND;
+
+    // Note:  This byte is where different interfaces would be
+    // specified:
+    transmit_txt2[j++] = 0x00;
+
+    for (i = 0; i < (int)strlen((const char *)transmit_txt); i++) {
+        c = transmit_txt[i];
+        if (c == KISS_FEND) {
+            transmit_txt2[j++] = KISS_FESC;
+            transmit_txt2[j++] = KISS_TFEND;
+        }
+        else if (c == KISS_FESC) {
+            transmit_txt2[j++] = KISS_FESC;
+            transmit_txt2[j++] = KISS_TFESC;
+        }
+        else {
+            transmit_txt2[j++] = c;
+        }
+    }
+    transmit_txt2[j++] = KISS_FEND;
+
+    // Terminate the string, but don't increment the 'j' counter.
+    // We don't want to send the NULL byte out the KISS interface,
+    // just make sure the string is terminated in all cases.
+    //
+    transmit_txt2[j] = '\0';
+
+//-------------------------------------------------------------------
+// Had to snag code from port_write_string() below because our string
+// needs to have 0x00 chars inside it.  port_write_string() can't
+// handle that case.  It's a good thing the transmit queue stuff
+// could handle it.
+//-------------------------------------------------------------------
+
+    erd = 0;
+
+
+    
+
+       port_write_string(
+                       transmit_txt2,
+                       j/*length*/,
+               APRS_PORT_TTY);
+
+       
+       
+/*
+    g_mutex_lock (_write_buffer[port].write_lock);
+    {
+       
+           write_in_pos_hold = _write_buffer[port].write_in_pos;
+       
+           for (i = 0; i < j && !erd; i++) {
+               _write_buffer[port].device_write_buffer[_write_buffer[port].write_in_pos++] = transmit_txt2[i];
+               if (_write_buffer[port].write_in_pos >= MAX_DEVICE_BUFFER)
+                       _write_buffer[port].write_in_pos = 0;
+       
+               if (_write_buffer[port].write_in_pos == _write_buffer[port].write_out_pos) {
+       
+                   // clear this restore original write_in pos and dump this string 
+                       _write_buffer[port].write_in_pos = write_in_pos_hold;
+                       _write_buffer[port].errors++;
+                   erd = 1;
+               }
+           }
+           
+           g_mutex_unlock (_write_buffer[port].write_lock);
+    }
+*/
+}
+
+
+
+
+// convert latitude from long to string 
+// Input is in Xastir coordinate system
+//
+// CONVERT_LP_NOSP      = DDMM.MMN
+// CONVERT_HP_NOSP      = DDMM.MMMN
+// CONVERT_VHP_NOSP     = DDMM.MMMMN
+// CONVERT_LP_NORMAL    = DD MM.MMN
+// CONVERT_HP_NORMAL    = DD MM.MMMN
+// CONVERT_UP_TRK       = NDD MM.MMMM
+// CONVERT_DEC_DEG      = DD.DDDDDN
+// CONVERT_DMS_NORMAL   = DD MM SS.SN
+// CONVERT_DMS_NORMAL_FORMATED   = DD'MM'SS.SN
+// CONVERT_HP_NORMAL_FORMATED   = DD'MM.MMMMN
+//
+void convert_lat_l2s(long lat, char *str, int str_len, int type) {
+    char ns;
+    float deg, min, sec;
+    int ideg, imin;
+    long temp;
+
+
+    str[0] = '\0';
+    deg = (float)(lat - 32400000l) / 360000.0;
+    // Switch to integer arithmetic to avoid floating-point
+    // rounding errors.
+    temp = (long)(deg * 100000);
+
+    ns = 'S';
+    if (temp <= 0) {
+        ns = 'N';
+        temp = labs(temp);
+    }   
+
+    ideg = (int)temp / 100000;
+    min = (temp % 100000) * 60.0 / 100000.0;
+
+    // Again switch to integer arithmetic to avoid floating-point
+    // rounding errors.
+    temp = (long)(min * 1000);
+    imin = (int)(temp / 1000);
+    sec = (temp % 1000) * 60.0 / 1000.0;
+
+    switch (type) {
+
+        case(CONVERT_LP_NOSP): /* do low P w/no space */
+            xastir_snprintf(str,
+                str_len,
+                "%02d%05.2f%c",
+                ideg,
+//                min+0.001, // Correct possible unbiased rounding
+                min,
+                ns);
+            break;
+
+        case(CONVERT_LP_NORMAL): /* do low P normal */
+            xastir_snprintf(str,
+                str_len,
+                "%02d %05.2f%c",
+                ideg,
+//                min+0.001, // Correct possible unbiased rounding
+                min,
+                ns);
+            break;
+
+        case(CONVERT_HP_NOSP): /* do HP w/no space */
+            xastir_snprintf(str,
+                str_len,
+                "%02d%06.3f%c",
+                ideg,
+//                min+0.0001, // Correct possible unbiased rounding
+                min,
+                ns);
+            break;
+
+        case(CONVERT_VHP_NOSP): /* do Very HP w/no space */
+            xastir_snprintf(str,
+                str_len,
+                "%02d%07.4f%c",
+                ideg,
+//                min+0.00001, // Correct possible unbiased rounding
+                min,
+                ns);
+            break;
+
+        case(CONVERT_UP_TRK): /* for tracklog files */
+            xastir_snprintf(str,
+                str_len,
+                "%c%02d %07.4f",
+                ns,
+                ideg,
+//                min+0.00001); // Correct possible unbiased rounding
+                min);
+            break;
+
+        case(CONVERT_DEC_DEG):
+            xastir_snprintf(str,
+                str_len,
+                "%08.5f%c",
+//                (ideg+min/60.0)+0.000001, // Correct possible unbiased rounding
+                ideg+min/60.0,
+                ns);
+            break;
+
+        case(CONVERT_DMS_NORMAL):
+            xastir_snprintf(str,
+                str_len,
+                "%02d %02d %04.1f%c",
+                ideg,
+                imin,
+//                sec+0.01, // Correct possible unbiased rounding
+                sec,
+                ns);
+            break;
+        
+        case(CONVERT_DMS_NORMAL_FORMATED):
+            xastir_snprintf(str,
+                str_len,
+                "%02d°%02d\'%04.1f%c",
+                ideg,
+                imin,
+//                sec+0.01, // Correct possible unbiased rounding
+                sec,
+                ns);
+            break;
+
+        case(CONVERT_HP_NORMAL_FORMATED):
+            xastir_snprintf(str,
+                str_len,
+                "%02d°%06.3f%c",
+                ideg,
+//                min+0.0001, // Correct possible unbiased roundin
+                min,
+                ns);
+            break;
+        
+        case(CONVERT_HP_NORMAL):
+        default: /* do HP normal */
+            xastir_snprintf(str,
+                str_len,
+                "%02d %06.3f%c",
+                ideg,
+//                min+0.0001, // Correct possible unbiased rounding
+                min,
+                ns);
+            break;
+    }
+}
+
+
+
+
+
+// convert longitude from long to string
+// Input is in Xastir coordinate system
+//
+// CONVERT_LP_NOSP      = DDDMM.MME
+// CONVERT_HP_NOSP      = DDDMM.MMME
+// CONVERT_VHP_NOSP     = DDDMM.MMMME
+// CONVERT_LP_NORMAL    = DDD MM.MME
+// CONVERT_HP_NORMAL    = DDD MM.MMME
+// CONVERT_UP_TRK       = EDDD MM.MMMM
+// CONVERT_DEC_DEG      = DDD.DDDDDE
+// CONVERT_DMS_NORMAL   = DDD MM SS.SN
+// CONVERT_DMS_NORMAL_FORMATED   = DDD'MM'SS.SN
+//
+void convert_lon_l2s(long lon, char *str, int str_len, int type) {
+    char ew;
+    float deg, min, sec;
+    int ideg, imin;
+    long temp;
+
+    str[0] = '\0';
+    deg = (float)(lon - 64800000l) / 360000.0;
+
+    // Switch to integer arithmetic to avoid floating-point rounding
+    // errors.
+    temp = (long)(deg * 100000);
+
+    ew = 'E';
+    if (temp <= 0) {
+        ew = 'W';
+        temp = labs(temp);
+    }
+
+    ideg = (int)temp / 100000;
+    min = (temp % 100000) * 60.0 / 100000.0;
+
+    // Again switch to integer arithmetic to avoid floating-point
+    // rounding errors.
+    temp = (long)(min * 1000);
+    imin = (int)(temp / 1000);
+    sec = (temp % 1000) * 60.0 / 1000.0;
+
+    switch(type) {
+
+        case(CONVERT_LP_NOSP): /* do low P w/nospacel */
+            xastir_snprintf(str,
+                str_len,
+                "%03d%05.2f%c",
+                ideg,
+//                min+0.001, // Correct possible unbiased rounding
+                min,
+                ew);
+            break;
+
+        case(CONVERT_LP_NORMAL): /* do low P normal */
+            xastir_snprintf(str,
+                str_len,
+                "%03d %05.2f%c",
+                ideg,
+//                min+0.001, // Correct possible unbiased rounding
+                min,
+                ew);
+            break;
+
+        case(CONVERT_HP_NOSP): /* do HP w/nospace */
+            xastir_snprintf(str,
+                str_len,
+                "%03d%06.3f%c",
+                ideg,
+//                min+0.0001, // Correct possible unbiased rounding
+                min,
+                ew);
+            break;
+
+        case(CONVERT_VHP_NOSP): /* do Very HP w/nospace */
+            xastir_snprintf(str,
+                str_len,
+                "%03d%07.4f%c",
+                ideg,
+//                min+0.00001, // Correct possible unbiased rounding
+                min,
+                ew);
+            break;
+
+        case(CONVERT_UP_TRK): /* for tracklog files */
+            xastir_snprintf(str,
+                str_len,
+                "%c%03d %07.4f",
+                ew,
+                ideg,
+//                min+0.00001); // Correct possible unbiased rounding
+                min);
+            break;
+
+        case(CONVERT_DEC_DEG):
+            xastir_snprintf(str,
+                str_len,
+                "%09.5f%c",
+//                (ideg+min/60.0)+0.000001, // Correct possible unbiased rounding
+                ideg+min/60.0,
+                ew);
+            break;
+
+        case(CONVERT_DMS_NORMAL):
+            xastir_snprintf(str,
+                str_len,
+                "%03d %02d %04.1f%c",
+                ideg,
+                imin,
+//                sec+0.01, // Correct possible unbiased rounding
+                sec,
+                ew);
+            break;
+
+        case(CONVERT_DMS_NORMAL_FORMATED):
+            xastir_snprintf(str,
+                str_len,
+                "%03d°%02d\'%04.1f%c",
+                ideg,
+                imin,
+//                sec+0.01, // Correct possible unbiased rounding
+                sec,
+                ew);
+            break;
+        
+        case(CONVERT_HP_NORMAL_FORMATED):
+            xastir_snprintf(str,
+                str_len,
+                "%03d°%06.3f%c",
+                ideg,
+//                min+0.0001, // Correct possible unbiased rounding
+                min,
+                ew);
+            break;
+
+        case(CONVERT_HP_NORMAL):
+        default: /* do HP normal */
+            xastir_snprintf(str,
+                str_len,
+                "%03d %06.3f%c",
+                ideg,
+//                min+0.0001, // Correct possible unbiased rounding
+                min,
+                ew);
+            break;
+    }
+}
+
+
+
+
+
+
+/*************************************************************************/
+/* output_lat - format position with position_amb_chars for transmission */
+/*************************************************************************/
+/*
+char *output_lat(char *in_lat, int comp_pos) {
+    int i,j;
+    int position_amb_chars = 0;
+//fprintf(stderr,"in_lat:%s\n", in_lat);
+
+    if (!comp_pos) {
+        // Don't do this as it results in truncation!
+        //in_lat[7]=in_lat[8]; // Shift N/S down for transmission
+    }
+    else if (position_amb_chars>0) {
+        in_lat[7]='0';
+    }
+
+    j=0;
+    if (position_amb_chars>0 && position_amb_chars<5) {
+        for (i=6;i>(6-position_amb_chars-j);i--) {
+            if (i==4) {
+                i--;
+                j=1;
+            }
+            if (!comp_pos) {
+                in_lat[i]=' ';
+            } else
+                in_lat[i]='0';
+        }
+    }
+
+    if (!comp_pos) {
+        in_lat[8] = '\0';
+    }
+
+    return(in_lat);
+}
+*/
+
+
+
+/**************************************************************************/
+/* output_long - format position with position_amb_chars for transmission */
+/**************************************************************************/
+/*
+char *output_long(char *in_long, int comp_pos) {
+    int i,j;
+int position_amb_chars = 0;
+//fprintf(stderr,"in_long:%s\n", in_long);
+
+    if (!comp_pos) {
+        // Don't do this as it results in truncation!
+        //in_long[8]=in_long[9]; // Shift e/w down for transmission
+    }
+    else if (position_amb_chars>0) {
+        in_long[8]='0';
+    }
+
+    j=0;
+    if (position_amb_chars>0 && position_amb_chars<5) {
+        for (i=7;i>(7-position_amb_chars-j);i--) {
+            if (i==5) {
+                i--;
+                j=1;
+            }
+            if (!comp_pos) {
+                in_long[i]=' ';
+            } else
+                in_long[i]='0';
+        }
+    }
+
+    if (!comp_pos)
+        in_long[9] = '\0';
+
+    return(in_long);
+}
+*/
+
+
+
+//***********************************************************
+// output_my_aprs_data
+// This is the function responsible for sending out my own
+// posits.  The next function below this one handles objects,
+// messages and the like (output_my_data).
+//***********************************************************/
+
+/*
+void create_output_lat_long(gchar *my_output_lat, gchar *my_output_long )
+{
+    _aprs_transmit_compressed_posit = FALSE;
+    gchar *my_lat = g_strdup_printf("%lf", _gps.lat);
+    gchar *my_long = g_strdup_printf("%lf", _gps.lon);
+
+    
+    // Format latitude string for transmit later
+    if (_aprs_transmit_compressed_posit) {    // High res version
+       // TODO - enable compressed beacon
+        snprintf(my_output_lat,
+            sizeof(my_output_lat),
+            "%s",
+            my_lat);
+    }
+    else {  // Create a low-res version of the latitude string
+        long my_temp_lat;
+        char temp_data[20];
+
+        // Convert to long
+        my_temp_lat = convert_lat_s2l(my_lat);
+
+        // Convert to low-res string
+        convert_lat_l2s(my_temp_lat,
+            temp_data,
+            sizeof(temp_data),
+            CONVERT_LP_NORMAL);
+
+        snprintf(my_output_lat,
+            sizeof(my_output_lat),
+            "%c%c%c%c.%c%c%c",
+            temp_data[0],
+            temp_data[1],
+            temp_data[3],
+            temp_data[4],
+            temp_data[6],
+            temp_data[7],
+            temp_data[8]);
+    }
+
+    (void)output_lat(my_output_lat, _aprs_transmit_compressed_posit);
+
+    // Format longitude string for transmit later
+    if (_aprs_transmit_compressed_posit) {    // High res version
+        snprintf(my_output_long,
+            sizeof(my_output_long),
+            "%s",
+            my_long);
+    }
+    else {  // Create a low-res version of the longitude string
+        long my_temp_long;
+        char temp_data[20];
+
+        // Convert to long
+        my_temp_long = convert_lon_s2l(my_long);
+
+        // Convert to low-res string
+        convert_lon_l2s(my_temp_long,
+            temp_data,
+            sizeof(temp_data),
+            CONVERT_LP_NORMAL);
+
+        snprintf(my_output_long,
+            sizeof(my_output_long),
+            "%c%c%c%c%c.%c%c%c",
+            temp_data[0],
+            temp_data[1],
+            temp_data[2],
+            temp_data[4],
+            temp_data[5],
+            temp_data[7],
+            temp_data[8],
+            temp_data[9]);
+    }
+
+    (void)output_long(my_output_long, _aprs_transmit_compressed_posit);
+
+}
+
+
+void output_my_aprs_data_tty() {
+//TODO
+       return ;
+
+       gchar my_output_lat[MAX_LAT];
+    gchar my_output_long[MAX_LONG];
+//    gchar header_txt[MAX_LINE_SIZE+5];
+//    gchar header_txt_save[MAX_LINE_SIZE+5];
+    gchar path_txt[MAX_LINE_SIZE+5];
+    gchar data_txt[MAX_LINE_SIZE+5];    
+    gchar temp[MAX_LINE_SIZE+5];
+    gchar *unproto_path = "";
+    gchar data_txt2[5];
+    struct tm *day_time;
+    gchar my_pos[256];
+    gchar output_net[256];
+    gchar output_phg[10];
+    gchar output_cs[10];
+    gchar output_alt[20];
+    gchar output_brk[3];
+    int ok;
+    gchar my_comment_tx[APRS_MAX_COMMENT+1];
+    int interfaces_ok_for_transmit = 0;
+    gchar my_phg[10];
+
+    time_t sec;
+    gchar header_txt[MAX_LINE_SIZE+5];
+    gchar header_txt_save[MAX_LINE_SIZE+5];
+
+    gchar data_txt_save[MAX_LINE_SIZE+5];
+
+
+    if (!(port_data.status == DEVICE_UP 
+               && _aprs_tty_enable && _aprs_enable && _aprs_enable_tty_tx)) return ;
+
+    header_txt_save[0] = '\0';
+    data_txt_save[0] = '\0';
+    
+    sec = sec_now();
+    
+    my_phg[0] = '\0';
+       
+    create_output_lat_long(my_output_lat, my_output_long);
+       
+
+    // First send any header/path info we might need out the port,
+    // set up TNC's to the proper mode, etc.
+    ok = 1;
+
+    // clear this for a TNC 
+    output_net[0] = '\0';
+
+    // Set my call sign 
+    // The leading \r is sent to normal serial TNCs.  The only
+    // reason for it is that some folks' D700s are getting 
+    // garbage in the input buffer, and the result is the mycall
+    // line is rejected.  The \r at the beginning clears out the 
+    // junk and lets it go through.  But data_out_ax25 tries to 
+    // parse the MYCALL line, and the presence of a leading \r 
+    // breaks it.
+    snprintf(header_txt,
+        sizeof(header_txt),
+        "%c%s %s\r",
+        '\3',
+        ((port_data.device_type !=DEVICE_AX25_TNC)?
+                    "\rMYCALL":"MYCALL"),
+                    _aprs_mycall);
+
+    // Send the callsign out to the TNC only if the interface is up and tx is enabled???
+    // We don't set it this way for KISS TNC interfaces.
+    if ( (port_data.device_type != DEVICE_SERIAL_KISS_TNC)
+            && (port_data.device_type != DEVICE_SERIAL_MKISS_TNC)
+            && (port_data.status == DEVICE_UP)
+            //&& (devices.transmit_data == 1)
+            //&& !transmit_disable
+            //&& !posit_tx_disable
+            ) {
+        port_write_string(header_txt, APRS_PORT_TTY);
+//send_line(gchar* text, gint text_len, TAprsPort port)
+    }
+
+    // Set unproto path:  Get next unproto path in
+    // sequence.
+    
+    snprintf(header_txt,
+            sizeof(header_txt),
+            "%c%s %s VIA %s\r",
+            '\3',
+            "UNPROTO",
+            VERSIONFRM,
+            _aprs_unproto_path);
+
+    snprintf(header_txt_save,
+            sizeof(header_txt_save),
+            "%s>%s,%s:",
+            _aprs_mycall,
+            VERSIONFRM,
+            _aprs_unproto_path);
+
+    snprintf(path_txt,
+            sizeof(path_txt),
+            "%s",
+            _aprs_unproto_path);
+
+
+    // Send the header data to the TNC.  This sets the
+    // unproto path that'll be used by the next packet.
+    // We don't set it this way for KISS TNC interfaces.
+    if ( (port_data.device_type != DEVICE_SERIAL_KISS_TNC)
+            && (port_data.device_type != DEVICE_SERIAL_MKISS_TNC)
+            && (port_data.status == DEVICE_UP)
+            //&& (devices.transmit_data == 1)
+            //&& !transmit_disable
+            //&& !posit_tx_disable
+            ) {
+        port_write_string(header_txt, APRS_PORT_TTY);
+    }
+
+
+    // Set converse mode.  We don't need to do this for
+    // KISS TNC interfaces.  One european TNC (tnc2-ui)
+    // doesn't accept "conv" but does accept the 'k'
+    // command.  A Kantronics KPC-2 v2.71 TNC accepts
+    // the "conv" command but not the 'k' command.
+    // Figures!
+    // 
+    snprintf(header_txt, sizeof(header_txt), "%c%s\r", '\3', APRS_CONVERSE_MODE);
+        if ( (port_data.device_type != DEVICE_SERIAL_KISS_TNC)
+                && (port_data.device_type != DEVICE_SERIAL_MKISS_TNC)
+                && (port_data.status == DEVICE_UP)
+                //&& (devices.transmit_data == 1)
+                //&& !transmit_disable
+            //&& !posit_tx_disable
+        ) {
+        port_write_string(header_txt, APRS_PORT_TTY);
+    }
+    // sleep(1);
+
+
+
+    // Set up some more strings for later transmission
+
+    // send station info 
+    output_cs[0] = '\0';
+    output_phg[0] = '\0';
+    output_alt[0] = '\0';
+    output_brk[0] = '\0';
+
+
+    if (_aprs_transmit_compressed_posit)
+    {
+       // TOOD - enable compressed beacon support
+
+//     snprintf(my_pos,
+//            sizeof(my_pos),
+//            "%s",
+//            compress_posit(my_output_lat,
+//                my_group,
+//                my_output_long,
+//                my_symbol,
+//                my_last_course,
+//                my_last_speed,  // In knots
+//                my_phg));
+
+    }
+    else { // standard non compressed mode 
+        snprintf(my_pos,
+            sizeof(my_pos),
+            "%s%c%s%c",
+            my_output_lat,
+            _aprs_beacon_group,
+            my_output_long,
+            _aprs_beacon_symbol);
+        // get PHG, if used for output 
+        if (strlen(my_phg) >= 6)
+            snprintf(output_phg,
+                sizeof(output_phg),
+                "%s",
+                my_phg);
+
+        // get CSE/SPD, Always needed for output even if 0 
+        snprintf(output_cs,
+            sizeof(output_cs),
+            "%03d/%03d/",
+            _gps.heading,
+            _gps.speed);    // Speed in knots
+
+        // get altitude 
+// TODO
+//        if (my_last_altitude_time > 0)
+//            snprintf(output_alt,
+//                sizeof(output_alt),
+//                "A=%06ld/",
+//                 my_last_altitude);
+    }
+
+
+    // APRS_MOBILE LOCAL TIME 
+
+// TODO
+//    if((strlen(output_cs) < 8) && (my_last_altitude_time > 0)) {
+//        xastir_snprintf(output_brk,
+//            sizeof(output_brk),
+//            "/");
+//    }
+
+    day_time = localtime(&sec);
+
+    snprintf(data_txt_save,
+        sizeof(data_txt_save),
+        "@%02d%02d%02d/%s%s%s%s%s",
+        day_time->tm_mday,
+        day_time->tm_hour,
+        day_time->tm_min,
+        my_pos,
+        output_cs,
+        output_brk,
+        output_alt,
+        my_comment_tx);
+
+//WE7U2:
+    // Truncate at max length for this type of APRS
+    // packet.
+    if (_aprs_transmit_compressed_posit) {
+        if (strlen(data_txt_save) > 61) {
+            data_txt_save[61] = '\0';
+        }
+    }
+    else { // Uncompressed lat/long
+        if (strlen(data_txt_save) > 70) {
+            data_txt_save[70] = '\0';
+        }
+    }
+
+    // Add '\r' onto end.
+    strncat(data_txt_save, "\r", 1);
+
+    snprintf(data_txt,
+        sizeof(data_txt),
+        "%s%s",
+        output_net,
+        data_txt_save);
+
+
+    if (ok) {
+        // Here's where the actual transmit of the posit occurs.  The
+        // transmit string has been set up in "data_txt" by this point.
+
+        // If transmit or posits have been turned off, don't transmit posit
+        if ( (port_data.status == DEVICE_UP)
+//                && (devices.transmit_data == 1)
+//                    && !transmit_disable
+//                    && !posit_tx_disable
+                ) {
+
+            interfaces_ok_for_transmit++;
+
+// WE7U:  Change so that path is passed as well for KISS TNC
+// interfaces:  header_txt_save would probably be the one to pass,
+// or create a new string just for KISS TNC's.
+
+            if ( (port_data.device_type == DEVICE_SERIAL_KISS_TNC)
+                    || (port_data.device_type == DEVICE_SERIAL_MKISS_TNC) ) {
+
+                // Note:  This one has callsign & destination in the string
+
+                // Transmit the posit out the KISS interface
+                send_ax25_frame(APRS_PORT_TTY, 
+                                               _aprs_mycall,    // source
+                                VERSIONFRM,     // destination
+                                path_txt,       // path
+                                data_txt);      // data
+            }
+            else {  // Not a Serial KISS TNC interface
+
+
+                port_write_string(data_txt, APRS_PORT_TTY);  // Transmit the posit
+            }
+
+
+            // Put our transmitted packet into the Incoming Data
+            // window as well.  This way we can see both sides of a
+            // conversation.  data_port == -1 for x_spider port,
+            // normal interface number otherwise.  -99 to get a "**"
+            // display meaning all ports.
+            //
+            // For packets that we're igating we end up with a CR or
+            // LF on the end of them.  Remove that so the display
+            // looks nice.
+            //snprintf(temp,
+            //    sizeof(temp),
+            //    "%s>%s,%s:%s",
+            //    my_callsign,
+            //    VERSIONFRM,
+            //    unproto_path,
+            //    data_txt);
+            //makePrintable(temp);
+            //packet_data_add("TX ", temp, port);
+
+        }
+    } // End of posit transmit: "if (ok)"
+}
+*/
+
+void create_output_pos_packet(TAprsPort port, gchar **packet, int *length)
+{
+       
+//     gchar encodedPos[MAX_LINE_SIZE];
+       
+       if(_aprs_transmit_compressed_posit)
+       {
+               // TODO
+       }
+       else
+       {
+               //!5122.09N/00008.42W&APRS4R IGATE RUNNING ON NSLU2
+               
+               // For now just use a simple packet
+               
+               gchar slat[10];
+               gchar slon[10];
+               
+               gdouble pos = (_gps.lat > 0 ? _gps.lat : _gps.lat*-1);
+               
+               gdouble min = (pos - (int)pos)*60.0;
+               sprintf(slat, "%02d%02d.%02.0f", (int)pos, (int)min,
+                                   ((min - (int)min)*100.0) );
+                           
+               pos = (_gps.lon > 0 ? _gps.lon : _gps.lon*-1);
+               
+               min = (pos - (int)pos)*60.0;
+               sprintf(slon, "%03d%02d.%02.0f", (int)pos, (int)min,
+                                                   ((min - (int)min)*100.0) );
+               
+               *packet = g_strdup_printf(
+               //snprintf(encodedPos, MAX_LINE_SIZE,
+                       "%c%s%c%c%s%c%c%s%c",
+                       '=',
+                       slat, 
+                       (_gps.lat > 0 ? 'N' : 'S'),
+                       _aprs_beacon_group,
+                       slon,
+                       (_gps.lon > 0 ? 'E' : 'W'),
+                       _aprs_beacon_symbol,
+                       (port == APRS_PORT_INET ? _aprs_inet_beacon_comment : _aprs_beacon_comment),
+                       (char)0
+                       );
+       }
+       
+/*     *packet = g_strdup_printf(
+        "%s>%s,%s:%s\r\n",
+        _aprs_mycall,
+        VERSIONFRM,
+        _aprs_unproto_path,
+        encodedPos);
+*/
+       *length = strlen(*packet);
+}
+
+
+void send_packet(TAprsPort port, gchar* to_call, gchar* path, gchar* packet, gint packet_length)
+{
+       if(port == APRS_PORT_INET 
+                       || !(port_data.device_type == DEVICE_SERIAL_KISS_TNC
+               || port_data.device_type == DEVICE_SERIAL_MKISS_TNC) )
+       {
+               gchar *packet_header
+                       =  g_strdup_printf(
+               "%s>%s,%s:",
+               _aprs_mycall,
+               to_call,
+               path);
+               
+               gchar *full_packet = g_strdup_printf("%s%s\r\n", packet_header, packet);
+               
+               
+               send_line(full_packet, strlen(packet_header)+packet_length+2,  port);
+       }
+       else
+       {
+               send_ax25_frame(port, _aprs_mycall, to_call, path, packet);
+       }
+       
+}
+
+void output_my_aprs_data(TAprsPort port) {
+       
+
+       gchar *packet;
+       int length = 0;
+       
+       
+    //create_output_lat_long(my_output_lat, my_output_long);
+       create_output_pos_packet(port, &packet, &length);
+
+       
+       send_packet(port, VERSIONFRM, _aprs_unproto_path, packet, length);
+       
+}
+
+/////////// TTY functionality
+
+int serial_init();
+
+static void thread_read_tty()
+{
+       set_aprs_tty_conn_state(RCVR_UP);
+       
+       serial_init ();
+       
+    port_read();
+}
+
+gboolean aprs_tty_connect()
+{
+    printf("%s(%d)\n", __PRETTY_FUNCTION__, _aprs_tty_state);
+
+    if(_aprs_tty_enable && _aprs_tty_state == RCVR_OFF)
+    {
+        set_aprs_tty_conn_state(RCVR_DOWN);
+
+        // Lock/Unlock the mutex to ensure that the thread doesn't
+        // start until _gps_thread is set. 
+        g_mutex_lock(_aprs_tty_init_mutex);
+
+        _aprs_tty_thread = g_thread_create((GThreadFunc)thread_read_tty,
+                NULL, TRUE, NULL); // Joinable. 
+
+        g_mutex_unlock(_aprs_tty_init_mutex);
+    }
+
+    vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
+    return FALSE;
+
+}
+
+void aprs_tty_disconnect()
+{
+    gboolean exit_now = FALSE;
+
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    GThread *my_thread = g_thread_self();
+
+    if(my_thread == _aprs_tty_thread)
+    {
+        exit_now = TRUE;
+    }
+
+    _aprs_tty_thread = NULL;
+
+    if(_window)
+        set_aprs_tty_conn_state(RCVR_OFF);
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+
+    if(exit_now) exit(0);
+
+}
+
+
+//***********************************************************
+// port_write_string()
+//
+// port is port# used
+// data is the string to write
+//***********************************************************
+
+void port_write_string(gchar *data, gint len, TAprsPort port) {
+    int i,erd, retval;
+    int write_in_pos_hold;
+
+    if (data == NULL)
+        return;
+
+    if (data[0] == '\0')
+        return;
+    
+    
+    
+    if(port == APRS_PORT_TTY)
+    {
+    
+       if(g_mutex_trylock (_write_buffer[port].write_lock))
+       {
+               //fprintf(stderr, "TTY Write... ");
+                   retval = (int)write(port_data.channel,
+                       data,
+                       len);
+                   //fprintf(stderr, "done... ");
+                   g_mutex_unlock (_write_buffer[port].write_lock);
+                   //fprintf(stderr, "Unlocked\n");
+       }
+       else
+               fprintf(stderr, "Failed to get lock\n");
+    }
+    else
+    {
+       send_line(data, len, port);
+    }
+}
+
+
+gboolean send_line(gchar* text, gint text_len, TAprsPort port)
+{
+       if(APRS_PORT_INET == port && !_aprs_enable_inet_tx) return FALSE;
+       else if (APRS_PORT_TTY == port && !_aprs_enable_tty_tx) return FALSE;
+       
+       if(APRS_PORT_TTY == port)
+       {
+       
+       }
+       gboolean error = FALSE;
+       gint i;
+    gint write_in_pos_hold = _write_buffer[port].write_in_pos;
+    
+    // Lock the mutex 
+    g_mutex_lock(_write_buffer[port].write_lock);
+    
+    for (i = 0; i < text_len && !error; i++) {
+       _write_buffer[port].device_write_buffer[_write_buffer[port].write_in_pos++] 
+                                               = text[i];
+       
+        if (_write_buffer[port].write_in_pos >= MAX_DEVICE_BUFFER)
+               _write_buffer[port].write_in_pos = 0;
+
+        if (_write_buffer[port].write_in_pos == _write_buffer[port].write_out_pos) {
+            fprintf(stderr,"Port %d Buffer overrun\n",port);
+
+            /* clear this restore original write_in pos and dump this string */
+            _write_buffer[port].write_in_pos = write_in_pos_hold;
+            _write_buffer[port].errors++;
+            error = TRUE;
+        }
+    }
+       
+    g_mutex_unlock(_write_buffer[port].write_lock);
+    
+    return error;
+}
+
+
+gboolean aprs_send_beacon_inet()
+{
+       aprs_send_beacon(APRS_PORT_INET);
+
+}
+gboolean aprs_send_beacon(TAprsPort port)
+{
+       if(_aprs_enable)
+       {
+               output_my_aprs_data(port);
+               
+               //fprintf(stderr, "Beacon sent\n" );
+       }
+       
+       return TRUE;
+}
+
+#endif //INCLUDE_APRS
diff --git a/src/aprs.h b/src/aprs.h
new file mode 100644 (file)
index 0000000..618aea9
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * 
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Maemo Mapper is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Maemo Mapper.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * 
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ * 
+ *  * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000  Frank Giannandrea
+ * Copyright (C) 2000-2007  The Xastir Group
+ * 
+ */
+
+#ifdef HAVE_CONFIG_H
+#    include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#ifndef MAEMO_MAPPER_APRS_H
+#define MAEMO_MAPPER_APRS_H
+
+#include "types.h"
+#include <termios.h>
+
+#define MAX_LINE_SIZE                  512
+#define MAX_STATUS_LINES               20
+#define MAX_COMMENT_LINES              20
+#define EARTH_RADIUS_METERS     6378138.0
+#define MAX_TACTICAL_CALL              20
+#define TACTICAL_HASH_SIZE             1024
+
+
+#define START_STR " }"
+#define LBRACE '{'
+#define RBRACE '}'
+
+
+
+#define MAX_DEVICE_NAME 128
+#define MAX_DEVICE_BUFFER_UNTIL_BINARY_SWITCH 700
+#define MAX_DEVICE_HOSTNM 40
+#define MAX_DEVICE_HOSTPW 40
+
+// KISS Protocol Special Characters & Commands:
+#define KISS_FEND           0xc0  // Frame End
+#define KISS_FESC           0xdb  // Frame Escape
+#define KISS_TFEND          0xdc  // Transposed Frame End
+#define KISS_TFESC          0xdd  // Transposed Frame Escape
+#define KISS_DATA           0x00
+#define KISS_TXDELAY        0x01
+#define KISS_PERSISTENCE    0x02
+#define KISS_SLOTTIME       0x03
+#define KISS_TXTAIL         0x04
+#define KISS_FULLDUPLEX     0x05
+#define KISS_SETHARDWARE    0x06
+#define KISS_RETURN         0xff
+
+
+enum Device_Types {
+    DEVICE_NONE,
+    DEVICE_SERIAL_TNC,
+    DEVICE_NET_STREAM,
+    DEVICE_AX25_TNC,
+    DEVICE_SERIAL_KISS_TNC,     // KISS TNC on serial port (not ax.25 kernel device)
+    DEVICE_SERIAL_MKISS_TNC     // Multi-port KISS TNC, like the Kantronics KAM
+};
+
+enum Device_Active {
+    DEVICE_NOT_IN_USE,
+    DEVICE_IN_USE
+};
+
+enum Device_Status {
+    DEVICE_DOWN,
+    DEVICE_UP,
+    DEVICE_ERROR
+};
+
+
+
+
+typedef struct {
+    int    device_type;                           /* device type                             */
+    int    active;                                /* channel in use                          */
+    int    status;                                /* current status (up or down)             */
+    char   device_name[MAX_DEVICE_NAME+1];        /* device name                             */
+    char   device_host_name[MAX_DEVICE_HOSTNM+1]; /* device host name for network            */
+    unsigned long int address;                    /* socket address for network              */
+    int    thread_status;                         /* thread status for connect thread        */
+    int    connect_status;                        /* connect status for connect thread       */
+    int    decode_errors;                         /* decode error count, used for data type  */
+    int    data_type;                             /* 0=normal 1=wx_binary                    */
+    int    socket_port;                           /* socket port# for network                */
+    char   device_host_pswd[MAX_DEVICE_HOSTPW+1]; /* host password                           */
+    int    channel;                               /* for serial and net ports                */
+    int    channel2;                              /* for AX25 ports                          */
+    char   ui_call[30];                           /* current call for this port              */
+    struct termios t,t_old;                       /* terminal struct for serial port         */
+    int    dtr;                                   /* dtr signal for HSP cable (status)       */
+    int    sp;                                    /* serial port speed                       */
+    int    style;                                 /* serial port style                       */
+    int    scan;                                  /* data read available                     */
+    int    errors;                                /* errors for this port                    */
+    int    reconnect;                             /* reconnect on net failure                */
+    int    reconnects;                            /* total number of reconnects by this port */
+    unsigned long   bytes_input;                  /* total bytes read by this port           */
+    unsigned long   bytes_output;                 /* total bytes written by this port        */
+    unsigned long   bytes_input_last;             /* total bytes read last check             */
+    unsigned long   bytes_output_last;            /* total bytes read last check             */
+    int    port_activity;                         /* 0 if no activity between checks         */
+    pthread_t read_thread;                        /* read thread                             */
+    int    read_in_pos;                           /* current read buffer input pos           */
+    int    read_out_pos;                          /* current read buffer output pos          */
+    char   device_read_buffer[MAX_DEVICE_BUFFER]; /* read buffer for this port               */
+    pthread_mutex_t read_lock;                       /* Lock for reading the port data          */
+    pthread_t write_thread;                       /* write thread                            */
+    int    write_in_pos;                          /* current write buffer input pos          */
+    int    write_out_pos;                         /* current write buffer output pos         */
+    pthread_mutex_t write_lock;                      /* Lock for writing the port data          */
+    char   device_write_buffer[MAX_DEVICE_BUFFER];/* write buffer for this port              */
+} iface;
+
+extern iface port_data;     // shared port data
+extern TWriteBuffer _write_buffer[APRS_PORT_COUNT];
+
+// Incoming data queue
+typedef struct _incoming_data_record {
+    int length;   // Used for binary strings such as KISS
+    int port;
+    unsigned char data[MAX_LINE_SIZE];
+} incoming_data_record;
+
+gboolean aprs_server_connect(void); // Called from menu.c 
+void aprs_server_disconnect(void);  // Called from menu.c
+
+void aprs_init(void);                          // Called form main.c
+
+void map_render_aprs();
+double distance_from_my_station(char *call_sign, char *course_deg, gint course_len);
+
+void pad_callsign(char *callsignout, char *callsignin);
+gboolean aprs_send_beacon(TAprsPort port);
+gboolean aprs_send_beacon_inet();
+void update_aprs_inet_options(gboolean force);
+void port_write_string(gchar *data, gint len, TAprsPort port);
+
+extern AprsDataRow *station_shortcuts[16384];
+
+#endif /* ifndef MAEMO_MAPPER_APRS_H */
+
+#endif // INCLUDE_APRS
diff --git a/src/aprs_decode.c b/src/aprs_decode.c
new file mode 100644 (file)
index 0000000..9f7ee23
--- /dev/null
@@ -0,0 +1,8597 @@
+/*
+ * 
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Maemo Mapper is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Maemo Mapper.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * 
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ * 
+ *  * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000  Frank Giannandrea
+ * Copyright (C) 2000-2007  The Xastir Group
+ * 
+ */
+
+#ifdef HAVE_CONFIG_H
+#    include "config.h"
+#endif
+
+
+#ifdef INCLUDE_APRS
+
+#include "aprs_decode.h"
+#include "data.h"
+#include "aprs.h"
+#include "types.h"
+#include "math.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "hashtable.h"
+#include "poi.h"
+#include "display.h"
+#include "aprs_message.h"
+//#include "aprs_weather.h"
+
+#define WX_TYPE 'X'
+
+float gust[60];                     // High wind gust for each min. of last hour
+int gust_write_ptr = 0;
+int gust_read_ptr = 0;
+int gust_last_write = 0;
+
+float rain_base[24];                // hundredths of an inch
+float rain_minute[60];              // Total rain for each min. of last hour, hundredths of an inch
+int rain_check = 0;                 // Flag for re-checking rain_total each hour
+
+int redraw_on_new_data;         // Station redraw request
+int wait_to_redraw;             /* wait to redraw until system is up */
+time_t posit_next_time;
+
+/////////////
+float rain_minute_total = 0.0;      // Total for last hour, hundredths of an inch
+int rain_minute_last_write = -1;    // Write pointer for rain_minute[] array, set to an invalid number
+float rain_00 = 0.0;                // hundredths of an inch
+float rain_24 = 0.0;                // hundredths of an inch
+
+
+int station_count;              // number of stored stations
+int station_count_save = 0;     // old copy of above
+AprsDataRow *n_first;               // pointer to first element in name sorted station list
+AprsDataRow *n_last;                // pointer to last  element in name sorted station list
+AprsDataRow *t_oldest;              // pointer to first element in time sorted station list (oldest)
+AprsDataRow *t_newest;              // pointer to last  element in time sorted station list (newest)
+time_t last_station_remove;     // last time we did a check for station removing
+time_t last_sec,curr_sec;       // for comparing if seconds in time have changed
+int next_time_sn;               // time serial number for unique time index
+
+int stations_heard = 0;
+
+////////////
+
+double cvt_m2len  = 3.28084;   // m to ft   // from meter
+double cvt_kn2len = 1.0;       // knots to knots;  // from knots
+double cvt_mi2len = 0.8689607; // mph to knots / mi to nm;  // from miles
+double cvt_dm2len = 0.328084;  // dm to ft  // from decimeter
+double cvt_hm2len = 0.0539957; // hm to nm;  // from hectometer
+
+
+int emergency_distance_check = 1;
+float emergency_range = 280.0;  // Default is 4hrs @ 70mph distance
+
+
+int decoration_offset_x = 0;
+int decoration_offset_y = 0;
+int last_station_info_x = 0;
+int last_station_info_y = 0;
+int fcc_lookup_pushed = 0;
+int rac_lookup_pushed = 0;
+
+time_t last_object_check = 0;   // Used to determine when to re-transmit objects/items
+
+time_t last_emergency_time = 0;
+char last_emergency_callsign[MAX_CALLSIGN+1];
+int st_direct_timeout = 60 * 60;        // 60 minutes.
+
+// Used in search_station_name() function.  Shortcuts into the
+// station list based on the least-significant 7 bits of the first
+// two letters of the callsign/object name.
+AprsDataRow *station_shortcuts[16384];
+
+// calculate every half hour, display in status line every 5 minutes
+#define ALOHA_CALC_INTERVAL 1800 
+#define ALOHA_STATUS_INTERVAL 300
+
+int process_emergency_packet_again = 0;
+
+
+////////////
+
+int  altnet;
+char altnet_call[MAX_CALLSIGN+1];
+static struct hashtable *tactical_hash = NULL;
+
+
+char echo_digis[6][MAX_CALLSIGN+1];
+int  tracked_stations = 0;       // A count variable used in debug code only
+
+int trail_segment_distance;     // Segment missing if greater distance
+int trail_segment_time;         // Segment missing if above this time (mins)
+int skip_dupe_checking;
+
+
+void station_shortcuts_update_function(int hash_key_in, AprsDataRow *p_rem);
+void delete_station_memory(AprsDataRow *p_del);
+void decode_Peet_Bros(int from, unsigned char *data, AprsWeatherRow *weather, int type);
+void decode_U2000_P(int from, unsigned char *data, AprsWeatherRow *weather);
+void decode_U2000_L(int from, unsigned char *data, AprsWeatherRow *weather);
+
+void init_weather(AprsWeatherRow *weather) {    // clear weather data
+
+    weather->wx_sec_time             = (time_t)0;
+    weather->wx_storm                = 0;
+    weather->wx_time[0]              = '\0';
+    weather->wx_course[0]            = '\0';
+    weather->wx_speed[0]             = '\0';
+    weather->wx_speed_sec_time       = 0; // ??
+    weather->wx_gust[0]              = '\0';
+    weather->wx_hurricane_radius[0]  = '\0';
+    weather->wx_trop_storm_radius[0] = '\0';
+    weather->wx_whole_gale_radius[0] = '\0';
+    weather->wx_temp[0]              = '\0';
+    weather->wx_rain[0]              = '\0';
+    weather->wx_rain_total[0]        = '\0';
+    weather->wx_snow[0]              = '\0';
+    weather->wx_prec_24[0]           = '\0';
+    weather->wx_prec_00[0]           = '\0';
+    weather->wx_hum[0]               = '\0';
+    weather->wx_baro[0]              = '\0';
+    weather->wx_fuel_temp[0]         = '\0';
+    weather->wx_fuel_moisture[0]     = '\0';
+    weather->wx_type                 = '\0';
+    weather->wx_station[0]           = '\0';
+}
+
+
+int tactical_keys_equal(void *key1, void *key2) {
+
+    if (strlen((char *)key1) == strlen((char *)key2)
+            && strncmp((char *)key1,(char *)key2,strlen((char *)key1))==0) {
+        return(1);
+    }
+    else {
+        return(0);
+    }
+}
+
+
+// Multiply all the characters in the callsign, truncated to
+// TACTICAL_HASH_SIZE
+//
+unsigned int tactical_hash_from_key(void *key) {
+    unsigned char *jj = key;
+    unsigned int tac_hash = 1;
+
+    while (*jj != '\0') {
+       tac_hash = tac_hash * (unsigned int)*jj++;
+    }
+
+    tac_hash = tac_hash % TACTICAL_HASH_SIZE;
+
+    return (tac_hash);
+}
+
+
+
+
+/* valid characters for APRS weather data fields */
+int is_aprs_chr(char ch) {
+
+    if (g_ascii_isdigit(ch) || ch==' ' || ch=='.' || ch=='-')
+    return(1);
+    else
+    return(0);
+}
+
+
+
+
+//
+// Search station record by callsign
+// Returns a station with a call equal or after the searched one
+//
+// We use a doubly-linked list for the stations, so we can traverse
+// in either direction.  We also use a 14-bit hash table created
+// from the first two letters of the call to dump us into the
+// beginning of the correct area that may hold the callsign, which
+// reduces search time quite a bit.  We end up doing a linear search
+// only through a small area of the linked list.
+//
+// DK7IN:  I don't look at case, objects and internet names could
+// have lower case.
+//
+int search_station_name(AprsDataRow **p_name, char *call, int exact) {
+    int kk;
+    int hash_key;
+    int result;
+    int ok = 1;
+
+    (*p_name) = n_first;                                // start of alphabet
+
+    if (call[0] == '\0') {
+        // If call we're searching for is empty, return n_first as
+        // the pointer.
+        return(0);
+    }
+
+    // We create the hash key out of the lower 7 bits of the first
+    // two characters, creating a 14-bit key (1 of 16384)
+    //
+    hash_key = (int)((call[0] & 0x7f) << 7);
+    hash_key = hash_key | (int)(call[1] & 0x7f);
+
+    // Look for a match using hash table lookup
+    //
+    (*p_name) = station_shortcuts[hash_key];
+
+    if ((*p_name) == NULL) {    // No hash-table entry found.
+        int mm;
+
+        // No index found for that letter.  Walk the array until
+        // we find an entry that is filled.  That'll be our
+        // potential insertion point (insertion into the list will
+        // occur just ahead of the hash entry).
+        for (mm = hash_key+1; mm < 16384; mm++) {
+            if (station_shortcuts[mm] != NULL) 
+            {
+               (*p_name) = station_shortcuts[mm];
+                
+                break;
+            }
+        }
+    }
+
+
+    // If we got to this point, we either have a NULL pointer or a
+    // real hash-table pointer entry.  A non-NULL pointer means that
+    // we have a match for the lower seven bits of the first two
+    // characters of the callsign.  Check the rest of the callsign,
+    // and jump out of the loop if we get outside the linear search
+    // area (if first two chars are different).
+
+    kk = (int)strlen(call);
+
+    // Search linearly through list.  Stop at end of list or break.
+    while ( (*p_name) != NULL) {
+
+        if (exact) {
+            // Check entire string for exact match
+            result = strcmp( call, (*p_name)->call_sign );
+        }
+        else {
+            // Check first part of string for match
+            result = strncmp( call, (*p_name)->call_sign, kk );
+        }
+
+        if (result < 0) {   // We went past the right location.
+                            // We're done.
+            ok = 0;
+
+            break;
+        }
+        else if (result == 0) { // Found a possible match
+            break;
+        }
+        else {  // Result > 0.  We haven't found it yet.
+               
+               (*p_name) = (*p_name)->n_next;  // Next element in list
+        }
+    }
+
+    // Did we find anything?
+    if ( (*p_name) == NULL) {
+        ok = 0;
+        return(ok); // Nope.  No match found.
+    }
+
+    // If "exact" is set, check that the string lengths match as
+    // well.  If not, we didn't find it.
+    if (exact && ok && strlen((*p_name)->call_sign) != strlen(call))
+        ok = 0;
+
+    return(ok);         // if not ok: p_name points to correct insert position in name list
+}
+
+
+
+/*
+ *  Check if current packet is a delayed echo
+ */
+int is_trailpoint_echo(AprsDataRow *p_station) {
+    int packets = 1;
+    time_t checktime;
+    AprsTrackRow *ptr;
+
+
+    // Check whether we're to skip checking for dupes (reading in
+    // objects/items from file is one such case).
+    //
+    if (skip_dupe_checking) {
+        return(0);  // Say that it isn't an echo
+    }
+
+    // Start at newest end of linked list and compare.  Return if we're
+    // beyond the checktime.
+    ptr = p_station->newest_trackpoint;
+
+    if (ptr == NULL)
+        return(0);  // first point couldn't be an echo
+
+    checktime = p_station->sec_heard - TRAIL_ECHO_TIME*60;
+
+    while (ptr != NULL) {
+
+        if (ptr->sec < checktime)
+            return(0);  // outside time frame, no echo found
+
+        if ((p_station->coord_lon == ptr->trail_long_pos)
+                && (p_station->coord_lat == ptr->trail_lat_pos)
+                && (p_station->speed == '\0' || ptr->speed < 0
+                        || (long)(atof(p_station->speed)*18.52) == ptr->speed)
+                        // current: char knots, trail: long 0.1m (-1 is undef)
+                && (p_station->course == '\0' || ptr->course <= 0
+                        || atoi(p_station->course) == ptr->course)
+                        // current: char, trail: int (-1 is undef)
+                && (p_station->altitude == '\0' || ptr->altitude <= -99999l
+                        || atoi(p_station->altitude)*10 == ptr->altitude)) {
+                        // current: char, trail: int (-99999l is undef)
+
+            return(1);              // we found a delayed echo
+        }
+        ptr = ptr->prev;
+        packets++;
+    }
+    return(0);                      // no echo found
+}
+
+
+/*
+ *  Keep track of last six digis that echo my transmission
+ */
+void upd_echo(char *path) {
+    int i,j,len;
+
+    if (echo_digis[5][0] != '\0') {
+        for (i=0;i<5;i++) {
+            xastir_snprintf(echo_digis[i],
+                MAX_CALLSIGN+1,
+                "%s",
+                echo_digis[i+1]);
+
+        }
+        echo_digis[5][0] = '\0';
+    }
+    for (i=0,j=0;i < (int)strlen(path);i++) {
+        if (path[i] == '*')
+            break;
+        if (path[i] == ',')
+            j=i;
+    }
+    if (j > 0)
+        j++;                    // first char of call
+    if (i > 0 && i-j <= 9) {
+        len = i-j;
+        for (i=0;i<5;i++) {     // look for free entry
+            if (echo_digis[i][0] == '\0')
+                break;
+        }
+        substr(echo_digis[i],path+j,len);
+    }
+}
+
+
+//
+// Store one trail point.  Allocate storage for the new data.
+//
+// We now store track data in a doubly-linked list.  Each record has a
+// pointer to the previous and the next record in the list.  The main
+// station record has a pointer to the oldest and the newest end of the
+// chain, and the chain can be traversed in either order.
+//
+int store_trail_point(AprsDataRow *p_station,
+                      long lon,
+                      long lat,
+                      time_t sec,
+                      char *alt,
+                      char *speed,
+                      char *course,
+                      short stn_flag) {
+// TODO - trails are currently disabled
+// This seems to fall over!!!
+       return 1;
+       
+       
+    char flag;
+    AprsTrackRow *ptr;
+
+
+    // Allocate storage for the new track point
+    ptr = malloc(sizeof(AprsTrackRow));
+    if (ptr == NULL) {
+        return(0); // Failed due to malloc
+    }
+    // Check whether we have any track data saved
+    if (p_station->newest_trackpoint == NULL) {
+        // new trail, do initialization
+
+        tracked_stations++;
+
+        // Assign a new trail color 'cuz it's a new trail
+        p_station->trail_color = new_trail_color(p_station->call_sign);
+    }
+
+    // Start linking the record to the new end of the chain
+    ptr->prev = p_station->newest_trackpoint;   // Link to record or NULL
+    ptr->next = NULL;   // Newest end of chain
+
+    // Have an older record already?
+    if (p_station->newest_trackpoint != NULL) { // Yes
+        p_station->newest_trackpoint->next = ptr;
+    }
+    else {  // No, this is our first record
+        p_station->oldest_trackpoint = ptr;
+    }
+
+    // Link it in as our newest record
+    p_station->newest_trackpoint = ptr;
+
+    ptr->trail_long_pos = lon;
+    ptr->trail_lat_pos  = lat;
+    ptr->sec            = sec;
+
+    if (alt[0] != '\0')
+            ptr->altitude = atoi(alt)*10;
+    else            
+            ptr->altitude = -99999l;
+
+    if (speed[0] != '\0')
+            ptr->speed  = (long)(atof(speed)*18.52);
+    else
+            ptr->speed  = -1;
+
+    if (course[0] != '\0')
+            ptr->course = (int)(atof(course) + 0.5);    // Poor man's rounding
+    else
+            ptr->course = -1;
+
+    flag = '\0';                    // init flags
+
+    if ((stn_flag & ST_DIRECT) != 0)
+            flag |= TR_LOCAL;           // set "local" flag
+
+    if (ptr->prev != NULL) {    // we have at least two points...
+        // Check whether distance between points is too far.  We
+        // must convert from degrees to the Xastir coordinate system
+        // units, which are 100th of a second.
+        if (    abs(lon - ptr->prev->trail_long_pos) > (trail_segment_distance * 60*60*100) ||
+                abs(lat - ptr->prev->trail_lat_pos)  > (trail_segment_distance * 60*60*100) ) {
+
+            // Set "new track" flag if there's
+            // "trail_segment_distance" degrees or more between
+            // points.  Originally was hard-coded to one degree, now
+            // set by a slider in the timing dialog.
+            flag |= TR_NEWTRK;
+        }
+        else {
+            // Check whether trail went above our maximum time
+            // between points.  If so, don't draw segment.
+            if (abs(sec - ptr->prev->sec) > (trail_segment_time *60)) {
+
+                // Set "new track" flag if long delay between
+                // reception of two points.  Time is set by a slider
+                // in the timing dialog.
+                flag |= TR_NEWTRK;
+            }
+        }
+        
+        // Since we have more then 1 previous track point, ensure we don't go over the configured max
+        // (_aprs_std_pos_hist)
+        AprsTrackRow *ptr_tmp = ptr;
+        AprsTrackRow *ptr_tmp_previous = ptr;
+        gint trackCount = 0;
+        
+        while( (ptr_tmp = ptr_tmp->prev) )
+        {
+               if(trackCount > _aprs_std_pos_hist)
+               {
+                       fprintf(stderr, "DEBUG: Deleting old track point\n");
+                       // Remove track
+                       ptr_tmp->prev->next = ptr_tmp->next;
+                       if(ptr_tmp->next) 
+                       {
+                               ptr_tmp->next->prev = ptr_tmp->prev;
+                       }
+                       ptr_tmp_previous = ptr_tmp->prev;
+                       free( ptr_tmp);
+                       ptr_tmp = ptr_tmp_previous;
+                       
+                       
+               }
+               else
+               {
+                       trackCount ++;
+               }
+               
+               ptr_tmp = ptr_tmp->prev;
+        }
+        
+    }
+    else {
+        // Set "new track" flag for first point received.
+        flag |= TR_NEWTRK;
+    }
+    ptr->flag = flag;
+
+    return(1);  // We succeeded
+}
+
+
+
+/*
+ *  Substring function WITH a terminating NULL char, needs a string of at least size+1
+ */
+void substr(char *dest, char *src, int size) 
+{
+    snprintf(dest, size+1, "%s", src);
+}
+
+
+
+/*
+ *  Remove element from name ordered list
+ */
+void remove_name(AprsDataRow *p_rem) {      // todo: return pointer to next element
+    int update_shortcuts = 0;
+    int hash_key;   // We use a 14-bit hash key
+
+
+    // Do a quick check to see if we're removing a station record
+    // that is pointed to by our pointer shortcuts array.
+    // If so, update our pointer shortcuts after we're done.
+    //
+    // We create the hash key out of the lower 7 bits of the first
+    // two characters, creating a 14-bit key (1 of 16384)
+    //
+    hash_key = (int)((p_rem->call_sign[0] & 0x7f) << 7);
+    hash_key = hash_key | (int)(p_rem->call_sign[1] & 0x7f);
+
+    if (station_shortcuts[hash_key] == p_rem) {
+        // Yes, we're trying to remove a record that a hash key
+        // directly points to.  We'll need to redo that hash key
+        // after we remove the record.
+        update_shortcuts++;
+    }
+
+
+    // Proceed to the station record removal
+    //
+    if (p_rem->n_prev == NULL) { // Appears to be first element in list
+
+        if (n_first == p_rem) {  // Yes, head of list
+
+            // Make list head point to 2nd element in list (or NULL)
+            // so that we can delete the current record.
+            n_first = p_rem->n_next;
+        }
+        else {  // No, not first element in list.  Problem!  The
+                // list pointers are inconsistent for some reason.
+                // The chain has been broken and we have dangling
+                // pointers.
+
+            fprintf(stderr,
+                "remove_name(): ERROR: p->n_prev == NULL but p != n_first\n");
+
+abort();    // Cause a core dump at this point
+// Perhaps we could do some repair to the list pointers here?  Start
+// at the other end of the chain and navigate back to this end, then
+// fix up n_first to point to it?  This is at the risk of a memory
+// leak, but at least Xastir might continue to run.
+
+        }
+    }
+    else {  // Not the first element in the list.  Fix up pointers
+            // to skip the current record.
+        p_rem->n_prev->n_next = p_rem->n_next;
+    }
+
+
+    if (p_rem->n_next == NULL) { // Appears to be last element in list
+
+        if (n_last == p_rem) {   // Yes, tail of list
+
+            // Make list tail point to previous element in list (or
+            // NULL) so that we can delete the current record.
+            n_last = p_rem->n_prev;
+        }
+        else {  // No, not last element in list.  Problem!  The list
+                // pointers are inconsistent for some reason.  The
+                // chain has been broken and we have dangling
+                // pointers.
+
+            fprintf(stderr,
+                "remove_name(): ERROR: p->n_next == NULL but p != n_last\n");
+
+abort();    // Cause a core dump at this point
+// Perhaps we could do some repair to the list pointers here?  Start
+// at the other end of the chain and navigate back to this end, then
+// fix up n_last to point to it?  This is at the risk of a memory
+// leak, but at least Xastir might continue to run.
+
+        }
+    }
+    else {  // Not the last element in the list.  Fix up pointers to
+            // skip the current record.
+        p_rem->n_next->n_prev = p_rem->n_prev;
+    }
+
+
+    // Update our pointer shortcuts.  Pass the removed hash_key to
+    // the function so that we can try to redo just that hash_key
+    // pointer.
+    if (update_shortcuts) {
+//fprintf(stderr,"\t\t\t\t\t\tRemoval of hash key: %i\n", hash_key);
+
+        // The -1 tells the function to redo all of the hash table
+        // pointers because we deleted one of them.  Later we could
+        // optimize this so that only the specific pointer is fixed
+        // up.
+        station_shortcuts_update_function(-1, NULL);
+    }
+
+
+}
+
+
+
+/*
+ *  Station Capabilities, Queries and Responses      [APRS Reference, chapter 15]
+ */
+//
+// According to Bob Bruninga we should wait a random time between 0
+// and 120 seconds before responding to a general query.  We use the
+// delayed-ack mechanism to add this randomness.
+//
+// NOTE:  We may end up sending these to RF when the query came in
+// over the internet.  We should check that.
+//
+int process_query( /*@unused@*/ char *call_sign, /*@unused@*/ char *path,char *message,TAprsPort port, /*@unused@*/ int third_party) {
+    char temp[100];
+    int ok = 0;
+    float randomize;
+
+
+    // Generate a random number between 0.0 and 1.0
+    randomize = rand() / (float)RAND_MAX;
+
+    // Convert to between 0 and 120 seconds
+    randomize = randomize * 120.0;
+
+    // Check for proper usage of the ?APRS? query
+//
+// NOTE:  We need to add support in here for the radius circle as
+// listed in the spec for general queries.  Right now we respond to
+// all queries, whether we're inside the circle or not.  Spec says
+// this:
+//
+// ?Query?Lat,Long,Radius
+// 1  n  1 n 1 n  1  4 Bytes
+//
+// i.e. ?APRS? 34.02,-117.15,0200
+//
+// Note leading space in latitude as its value is positive.
+// Lat/long are floating point degrees.  N/E are positive, indicated
+// by a leading space.  S/W are negative.  Radius is in miles
+// expressed as a fixed 4-digit number in whole miles.  All stations
+// inside the specified circle should respond with a position report
+// and a status report.
+//
+    if (!ok && strncmp(message,"APRS?",5)==0) {
+        //
+        // Initiate a delayed transmit of our own posit.
+        // UpdateTime() uses posit_next_time to decide when to
+        // transmit, so we'll just muck with that.
+        //
+        if ( posit_next_time - sec_now() < randomize ) {
+            // Skip setting it, as we'll transmit soon anyway
+        }
+        else {
+            posit_next_time = (size_t)(sec_now() + randomize);
+        }
+        ok = 1;
+    }
+    // Check for illegal case for the ?APRS? query
+    if (!ok && g_strncasecmp(message,"APRS?",5)==0) {
+        ok = 1;
+//        fprintf(stderr,
+//            "%s just queried us with an illegal query: %s\n",
+//            call_sign,
+//            message),
+//        fprintf(stderr,
+//            "Consider sending a message, asking them to follow the spec\n");
+    }
+
+    // Check for proper usage of the ?WX? query
+    if (!ok && strncmp(message,"WX?",3)==0) {
+       // WX is not supported
+
+        ok = 1;
+    }
+    
+    // Check for illegal case for the ?WX? query
+    if (!ok && g_strncasecmp(message,"WX?",3)==0) {
+        ok = 1;
+//        fprintf(stderr,
+//            "%s just queried us with an illegal query: %s\n",
+//            call_sign,
+//            message),
+//        fprintf(stderr,
+//            "Consider sending a message, asking them to follow the spec\n");
+    }
+
+    return(ok);
+}
+
+
+
+/*
+ *  Get new trail color for a call
+ */
+int new_trail_color(char *call) {
+    // TODO - stub
+
+    return(0);
+}
+
+
+/*
+ *  Insert existing element into time ordered list before p_time
+ *  The p_new record ends up being on the "older" side of p_time when
+ *  all done inserting (closer in the list to the t_oldest pointer).
+ *  If p_time == NULL, insert at newest end of list.
+ */
+void insert_time(AprsDataRow *p_new, AprsDataRow *p_time) {
+       
+       
+    // Set up pointer to next record (or NULL), sorted by time
+    p_new->t_newer = p_time;
+
+    if (p_time == NULL) {               // add to end of list (becomes newest station)
+
+        p_new->t_older = t_newest;         // connect to previous end of list
+
+        if (t_newest == NULL)             // if list empty, create list
+            t_oldest = p_new;            // it's now our only station on the list
+        else
+            t_newest->t_newer = p_new;     // list not empty, link original last record to our new one
+
+        t_newest = p_new;                 // end of list (newest record pointer) points to our new record
+    }
+
+    else {                            // Else we're inserting into the middle of the list somewhere
+
+        p_new->t_older = p_time->t_older;
+
+        if (p_time->t_older == NULL)     // add to end of list (new record becomes oldest station)
+            t_oldest = p_new;
+        else
+            p_time->t_older->t_newer = p_new; // else 
+
+        p_time->t_older = p_new;
+    }
+    
+    // TODO - this may need implementing? 
+    /*
+    if(_aprs_max_stations > 0 && stations_heard > _aprs_max_stations)
+    {
+       // Delete the oldest
+       if(t_oldest != NULL)
+       {
+  //           fprintf(stderr, "DEBUG: oldest station deleted\n");
+               
+               delete_station_memory(t_oldest);
+       }
+       else
+       {
+               stations_heard++;
+       }
+    }
+    else
+    {
+//     fprintf(stderr, "DEBUG: Stations in memory: %d\n", stations_heard);
+       
+       stations_heard++; // Should really be done in the new station method
+    }
+    */
+}
+
+
+
+
+/*
+ *  Remove element from time ordered list
+ */
+void remove_time(AprsDataRow *p_rem) {      // todo: return pointer to next element
+
+    if (p_rem->t_older == NULL) { // Appears to be first element in list
+
+        if (t_oldest == p_rem) {  // Yes, head of list (oldest)
+
+            // Make oldest list head point to 2nd element in list (or NULL)
+            // so that we can delete the current record.
+            t_oldest = p_rem->t_newer;
+        }
+        else {  // No, not first (oldest) element in list.  Problem!
+                // The list pointers are inconsistent for some
+                // reason.  The chain has been broken and we have
+                // dangling pointers.
+
+            fprintf(stderr,
+                "remove_time(): ERROR: p->t_older == NULL but p != t_oldest\n");
+
+abort();    // Cause a core dump at this point
+// Perhaps we could do some repair to the list pointers here?  Start
+// at the other end of the chain and navigate back to this end, then
+// fix up t_oldest to point to it?  This is at the risk of a memory
+// leak, but at least Xastir might continue to run.
+
+        }
+    }
+    else {  // Not the first (oldest) element in the list.  Fix up
+            // pointers to skip the current record.
+        p_rem->t_older->t_newer = p_rem->t_newer;
+    }
+
+
+    if (p_rem->t_newer == NULL) { // Appears to be last (newest) element in list
+
+        if (t_newest == p_rem) {   // Yes, head of list (newest)
+
+            // Make newest list head point to previous element in
+            // list (or NULL) so that we can delete the current
+            // record.
+            t_newest = p_rem->t_older;
+        }
+        else {  // No, not newest element in list.  Problem!  The
+                // list pointers are inconsistent for some reason.
+                // The chain has been broken and we have dangling
+                // pointers.
+
+            fprintf(stderr,
+                "remove_time(): ERROR: p->t_newer == NULL but p != t_newest\n");
+
+abort();    // Cause a core dump at this point
+// Perhaps we could do some repair to the list pointers here?  Start
+// at the other end of the chain and navigate back to this end, then
+// fix up t_newest to point to it?  This is at the risk of a memory
+// leak, but at least Xastir might continue to run.
+
+        }
+    }
+    else {  // Not the newest element in the list.  Fix up pointers
+            // to skip the current record.
+        p_rem->t_newer->t_older = p_rem->t_older;
+    }
+    
+    stations_heard--;
+}
+
+
+
+
+/*
+ *  Move station record before p_time in time ordered list
+ */
+void move_station_time(AprsDataRow *p_curr, AprsDataRow *p_time) {
+
+    if (p_curr != NULL) {               // need a valid record
+        remove_time(p_curr);
+        insert_time(p_curr,p_time);
+    }
+}
+
+
+/*
+ *  Extract powergain and/or range from APRS info field:
+ * "PHG1234/", "PHG1234", or "RNG1234" from APRS data extension.
+ */
+int extract_powergain_range(char *info, char *phgd) {
+    int i,found,len;
+    char *info2;
+
+
+//fprintf(stderr,"Info:%s\n",info);
+
+    // Check whether two strings of interest are present and snag a
+    // pointer to them.
+    info2 = strstr(info,"RNG");
+    if (!info2)
+        info2 = strstr(info,"PHG");
+    if (!info2) {
+        phgd[0] = '\0';
+        return(0);
+    }
+
+    found=0;
+    len = (int)strlen(info2);
+
+    if (len >= 9 && strncmp(info2,"PHG",3)==0
+            && info2[7]=='/'
+            && info2[8]!='A'  // trailing '/' not defined in Reference...
+            && g_ascii_isdigit(info2[3])
+            && g_ascii_isdigit(info2[4])
+            && g_ascii_isdigit(info2[5])
+            && g_ascii_isdigit(info2[6])) {
+        substr(phgd,info2,7);
+        found = 1;
+        for (i=0;i<=len-8;i++)        // delete powergain from data extension field
+            info2[i] = info2[i+8];
+    }
+    else {
+        if (len >= 7 && strncmp(info2,"PHG",3)==0
+                && g_ascii_isdigit(info2[3])
+                && g_ascii_isdigit(info2[4])
+                && g_ascii_isdigit(info2[5])
+                && g_ascii_isdigit(info2[6])) {
+            substr(phgd,info2,7);
+            found = 1;
+            for (i=0;i<=len-7;i++)        // delete powergain from data extension field
+                info2[i] = info2[i+7];
+        }
+        else if (len >= 7 && strncmp(info2,"RNG",3)==0
+                && g_ascii_isdigit(info2[3])
+                && g_ascii_isdigit(info2[4])
+                && g_ascii_isdigit(info2[5])
+                && g_ascii_isdigit(info2[6])) {
+            substr(phgd,info2,7);
+            found = 1;
+            for (i=0;i<=len-7;i++)        // delete powergain from data extension field
+                info2[i] = info2[i+7];
+        }
+        else {
+            phgd[0] = '\0';
+        }
+    }
+    return(found);
+}
+
+
+/*
+ *  Extract omnidf from APRS info field          "DFS1234/"    from APRS data extension
+ */
+int extract_omnidf(char *info, char *phgd) {
+    int i,found,len;
+
+    found=0;
+    len = (int)strlen(info);
+    if (len >= 8 && strncmp(info,"DFS",3)==0 && info[7]=='/'    // trailing '/' not defined in Reference...
+                 && g_ascii_isdigit(info[3]) && g_ascii_isdigit(info[5]) && g_ascii_isdigit(info[6])) {
+        substr(phgd,info,7);
+        for (i=0;i<=len-8;i++)        // delete omnidf from data extension field
+            info[i] = info[i+8];
+        return(1);
+    }
+    else {
+        phgd[0] = '\0';
+        return(0);
+    }
+}
+
+
+//
+//  Extract speed and/or course from beginning of info field
+//
+// Returns course in degrees, speed in KNOTS.
+//
+int extract_speed_course(char *info, char *speed, char *course) {
+    int i,found,len;
+
+    len = (int)strlen(info);
+    found = 0;
+    if (len >= 7) {
+        found = 1;
+        for(i=0; found && i<7; i++) {           // check data format
+            if (i==3) {                         // check separator
+                if (info[i]!='/')
+                    found = 0;
+            }
+            else {
+                if( !( g_ascii_isdigit(info[i])
+                        || (info[i] == ' ')     // Spaces and periods are allowed.  Need these
+                        || (info[i] == '.') ) ) // here so that we can get the field deleted
+                    found = 0;
+            }
+        }
+    }
+    if (found) {
+        substr(course,info,3);
+        substr(speed,info+4,3);
+        for (i=0;i<=len-7;i++)        // delete speed/course from info field
+            info[i] = info[i+7];
+    }
+    if (!found || atoi(course) < 1) {   // course 0 means undefined
+//        speed[0] ='\0';   // Don't do this!  We can have a valid
+//        speed without a valid course.
+        course[0]='\0';
+    }
+    else {  // recheck data format looking for undefined fields
+        for(i=0; i<2; i++) {
+            if( !(g_ascii_isdigit(speed[i]) ) )
+                speed[0] = '\0';
+            if( !(g_ascii_isdigit(course[i]) ) )
+                course[0] = '\0';
+        }
+    }
+
+    return(found);
+}
+
+
+
+
+
+/*
+ *  Extract Area Object
+ */
+void extract_area(AprsDataRow *p_station, char *data) {
+    int i, val, len;
+    unsigned int uval;
+    AprsAreaObject temp_area;
+
+    /* NOTE: If we are here, the symbol was the area symbol.  But if this
+       is a slightly corrupted packet, we shouldn't blow away the area info
+       for this station, since it could be from a previously received good
+       packet.  So we will work on temp_area and only copy to p_station at
+       the end, returning on any error as we parse. N7TAP */
+
+    //fprintf(stderr,"Area Data: %s\n", data);
+
+    len = (int)strlen(data);
+    val = data[0] - '0';
+    if (val >= 0 && val <= AREA_MAX) {
+        temp_area.type = val;
+        val = data[4] - '0';
+        if (data[3] == '/') {
+            if (val >=0 && val <= 9) {
+                temp_area.color = val;
+            }
+            else {
+                return;
+            }
+        }
+        else if (data[3] == '1') {
+            if (val >=0 && val <= 5) {
+                temp_area.color = 10 + val;
+            }
+            else {
+                return;
+            }
+        }
+
+        val = 0;
+        if (isdigit((int)data[1]) && isdigit((int)data[2])) {
+            val = (10 * (data[1] - '0')) + (data[2] - '0');
+        }
+        else {
+            return;
+        }
+        temp_area.sqrt_lat_off = val;
+
+        val = 0;
+        if (isdigit((int)data[5]) && isdigit((int)data[6])) {
+            val = (10 * (data[5] - '0')) + (data[6] - '0');
+        }
+        else {
+            return;
+        }
+        temp_area.sqrt_lon_off = val;
+
+        for (i = 0; i <= len-7; i++) // delete area object from data extension field
+            data[i] = data[i+7];
+        len -= 7;
+
+        if (temp_area.type == AREA_LINE_RIGHT || temp_area.type == AREA_LINE_LEFT) {
+            if (data[0] == '{') {
+                if (sscanf(data, "{%u}", &uval) == 1) {
+                    temp_area.corridor_width = uval & 0xffff;
+                    for (i = 0; i <= len; i++)
+                        if (data[i] == '}')
+                            break;
+                    uval = i+1;
+                    for (i = 0; i <= (int)(len-uval); i++)
+                        data[i] = data[i+uval]; // delete corridor width
+                }
+                else {
+                    temp_area.corridor_width = 0;
+                    return;
+                }
+            }
+            else {
+                temp_area.corridor_width = 0;
+            }
+        }
+        else {
+            temp_area.corridor_width = 0;
+        }
+    }
+    else {
+        return;
+    }
+
+    memcpy(&(p_station->aprs_symbol.area_object), &temp_area, sizeof(AprsAreaObject));
+
+}
+
+
+
+/*
+ *  Extract probability_max data from APRS info field: "Pmax1.23,"
+ *  Please note the ending comma.  We use it to delimit the field.
+ */
+int extract_probability_max(char *info, char *prob_max, int prob_max_size) {
+    int len,done;
+    char *c;
+    char *d;
+
+
+
+    len = (int)strlen(info);
+    if (len < 6) {          // Too short
+        prob_max[0] = '\0';
+        return(0);
+    }
+
+    c = strstr(info,"Pmax");
+    if (c == NULL) {        // Pmax not found
+        prob_max[0] = '\0';
+        return(0);
+    }
+
+    c = c+4;    // Skip the Pmax part
+    // Find the ending comma
+    d = c;
+    done = 0;
+    while (!done) {
+        if (*d == ',') {    // We're done
+            done++;
+        }
+        else {
+            d++;
+        }
+
+        // Check for string too long
+        if ( ((d-c) > 10) && !done) {    // Something is wrong, we should be done by now
+            prob_max[0] = '\0';
+            return(0);
+        }
+    }
+
+    // Copy the substring across
+    snprintf(prob_max,
+        prob_max_size,
+        "%s",
+        c);
+    prob_max[d-c] = '\0';
+    prob_max[10] = '\0';    // Just to make sure
+
+    // Delete data from data extension field 
+    d++;    // Skip the comma
+    done = 0;
+    while (!done) {
+        *(c-4) = *d;
+        if (*d == '\0')
+            done++;
+        c++;
+        d++;
+    }
+    return(1);
+}
+
+
+
+
+/*
+ *  Extract probability_min data from APRS info field: "Pmin1.23,"
+ *  Please note the ending comma.  We use it to delimit the field.
+ */
+int extract_probability_min(char *info, char *prob_min, int prob_min_size) {
+    int len,done;
+    char *c;
+    char *d;
+
+    len = (int)strlen(info);
+    if (len < 6) {          // Too short
+        prob_min[0] = '\0';
+        return(0);
+    }
+
+    c = strstr(info,"Pmin");
+    if (c == NULL) {        // Pmin not found
+        prob_min[0] = '\0';
+        return(0);
+    }
+
+    c = c+4;    // Skip the Pmin part
+    // Find the ending comma
+    d = c;
+    done = 0;
+    while (!done) {
+        if (*d == ',') {    // We're done
+            done++;
+        }
+        else {
+            d++;
+        }
+
+        // Check for string too long
+        if ( ((d-c) > 10) && !done) {    // Something is wrong, we should be done by now
+            prob_min[0] = '\0';
+            return(0);
+        }
+    }
+
+    // Copy the substring across
+    xastir_snprintf(prob_min,
+        prob_min_size,
+        "%s",
+        c);
+    prob_min[d-c] = '\0';
+    prob_min[10] = '\0';    // Just to make sure
+
+    // Delete data from data extension field 
+    d++;    // Skip the comma
+    done = 0;
+    while (!done) {
+        *(c-4) = *d;
+        if (*d == '\0')
+            done++;
+        c++;
+        d++;
+    }
+
+    return(1);
+}
+
+
+
+
+/*
+ *  Extract signpost data from APRS info field: "{123}", an APRS data extension
+ *  Format can be {1}, {12}, or {123}.  Letters or digits are ok.
+ */
+int extract_signpost(char *info, char *signpost) {
+    int i,found,len,done;
+
+//0123456
+//{1}
+//{12}
+//{121}
+
+    found=0;
+    len = (int)strlen(info);
+    if ( (len > 2)
+            && (info[0] == '{')
+            && ( (info[2] == '}' ) || (info[3] == '}' ) || (info[4] == '}' ) ) ) {
+
+        i = 1;
+        done = 0;
+        while (!done) {                 // Snag up to three digits
+            if (info[i] == '}') {       // We're done
+                found = i;              // found = position of '}' character
+                done++;
+            }
+            else {
+                signpost[i-1] = info[i];
+            }
+
+            i++;
+
+            if ( (i > 4) && !done) {    // Something is wrong, we should be done by now
+                done++;
+                signpost[0] = '\0';
+                return(0);
+            }
+        }
+        substr(signpost,info+1,found-1);
+        found++;
+        for (i=0;i<=len-found;i++) {    // delete omnidf from data extension field
+            info[i] = info[i+found];
+        }
+        return(1);
+    }
+    else {
+        signpost[0] = '\0';
+        return(0);
+    }
+}
+
+
+
+// is_altnet()
+//
+// Returns true if station fits the current altnet description.
+//
+int is_altnet(AprsDataRow *p_station) {
+    char temp_altnet_call[20+1];
+    char temp2[20+1];
+    char *net_ptr;
+    int  altnet_match;
+    int  result;
+
+
+    // Snag a possible altnet call out of the record for later use
+    if (p_station->node_path_ptr != NULL)
+        substr(temp_altnet_call, p_station->node_path_ptr, MAX_CALLSIGN);
+    else
+        temp_altnet_call[0] = '\0';
+
+    // Save for later
+    snprintf(temp2,
+        sizeof(temp2),
+        "%s",
+        temp_altnet_call);
+
+    if ((net_ptr = strchr(temp_altnet_call, ',')))
+        *net_ptr = '\0';    // Chop the string at the first ',' character
+
+    for (altnet_match = (int)strlen(altnet_call); altnet && altnet_call[altnet_match-1] == '*'; altnet_match--);
+
+    result = (!strncmp(temp_altnet_call, altnet_call, (size_t)altnet_match)
+                 || !strcmp(temp_altnet_call, "local")
+                 || !strncmp(temp_altnet_call, "SPC", 3)
+                 || !strcmp(temp_altnet_call, "SPECL")
+                 || ( is_my_station(p_station) ) ) ;  // It's my callsign/SSID
+
+    return(result);
+}
+
+
+
+int is_num_or_sp(char ch) 
+{
+    return((int)((ch >= '0' && ch <= '9') || ch == ' '));
+}
+
+
+char *get_time(char *time_here) {
+    struct tm *time_now;
+    time_t timenw;
+
+    (void)time(&timenw);
+    time_now = localtime(&timenw);
+    (void)strftime(time_here,MAX_TIME,"%m%d%Y%H%M%S",time_now);
+    return(time_here);
+}
+
+
+static void clear_area(AprsDataRow *p_station) {
+    p_station->aprs_symbol.area_object.type           = AREA_NONE;
+    p_station->aprs_symbol.area_object.color          = AREA_GRAY_LO;
+    p_station->aprs_symbol.area_object.sqrt_lat_off   = 0;
+    p_station->aprs_symbol.area_object.sqrt_lon_off   = 0;
+    p_station->aprs_symbol.area_object.corridor_width = 0;
+}
+
+
+
+
+// Check for valid overlay characters:  'A-Z', '0-9', and 'a-j'.  If
+// 'a-j', it's from a compressed posit, and we need to convert it to
+// '0-9'.
+void overlay_symbol(char symbol, char data, AprsDataRow *fill) {
+
+    if ( data != '/' && data !='\\') {  // Symbol overlay
+
+        if (data >= 'a' && data <= 'j') {
+            // Found a compressed posit numerical overlay
+            data = data - 'a'+'0';  // Convert to a digit
+        }
+        if ( (data >= '0' && data <= '9')
+                || (data >= 'A' && data <= 'Z') ) {
+            // Found normal overlay character
+            fill->aprs_symbol.aprs_type = '\\';
+            fill->aprs_symbol.special_overlay = data;
+        }
+        else {
+            // Bad overlay character.  Don't use it.  Insert the
+            // normal alternate table character instead.
+            fill->aprs_symbol.aprs_type = '\\';
+            fill->aprs_symbol.special_overlay='\0';
+        }
+    }
+    else {    // No overlay character
+        fill->aprs_symbol.aprs_type = data;
+        fill->aprs_symbol.special_overlay='\0';
+    }
+    fill->aprs_symbol.aprs_symbol = symbol;
+}
+
+
+/*
+ *  Extract Time from begin of line      [APRS Reference, chapter 6]
+ *
+ * If a time string is found in "data", it is deleted from the
+ * beginning of the string.
+ */
+int extract_time(AprsDataRow *p_station, char *data, int type) {
+    int len, i;
+    int ok = 0;
+
+    // todo: better check of time data ranges
+    len = (int)strlen(data);
+    if (type == APRS_WX2) {
+        // 8 digit time from stand-alone positionless weather stations...
+        if (len > 8) {
+            // MMDDHHMM   zulu time
+            // MM 01-12         todo: better check of time data ranges
+            // DD 01-31
+            // HH 01-23
+            // MM 01-59
+            ok = 1;
+            for (i=0;ok && i<8;i++)
+                if (!isdigit((int)data[i]))
+                    ok = 0;
+            if (ok) {
+//                substr(p_station->station_time,data+2,6);
+//                p_station->station_time_type = 'z';
+                for (i=0;i<=len-8;i++)         // delete time from data
+                    data[i] = data[i+8];
+            }
+        }
+    }
+    else {
+        if (len > 6) {
+            // Status messages only with optional zulu format
+            // DK7IN: APRS ref says one of 'z' '/' 'h', but I found 'c' at HB9TJM-8   ???
+            if (toupper(data[6])=='Z' || data[6]=='/' || toupper(data[6])=='H')
+                ok = 1;
+            for (i=0;ok && i<6;i++)
+                if (!isdigit((int)data[i]))
+                    ok = 0;
+            if (ok) {
+//                substr(p_station->station_time,data,6);
+//                p_station->station_time_type = data[6];
+                for (i=0;i<=len-7;i++)         // delete time from data
+                    data[i] = data[i+7];
+            }
+        }
+    }
+    return(ok);
+}
+
+
+
+// Breaks up a string into substrings using comma as the delimiter.
+// Makes each entry in the array of char ptrs point to one
+// substring.  Modifies incoming string and cptr[] array.  Send a
+// character constant string to it and you'll get an instant
+// segfault (the function can't modify a char constant string).
+//
+void split_string( char *data, char *cptr[], int max ) {
+  int ii;
+  char *temp;
+  char *current = data;
+
+
+  // NULL each char pointer
+  for (ii = 0; ii < max; ii++) {
+    cptr[ii] = NULL;
+  }
+
+  // Save the beginning substring address
+  cptr[0] = current;
+
+  for (ii = 1; ii < max; ii++) {
+    temp = strchr(current,',');  // Find next comma
+
+    if(!temp) { // No commas found 
+      return; // All done with string
+    }
+
+    // Store pointer to next substring in array
+    cptr[ii] = &temp[1];
+    current  = &temp[1];
+
+    // Overwrite comma with end-of-string char and bump pointer by
+    // one.
+    temp[0] = '\0';
+  }
+}
+
+
+
+/* check data format    123 ___ ... */
+// We wish to count how many ' ' or '.' characters we find.  If it
+// equals zero or the field width, it might be a weather field.  If
+// not, then it might be part of a comment field or something else.
+//
+int is_weather_data(char *data, int len) {
+    int ok = 1;
+    int i;
+    int count = 0;
+
+    for (i=0;ok && i<len;i++)
+        if (!is_aprs_chr(data[i]))
+            ok = 0;
+
+    // Count filler characters.  Must equal zero or field width to
+    // be a weather field.  There doesn't appear to be a case where
+    // a single period is allowed in any weather-related fields.
+    //
+    for (i=0;ok && i<len;i++) {
+        if (data[i] == ' ' || data[i] == '.') {
+            count++;
+        }
+    }
+    if (count != 0 && count != len) {
+        ok = 0;
+    }
+
+    return(ok);
+}
+
+
+
+
+
+
+/* convert latitude from string to long with 1/100 sec resolution */
+//
+// Input is in [D]DMM.MM[MM]N format (degrees/decimal
+// minutes/direction)
+//
+long convert_lat_s2l(char *lat) {      /* N=0°, Ctr=90°, S=180° */
+    long centi_sec;
+    char copy[15];
+    char n[15];
+    char *p;
+    char offset;
+
+
+    // Find the decimal point if present
+    p = strstr(lat, ".");
+
+    if (p == NULL)  // No decimal point found
+        return(0l);
+
+    offset = p - lat;   // Arithmetic on pointers
+    switch (offset) {
+        case 0:     // .MM[MM]N
+            return(0l); // Bad, no degrees or minutes
+            break;
+        case 1:     // M.MM[MM]N
+            return(0l); // Bad, no degrees
+            break;
+        case 2:     // MM.MM[MM]N
+            return(0l); // Bad, no degrees
+            break;
+        case 3:     // DMM.MM[MM]N
+            xastir_snprintf(copy,
+                sizeof(copy),
+                "0%s",  // Add a leading '0'
+                lat);
+            break;
+        case 4:     // DDMM.MM[MM]N
+            xastir_snprintf(copy,
+                sizeof(copy),
+                "%s",   // Copy verbatim
+                lat);
+            break;
+        default:
+            break;
+    }
+
+    copy[14] = '\0';
+    centi_sec=0l;
+    if (copy[4]=='.'
+            && (   (char)toupper((int)copy[ 5])=='N'
+                || (char)toupper((int)copy[ 6])=='N'
+                || (char)toupper((int)copy[ 7])=='N'
+                || (char)toupper((int)copy[ 8])=='N'
+                || (char)toupper((int)copy[ 9])=='N'
+                || (char)toupper((int)copy[10])=='N'
+                || (char)toupper((int)copy[11])=='N'
+                || (char)toupper((int)copy[ 5])=='S'
+                || (char)toupper((int)copy[ 6])=='S'
+                || (char)toupper((int)copy[ 7])=='S'
+                || (char)toupper((int)copy[ 8])=='S'
+                || (char)toupper((int)copy[ 9])=='S'
+                || (char)toupper((int)copy[10])=='S'
+                || (char)toupper((int)copy[11])=='S')) {
+
+        substr(n, copy, 2);       // degrees
+        centi_sec=atoi(n)*60*60*100;
+
+        substr(n, copy+2, 2);     // minutes
+        centi_sec += atoi(n)*60*100;
+
+        substr(n, copy+5, 4);     // fractional minutes
+        // Keep the fourth digit if present, as it resolves to 0.6
+        // of a 1/100 sec resolution.  Two counts make one count in
+        // the Xastir coordinate system.
+        // Extend the digits to full precision by adding zeroes on
+        // the end.
+        strncat(n, "0000", sizeof(n) - strlen(n));
+
+        // Get rid of the N/S character
+        if (!isdigit((int)n[2]))
+            n[2] = '0';
+        if (!isdigit((int)n[3]))
+            n[3] = '0';
+
+        // Terminate substring at the correct digit
+        n[4] = '\0';
+//fprintf(stderr,"Lat: %s\n", n);
+
+        // Add 0.5 (Poor man's rounding)
+        centi_sec += (long)((atoi(n) * 0.6) + 0.5);
+
+        if (       (char)toupper((int)copy[ 5])=='N'
+                || (char)toupper((int)copy[ 6])=='N'
+                || (char)toupper((int)copy[ 7])=='N'
+                || (char)toupper((int)copy[ 8])=='N'
+                || (char)toupper((int)copy[ 9])=='N'
+                || (char)toupper((int)copy[10])=='N'
+                || (char)toupper((int)copy[11])=='N') {
+            centi_sec = -centi_sec;
+        }
+
+        centi_sec += 90*60*60*100;
+    }
+    return(centi_sec);
+}
+
+
+
+
+
+/* convert longitude from string to long with 1/100 sec resolution */
+//
+// Input is in [DD]DMM.MM[MM]W format (degrees/decimal
+// minutes/direction).
+//
+long convert_lon_s2l(char *lon) {     /* W=0°, Ctr=180°, E=360° */
+    long centi_sec;
+    char copy[16];
+    char n[16];
+    char *p;
+    char offset;
+
+
+    // Find the decimal point if present
+    p = strstr(lon, ".");
+
+    if (p == NULL)  // No decimal point found
+        return(0l);
+
+    offset = p - lon;   // Arithmetic on pointers
+    switch (offset) {
+        case 0:     // .MM[MM]N
+            return(0l); // Bad, no degrees or minutes
+            break;
+        case 1:     // M.MM[MM]N
+            return(0l); // Bad, no degrees
+            break;
+        case 2:     // MM.MM[MM]N
+            return(0l); // Bad, no degrees
+            break;
+        case 3:     // DMM.MM[MM]N
+            xastir_snprintf(copy,
+                sizeof(copy),
+                "00%s",  // Add two leading zeroes
+                lon);
+            break;
+        case 4:     // DDMM.MM[MM]N
+            xastir_snprintf(copy,
+                sizeof(copy),
+                "0%s",   // Add leading '0'
+                lon);
+            break;
+        case 5:     // DDDMM.MM[MM]N
+            xastir_snprintf(copy,
+                sizeof(copy),
+                "%s",   // Copy verbatim
+                lon);
+            break;
+        default:
+            break;
+    }
+
+    copy[15] = '\0';
+    centi_sec=0l;
+    if (copy[5]=='.'
+            && (   (char)toupper((int)copy[ 6])=='W'
+                || (char)toupper((int)copy[ 7])=='W'
+                || (char)toupper((int)copy[ 8])=='W'
+                || (char)toupper((int)copy[ 9])=='W'
+                || (char)toupper((int)copy[10])=='W'
+                || (char)toupper((int)copy[11])=='W'
+                || (char)toupper((int)copy[12])=='W'
+                || (char)toupper((int)copy[ 6])=='E'
+                || (char)toupper((int)copy[ 7])=='E'
+                || (char)toupper((int)copy[ 8])=='E'
+                || (char)toupper((int)copy[ 9])=='E'
+                || (char)toupper((int)copy[10])=='E'
+                || (char)toupper((int)copy[11])=='E'
+                || (char)toupper((int)copy[12])=='E')) {
+
+        substr(n,copy,3);    // degrees 013
+        centi_sec=atoi(n)*60*60*100;
+
+        substr(n,copy+3,2);  // minutes 26
+        centi_sec += atoi(n)*60*100;
+        // 01326.66E  01326.660E
+
+        substr(n,copy+6,4);  // fractional minutes 66E 660E or 6601
+        // Keep the fourth digit if present, as it resolves to 0.6
+        // of a 1/100 sec resolution.  Two counts make one count in
+        // the Xastir coordinate system.
+        // Extend the digits to full precision by adding zeroes on
+        // the end.
+        strncat(n, "0000", sizeof(n) - strlen(n));
+
+        // Get rid of the E/W character
+        if (!isdigit((int)n[2]))
+            n[2] = '0';
+        if (!isdigit((int)n[3]))
+            n[3] = '0';
+
+        n[4] = '\0';    // Make sure substring is terminated
+//fprintf(stderr,"Lon: %s\n", n);
+
+        // Add 0.5 (Poor man's rounding)
+        centi_sec += (long)((atoi(n) * 0.6) + 0.5);
+
+        if (       (char)toupper((int)copy[ 6])=='W'
+                || (char)toupper((int)copy[ 7])=='W'
+                || (char)toupper((int)copy[ 8])=='W'
+                || (char)toupper((int)copy[ 9])=='W'
+                || (char)toupper((int)copy[10])=='W'
+                || (char)toupper((int)copy[11])=='W'
+                || (char)toupper((int)copy[12])=='W') {
+            centi_sec = -centi_sec;
+        }
+
+        centi_sec +=180*60*60*100;;
+    }
+    return(centi_sec);
+}
+
+
+
+APRS_Symbol *id_callsign(char *call_sign, char * to_call) {
+    char *ptr;
+    char *id = "/aUfbYX's><OjRkv";
+    char hold[MAX_CALLSIGN+1];
+    int index;
+    static APRS_Symbol symbol;
+
+    symbol.aprs_symbol = '/';
+    symbol.special_overlay = '\0';
+    symbol.aprs_type ='/';
+    ptr=strchr(call_sign,'-');
+    if(ptr!=NULL)                      /* get symbol from SSID */
+        if((index=atoi(ptr+1))<= 15)
+            symbol.aprs_symbol = id[index];
+
+    if (strncmp(to_call, "GPS", 3) == 0 || strncmp(to_call, "SPC", 3) == 0 || strncmp(to_call, "SYM", 3) == 0) 
+    {
+        substr(hold, to_call+3, 3);
+        if ((ptr = strpbrk(hold, "->,")) != NULL)
+            *ptr = '\0';
+
+        if (strlen(hold) >= 2) {
+            switch (hold[0]) {
+                case 'A':
+                    symbol.aprs_type = '\\';
+
+                case 'P':
+                    if (('0' <= hold[1] && hold[1] <= '9') || ('A' <= hold[1] && hold[1] <= 'Z'))
+                        symbol.aprs_symbol = hold[1];
+
+                    break;
+
+                case 'O':
+                    symbol.aprs_type = '\\';
+
+                case 'B':
+                    switch (hold[1]) {
+                        case 'B':
+                            symbol.aprs_symbol = '!';
+                            break;
+                        case 'C':
+                            symbol.aprs_symbol = '"';
+                            break;
+                        case 'D':
+                            symbol.aprs_symbol = '#';
+                            break;
+                        case 'E':
+                            symbol.aprs_symbol = '$';
+                            break;
+                        case 'F':
+                            symbol.aprs_symbol = '%';
+                            break;
+                        case 'G':
+                            symbol.aprs_symbol = '&';
+                            break;
+                        case 'H':
+                            symbol.aprs_symbol = '\'';
+                            break;
+                        case 'I':
+                            symbol.aprs_symbol = '(';
+                            break;
+                        case 'J':
+                            symbol.aprs_symbol = ')';
+                            break;
+                        case 'K':
+                            symbol.aprs_symbol = '*';
+                            break;
+                        case 'L':
+                            symbol.aprs_symbol = '+';
+                            break;
+                        case 'M':
+                            symbol.aprs_symbol = ',';
+                            break;
+                        case 'N':
+                            symbol.aprs_symbol = '-';
+                            break;
+                        case 'O':
+                            symbol.aprs_symbol = '.';
+                            break;
+                        case 'P':
+                            symbol.aprs_symbol = '/';
+                            break;
+                    }
+                    break;
+
+                case 'D':
+                    symbol.aprs_type = '\\';
+
+                case 'H':
+                    switch (hold[1]) {
+                        case 'S':
+                            symbol.aprs_symbol = '[';
+                            break;
+                        case 'T':
+                            symbol.aprs_symbol = '\\';
+                            break;
+                        case 'U':
+                            symbol.aprs_symbol = ']';
+                            break;
+                        case 'V':
+                            symbol.aprs_symbol = '^';
+                            break;
+                        case 'W':
+                            symbol.aprs_symbol = '_';
+                            break;
+                        case 'X':
+                            symbol.aprs_symbol = '`';
+                            break;
+                    }
+                    break;
+
+                case 'N':
+                    symbol.aprs_type = '\\';
+
+                case 'M':
+                    switch (hold[1]) {
+                        case 'R':
+                            symbol.aprs_symbol = ':';
+                            break;
+                        case 'S':
+                            symbol.aprs_symbol = ';';
+                            break;
+                        case 'T':
+                            symbol.aprs_symbol = '<';
+                            break;
+                        case 'U':
+                            symbol.aprs_symbol = '=';
+                            break;
+                        case 'V':
+                            symbol.aprs_symbol = '>';
+                            break;
+                        case 'W':
+                            symbol.aprs_symbol = '?';
+                            break;
+                        case 'X':
+                            symbol.aprs_symbol = '@';
+                            break;
+                    }
+                    break;
+
+                case 'Q':
+                    symbol.aprs_type = '\\';
+
+                case 'J':
+                    switch (hold[1]) {
+                        case '1':
+                            symbol.aprs_symbol = '{';
+                            break;
+                        case '2':
+                            symbol.aprs_symbol = '|';
+                            break;
+                        case '3':
+                            symbol.aprs_symbol = '}';
+                            break;
+                        case '4':
+                            symbol.aprs_symbol = '~';
+                            break;
+                    }
+                    break;
+
+                case 'S':
+                    symbol.aprs_type = '\\';
+
+                case 'L':
+                    if ('A' <= hold[1] && hold[1] <= 'Z')
+                        symbol.aprs_symbol = tolower((int)hold[1]);
+
+                    break;
+            }
+            if (hold[2]) {
+                if (hold[2] >= 'a' && hold[2] <= 'j') {
+                    // Compressed mode numeric overlay
+                    symbol.special_overlay = hold[2] - 'a';
+                }
+                else if ( (hold[2] >= '0' && hold[2] <= '9')
+                        || (hold[2] >= 'A' && hold[2] <= 'Z') ) {
+                    // Normal overlay character
+                    symbol.special_overlay = hold[2];
+                }
+                else {
+                    // Bad overlay character found
+                    symbol.special_overlay = '\0';
+                }
+            }
+            else {
+                // No overlay character found
+                symbol.special_overlay = '\0';
+            }
+        }
+    }
+    return(&symbol);
+}
+
+
+
+/*
+ * See if position is defined
+ * 90°N 180°W (0,0 in internal coordinates) is our undefined position
+ * 0N/0E is excluded from trails, could be excluded from map (#define ACCEPT_0N_0E)
+ */
+
+int position_defined(long lat, long lon, int strict) {
+
+    if (lat == 0l && lon == 0l)
+        return(0);              // undefined location
+#ifndef ACCEPT_0N_0E
+    if (strict)
+#endif  // ACCEPT_0N_0E
+        if (lat == 90*60*60*100l && lon == 180*60*60*100l)      // 0N/0E
+            return(0);          // undefined location
+    return(1);
+}
+
+
+//
+//  Extract data for $GPRMC, it fails if there is no position!!
+//
+// GPRMC,UTC-Time,status(A/V),lat,N/S,lon,E/W,SOG,COG,UTC-Date,Mag-Var,E/W,Fix-Quality[*CHK]
+// GPRMC,hhmmss[.sss],{A|V},ddmm.mm[mm],{N|S},dddmm.mm[mm],{E|W},[dd]d.d[ddddd],[dd]d.d[d],ddmmyy,[ddd.d],[{E|W}][,A|D|E|N|S][*CHK]
+//
+// The last field before the checksum is entirely optional, and in
+// fact first appeared in NMEA 2.3 (fairly recently).  Most GPS's do
+// not currently put out that field.  The field may be null or
+// nonexistent including the comma.  Only "A" or "D" are considered
+// to be active and reliable fixes if this field is present.
+// Fix-Quality:
+//  A: Autonomous
+//  D: Differential
+//  E: Estimated
+//  N: Not Valid
+//  S: Simulator
+//
+// $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
+// $GPRMC,104748.821,A,4301.1492,N,08803.0374,W,0.085048,102.36,010605,,*1A
+// $GPRMC,104749.821,A,4301.1492,N,08803.0377,W,0.054215,74.60,010605,,*2D
+//
+int extract_RMC(AprsDataRow *p_station, char *data, char *call_sign, char *path, int *num_digits) {
+    char temp_data[40]; // short term string storage, MAX_CALLSIGN, ...  ???
+    char lat_s[20];
+    char long_s[20];
+    int ok;
+    char *Substring[12];  // Pointers to substrings parsed by split_string()
+    char temp_string[MAX_MESSAGE_LENGTH+1];
+    char temp_char;
+
+
+    // should we copy it before processing? it changes data: ',' gets substituted by '\0' !!
+    ok = 0; // Start out as invalid.  If we get enough info, we change this to a 1.
+
+    if ( (data == NULL) || (strlen(data) < 34) ) {  // Not enough data to parse position from.
+        return(ok);
+    }
+
+    p_station->record_type = NORMAL_GPS_RMC;
+    // Create a timestamp from the current time
+    // get_time saves the time in temp_data
+    xastir_snprintf(p_station->pos_time,
+        sizeof(p_station->pos_time),
+        "%s",
+        get_time(temp_data));
+    p_station->flag &= (~ST_MSGCAP);    // clear "message capable" flag
+
+    /* check aprs type on call sign */
+    p_station->aprs_symbol = *id_callsign(call_sign, path);
+
+    // Make a copy of the incoming data.  The string passed to
+    // split_string() gets destroyed.
+    xastir_snprintf(temp_string,
+        sizeof(temp_string),
+        "%s",
+        data);
+    split_string(temp_string, Substring, 12);
+
+    // The Substring[] array contains pointers to each substring in
+    // the original data string.
+
+// GPRMC,034728,A,5101.016,N,11359.464,W,000.0,284.9,110701,018.0,E*7D
+//   0     1    2    3     4    5      6   7    8      9     10    11
+
+    if (Substring[0] == NULL)   // No GPRMC string
+        return(ok);
+
+    if (Substring[1] == NULL)   // No time string
+        return(ok);
+
+    if (Substring[2] == NULL)   // No valid fix char
+        return(ok);
+
+    if (Substring[2][0] != 'A' && Substring[2][0] != 'V')
+        return(ok);
+// V is a warning but we can get good data still ?
+// DK7IN: got no position with 'V' !
+
+    if (Substring[3] == NULL)   // No latitude string
+        return(ok);
+
+    if (Substring[4] == NULL)   // No latitude N/S
+        return(ok);
+
+// Need to check lat_s for validity here.  Note that some GPS's put out another digit of precision
+// (4801.1234) or leave one out (4801.12).  Next character after digits should be a ','
+
+    // Count digits after the decimal point for latitude
+    if (strchr(Substring[3],'.')) {
+        *num_digits = strlen(Substring[3]) - (int)(strchr(Substring[3],'.') - Substring[3]) - 1;
+    }
+    else {
+        *num_digits = 0;
+    }
+
+    temp_char = toupper((int)Substring[4][0]);
+
+    if (temp_char != 'N' && temp_char != 'S')   // Bad N/S
+        return(ok);
+
+    xastir_snprintf(lat_s,
+        sizeof(lat_s),
+        "%s%c",
+        Substring[3],
+        temp_char);
+
+    if (Substring[5] == NULL)   // No longitude string
+        return(ok);
+
+    if (Substring[6] == NULL)   // No longitude E/W
+        return(ok);
+
+// Need to check long_s for validity here.  Should be all digits.  Note that some GPS's put out another
+// digit of precision.  (12201.1234).  Next character after digits should be a ','
+
+    temp_char = toupper((int)Substring[6][0]);
+
+    if (temp_char != 'E' && temp_char != 'W')   // Bad E/W
+        return(ok);
+
+    xastir_snprintf(long_s,
+        sizeof(long_s),
+        "%s%c",
+        Substring[5],
+        temp_char);
+
+    p_station->coord_lat = convert_lat_s2l(lat_s);
+    p_station->coord_lon = convert_lon_s2l(long_s);
+
+    // If we've made it this far, We have enough for a position now!
+    ok = 1;
+
+    // Now that we have a basic position, let's see what other data
+    // can be parsed from the packet.  The rest of it can still be
+    // corrupt, so we're proceeding carefully under yellow alert on
+    // impulse engines only.
+
+// GPRMC,034728,A,5101.016,N,11359.464,W,000.0,284.9,110701,018.0,E*7D
+//   0     1    2    3     4    5      6   7    8      9     10    11
+
+    if (Substring[7] == NULL) { // No speed string
+        p_station->speed[0] = '\0'; // No speed available
+        return(ok);
+    }
+    else {
+        xastir_snprintf(p_station->speed,
+            MAX_SPEED,
+            "%s",
+            Substring[7]);
+        // Is it always knots, otherwise we need a conversion!
+    }
+
+    if (Substring[8] == NULL) { // No course string
+        xastir_snprintf(p_station->course,
+            sizeof(p_station->course),
+            "000.0");  // No course available
+        return(ok);
+    }
+    else {
+        xastir_snprintf(p_station->course,
+            MAX_COURSE,
+            "%s",
+            Substring[8]);
+    }
+
+    return(ok);
+}
+
+
+//
+//  Extract data for $GPGLL
+//
+// $GPGLL,4748.811,N,12219.564,W,033850,A*3C
+// lat, long, UTCtime in hhmmss, A=Valid, checksum
+//
+// GPGLL,4748.811,N,12219.564,W,033850,A*3C
+//   0       1    2      3    4    5   6
+//
+int extract_GLL(AprsDataRow *p_station,char *data,char *call_sign, char *path, int *num_digits) {
+    char temp_data[40]; // short term string storage, MAX_CALLSIGN, ...  ???
+    char lat_s[20];
+    char long_s[20];
+    int ok;
+    char *Substring[7];  // Pointers to substrings parsed by split_string()
+    char temp_string[MAX_MESSAGE_LENGTH+1];
+    char temp_char;
+
+
+    ok = 0; // Start out as invalid.  If we get enough info, we change this to a 1.
+  
+    if ( (data == NULL) || (strlen(data) < 28) )  // Not enough data to parse position from.
+        return(ok);
+
+    p_station->record_type = NORMAL_GPS_GLL;
+    // Create a timestamp from the current time
+    // get_time saves the time in temp_data
+    xastir_snprintf(p_station->pos_time,
+        sizeof(p_station->pos_time),
+        "%s",
+        get_time(temp_data));
+    p_station->flag &= (~ST_MSGCAP);    // clear "message capable" flag
+
+    /* check aprs type on call sign */
+    p_station->aprs_symbol = *id_callsign(call_sign, path);
+
+    // Make a copy of the incoming data.  The string passed to
+    // split_string() gets destroyed.
+    xastir_snprintf(temp_string,
+        sizeof(temp_string),
+        "%s",
+        data);
+    split_string(temp_string, Substring, 7);
+
+    // The Substring[] array contains pointers to each substring in
+    // the original data string.
+
+    if (Substring[0] == NULL)  // No GPGGA string
+        return(ok);
+
+    if (Substring[1] == NULL)  // No latitude string
+        return(ok);
+
+    if (Substring[2] == NULL)   // No N/S string
+        return(ok);
+
+    if (Substring[3] == NULL)   // No longitude string
+        return(ok);
+
+    if (Substring[4] == NULL)   // No E/W string
+        return(ok);
+
+    temp_char = toupper((int)Substring[2][0]);
+    if (temp_char != 'N' && temp_char != 'S')
+        return(ok);
+
+    xastir_snprintf(lat_s,
+        sizeof(lat_s),
+        "%s%c",
+        Substring[1],
+        temp_char);
+// Need to check lat_s for validity here.  Note that some GPS's put out another digit of precision
+// (4801.1234).  Next character after digits should be a ','
+
+    // Count digits after the decimal point for latitude
+    if (strchr(Substring[1],'.')) {
+        *num_digits = strlen(Substring[1]) - (int)(strchr(Substring[1],'.') - Substring[1]) - 1;
+    }
+    else {
+        *num_digits = 0;
+    }
+
+    temp_char = toupper((int)Substring[4][0]);
+    if (temp_char != 'E' && temp_char != 'W')
+        return(ok);
+
+    xastir_snprintf(long_s,
+        sizeof(long_s),
+        "%s%c",
+        Substring[3],
+        temp_char);
+// Need to check long_s for validity here.  Should be all digits.  Note that some GPS's put out another
+// digit of precision.  (12201.1234).  Next character after digits should be a ','
+
+    p_station->coord_lat = convert_lat_s2l(lat_s);
+    p_station->coord_lon = convert_lon_s2l(long_s);
+    ok = 1; // We have enough for a position now
+
+    xastir_snprintf(p_station->course,
+        sizeof(p_station->course),
+        "000.0");  // Fill in with dummy values
+    p_station->speed[0] = '\0';        // Fill in with dummy values
+
+    // A is valid, V is a warning but we can get good data still?
+    // We don't currently check the data valid flag.
+
+    return(ok);
+}
+
+
+
+//
+//  Extract data for $GPGGA
+//
+// GPGGA,UTC-Time,lat,N/S,long,E/W,GPS-Quality,nsat,HDOP,MSL-Meters,M,Geoidal-Meters,M,DGPS-Data-Age(seconds),DGPS-Ref-Station-ID[*CHK]
+// GPGGA,hhmmss[.sss],ddmm.mm[mm],{N|S},dddmm.mm[mm],{E|W},{0-8},dd,[d]d.d,[-dddd]d.d,M,[-ddd]d.d,M,[dddd.d],[dddd][*CHK]
+//
+// GPS-Quality:
+//  0: Invalid Fix
+//  1: GPS Fix
+//  2: DGPS Fix
+//  3: PPS Fix
+//  4: RTK Fix
+//  5: Float RTK Fix
+//  6: Estimated (dead-reckoning) Fix
+//  7: Manual Input Mode
+//  8: Simulation Mode
+//
+// $GPGGA,170834,4124.8963,N,08151.6838,W,1,05,1.5,280.2,M,-34.0,M,,,*75 
+// $GPGGA,104438.833,4301.1439,N,08803.0338,W,1,05,1.8,185.8,M,-34.2,M,0.0,0000*40
+//
+// nsat=Number of Satellites being tracked
+//
+//
+int extract_GGA(AprsDataRow *p_station,char *data,char *call_sign, char *path, int *num_digits) {
+    char temp_data[40]; // short term string storage, MAX_CALLSIGN, ...  ???
+    char lat_s[20];
+    char long_s[20];
+    int  ok;
+    char *Substring[15];  // Pointers to substrings parsed by split_string()
+    char temp_string[MAX_MESSAGE_LENGTH+1];
+    char temp_char;
+    int  temp_num;
+
+
+    ok = 0; // Start out as invalid.  If we get enough info, we change this to a 1.
+    if ( (data == NULL) || (strlen(data) < 32) )  // Not enough data to parse position from.
+        return(ok);
+
+    p_station->record_type = NORMAL_GPS_GGA;
+    // Create a timestamp from the current time
+    // get_time saves the time in temp_data
+    xastir_snprintf(p_station->pos_time,
+        sizeof(p_station->pos_time),
+        "%s",
+        get_time(temp_data));
+    p_station->flag &= (~ST_MSGCAP);    // clear "message capable" flag
+
+    /* check aprs type on call sign */
+    p_station->aprs_symbol = *id_callsign(call_sign, path);
+
+    // Make a copy of the incoming data.  The string passed to
+    // split_string() gets destroyed.
+    xastir_snprintf(temp_string,
+        sizeof(temp_string),
+        "%s",
+        data);
+    split_string(temp_string, Substring, 15);
+
+    // The Substring[] array contains pointers to each substring in
+    // the original data string.
+
+
+// GPGGA,hhmmss[.sss],ddmm.mm[mm],{N|S},dddmm.mm[mm],{E|W},{0-8},dd,[d]d.d,[-dddd]d.d,M,[-ddd]d.d,M,[dddd.d],[dddd][*CHK]
+//   0     1              2         3        4         5        6      7     8        9     1     1     1    1        1
+//                                                                                          0     1     2    3        4
+
+    if (Substring[0] == NULL)  // No GPGGA string
+        return(ok);
+
+    if (Substring[1] == NULL)  // No time string
+        return(ok);
+
+    if (Substring[2] == NULL)   // No latitude string
+        return(ok);
+
+    if (Substring[3] == NULL)   // No latitude N/S
+        return(ok);
+
+// Need to check lat_s for validity here.  Note that some GPS's put out another digit of precision
+// (4801.1234).  Next character after digits should be a ','
+
+    // Count digits after the decimal point for latitude
+    if (strchr(Substring[2],'.')) {
+        *num_digits = strlen(Substring[2]) - (int)(strchr(Substring[2],'.') - Substring[2]) - 1;
+    }
+    else {
+        *num_digits = 0;
+    }
+
+    temp_char = toupper((int)Substring[3][0]);
+
+    if (temp_char != 'N' && temp_char != 'S')   // Bad N/S
+        return(ok);
+
+    xastir_snprintf(lat_s,
+        sizeof(lat_s),
+        "%s%c",
+        Substring[2],
+        temp_char);
+
+    if (Substring[4] == NULL)   // No longitude string
+        return(ok);
+
+    if (Substring[5] == NULL)   // No longitude E/W
+        return(ok);
+
+// Need to check long_s for validity here.  Should be all digits.  Note that some GPS's put out another
+// digit of precision.  (12201.1234).  Next character after digits should be a ','
+
+    temp_char = toupper((int)Substring[5][0]);
+
+    if (temp_char != 'E' && temp_char != 'W')   // Bad E/W
+        return(ok);
+
+    xastir_snprintf(long_s,
+        sizeof(long_s),
+        "%s%c",
+        Substring[4],
+        temp_char);
+
+    p_station->coord_lat = convert_lat_s2l(lat_s);
+    p_station->coord_lon = convert_lon_s2l(long_s);
+
+    // If we've made it this far, We have enough for a position now!
+    ok = 1;
+
+
+    // Now that we have a basic position, let's see what other data
+    // can be parsed from the packet.  The rest of it can still be
+    // corrupt, so we're proceeding carefully under yellow alert on
+    // impulse engines only.
+
+    // Check for valid fix {
+    if (Substring[6] == NULL
+            || Substring[6][0] == '0'      // Fix quality
+            || Substring[7] == NULL        // Sat number
+            || Substring[8] == NULL        // hdop
+            || Substring[9] == NULL) {     // Altitude in meters
+        p_station->sats_visible[0] = '\0'; // Store empty sats visible
+        p_station->altitude[0] = '\0';;    // Store empty altitude
+        return(ok); // A field between fix quality and altitude is missing
+    }
+
+// Need to check for validity of this number.  Should be 0-12?  Perhaps a few more with WAAS, GLONASS, etc?
+    temp_num = atoi(Substring[7]);
+    if (temp_num < 0 || temp_num > 30) {
+        return(ok); // Number of satellites not valid
+    }
+    else {
+        // Store 
+        xastir_snprintf(p_station->sats_visible,
+            sizeof(p_station->sats_visible),
+            "%d",
+            temp_num);
+    }
+
+
+// Check for valid number for HDOP instead of just throwing it away?
+
+
+    xastir_snprintf(p_station->altitude,
+        sizeof(p_station->altitude),
+        "%s",
+        Substring[9]); // Get altitude
+
+// Need to check for valid altitude before conversion
+
+    // unit is in meters, if not adjust value ???
+
+    if (Substring[10] == NULL)  // No units for altitude
+        return(ok);
+
+    if (Substring[10][0] != 'M') {
+        //fprintf(stderr,"ERROR: should adjust altitude for meters\n");
+        //} else {  // Altitude units wrong.  Assume altitude bad
+        p_station->altitude[0] = '\0';
+    }
+
+    return(ok);
+}
+
+
+
+static void extract_multipoints(AprsDataRow *p_station,
+        char *data,
+        int type,
+        int remove_string) {
+    // If they're in there, the multipoints start with the
+    // sequence <space><rbrace><lower><digit> and end with a <lbrace>.
+    // In addition, there must be no spaces in there, and there
+    // must be an even number of characters (after the lead-in).
+
+    char *p, *p2;
+    int found = 0;
+    char *end;
+    int data_size;
+
+
+    if (data == NULL) {
+        return;
+    }
+
+//fprintf(stderr,"Data: %s\t\t", data);
+
+    data_size = strlen(data);
+
+    end = data + (strlen(data) - 7);  // 7 == 3 lead-in chars, plus 2 points
+    p_station->num_multipoints = 0;
+
+    /*
+    for (p = data; !found && p <= end; ++p) {
+        if (*p == ' ' && *(p+1) == RBRACE && islower((int)*(p+2)) && isdigit((int)*(p+3)) && 
+                            (p2 = strchr(p+4, LBRACE)) != NULL && ((p2 - p) % 2) == 1) {
+            found = 1;
+        }
+    }
+    */
+
+    // Start looking at the beginning of the data.
+
+    p = data;
+
+    // Look for the opening string.
+
+    while (!found && p < end && (p = strstr(p, START_STR)) != NULL) {
+        // The opening string was found. Check the following information.
+
+        if (islower((int)*(p+2)) && g_ascii_isdigit(*(p+3)) && (p2 = strchr(p+4, LBRACE)) != NULL && ((p2 - p) % 2) == 1) {
+            // It all looks good!
+
+            found = 1;
+        }
+        else {
+            // The following characters are not right. Advance and
+            // look again.
+
+            ++p;
+        }
+    }
+
+    if (found) {
+        long multiplier;
+        double d;
+        char *m_start = p;    // Start of multipoint string
+        char ok = 1;
+        // The second character (the lowercase) indicates additional style information,
+        // such as color, line type, etc.
+
+        p_station->style = *(p+2);
+
+        // The third character (the digit) indicates the way the points should be
+        // used. They may be used to draw a closed polygon, a series of line segments,
+        // etc.
+
+        p_station->type = *(p+3);
+
+        // The fourth character indicates the scale of the coordinates that
+        // follow. It may range from '!' to 'z'. The value represents the
+        // unit of measure (1, 0.1, 0.001, etc., in degrees) used in the offsets.
+        //
+        // Use the following formula to convert the char to the value:
+        // (10 ^ ((c - 33) / 20)) / 10000 degrees
+        //
+        // Finally we have to convert to Xastir units. Xastir stores coordinates
+        // as hudredths of seconds. There are 360,000 of those per degree, so we
+        // need to multiply by that factor so our numbers will be converted to
+        // Xastir units.
+
+        p = p + 4;
+
+        if (*p < '!' || *p > 'z') {
+            fprintf(stderr,"extract_multipoints: invalid scale character %d\n", *p);
+            ok = 0; // Failure
+        }
+        else {
+
+            d = (double)(*p);
+            d = pow(10.0, ((d - 33) / 20)) / 10000.0 * 360000.0;
+            multiplier = (long)d;
+            ++p;
+
+            // The remaining characters are in pairs. Each pair is the
+            // offset lat and lon for one of the points. (The offset is
+            // from the actual location of the object.) Convert each
+            // character to its numeric value and save it.
+
+            while (*p != LBRACE && p_station->num_multipoints < MAX_MULTIPOINTS) {
+                // The characters are in the range '"' (34 decimal) to 'z' (122). They
+                // encode values in the range -44 to +44. To convert to the correct
+                // value 78 is subtracted from the character's value.
+
+                int lat_val = *p - 78;
+                int lon_val = *(p+1) - 78;
+
+                // Check for correct values.
+
+                if (lon_val < -44 || lon_val > 44 || lat_val < -44 || lat_val > 44) {
+                    char temp[MAX_LINE_SIZE+1];
+                    int i;
+
+                    // Filter the string so we don't send strange
+                    // chars to the xterm
+                    for (i = 0; i < (int)strlen(data); i++) {
+                        temp[i] = data[i] & 0x7f;
+                        if ( (temp[i] < 0x20) || (temp[i] > 0x7e) )
+                            temp[i] = ' ';
+                    }
+                    temp[strlen(data)] = '\0';
+                    
+                    fprintf(stderr,"extract_multipoints: invalid value in (filtered) \"%s\": %d,%d\n",
+                        temp,
+                        lat_val,
+                        lon_val);
+
+                    p_station->num_multipoints = 0;     // forget any points we already set
+                    ok = 0; // Failure to decode
+                    break;
+                }
+
+                // Malloc the storage area for this if we don't have
+                // it yet.
+                if (p_station->multipoint_data == NULL) {
+
+                    p_station->multipoint_data = malloc(sizeof(AprsMultipointRow));
+                    if (p_station->multipoint_data == NULL) {
+                        p_station->num_multipoints = 0;
+                        fprintf(stderr,"Couldn't malloc AprsMultipointRow'\n");
+                        return;
+                    }
+                }
+                // Add the offset to the object's position to obtain the position of the point.
+                // Note that we're working in Xastir coordinates, and in North America they
+                // are exactly opposite to lat/lon (larger numbers are farther east and south).
+                // An offset with a positive value means that the point should be north and/or
+                // west of the object, so we have to *subtract* the offset to get the correct
+                // placement in Xastir coordinates.
+                // TODO: Consider what we should do in the other geographic quadrants. Should we
+                // check here for the correct sign of the offset? Or should the program that
+                // creates the offsets take that into account?
+
+                p_station->multipoint_data->multipoints[p_station->num_multipoints][0]
+                    = p_station->coord_lon - (lon_val * multiplier);
+                p_station->multipoint_data->multipoints[p_station->num_multipoints][1]
+                    = p_station->coord_lat - (lat_val * multiplier);
+
+                p += 2;
+                ++p_station->num_multipoints;
+            }   // End of while loop
+        }
+
+        if (ok && remove_string) {
+            // We've successfully decoded a multipoint object?
+            // Remove the multipoint strings (and the sequence
+            // number at the end if present) from the data string.
+            // m_start points to the first character (a space).  'p'
+            // should be pointing at the LBRACE character.
+
+            // Make 'p' point to just after the end of the chars
+            while ( (p < data+strlen(data)) && (*p != ' ') ) {
+               p++;
+            }
+            // The string that 'p' points to now may be empty
+
+            // Truncate "data" at the starting brace - 1
+            *m_start = '\0';
+
+            // Now we have two strings inside "data".  Copy the 2nd
+            // string directly onto the end of the first.
+            strncat(data, p, data_size+1);
+
+            // The multipoint string and sequence number should be
+            // erased now from "data".
+//fprintf(stderr,"New Data: %s\n", data);
+        }
+
+    }
+
+}
+
+
+
+
+// Returns time in seconds since the Unix epoch.
+//
+time_t sec_now(void) {
+    time_t timenw;
+    time_t ret;
+
+    ret = time(&timenw);
+    return(ret);
+}
+
+
+int is_my_station(AprsDataRow *p_station) {
+    // if station is owned by me (including SSID)
+    return(p_station->flag & ST_MYSTATION);
+}
+
+int is_my_object_item(AprsDataRow *p_station) {
+    // If object/item is owned by me (including SSID)
+    return(p_station->flag & ST_MYOBJITEM);
+}
+
+/*
+ *  Display text in the status line, text is removed after timeout
+ */
+void statusline(char *status_text,int update) {
+
+// Maybe useful
+/*
+    XmTextFieldSetString (text, status_text);
+    last_statusline = sec_now();    // Used for auto-ID timeout
+*/
+}
+
+
+/*
+ *  Extract text inserted by TNC X-1J4 from start of info line
+ */
+void extract_TNC_text(char *info) {
+    int i,j,len;
+
+    if (strncasecmp(info,"thenet ",7) == 0) {   // 1st match
+        len = strlen(info)-1;
+        for (i=7;i<len;i++) {
+            if (info[i] == ')')
+                break;
+        }
+        len++;
+        if (i>7 && info[i] == ')' && info[i+1] == ' ') {        // found
+            i += 2;
+            for (j=0;i<=len;i++,j++) {
+                info[j] = info[i];
+            }
+        }
+    }
+}
+
+
+
+/*
+ *  Check for valid path and change it to TAPR format
+ *  Add missing asterisk for stations that we must have heard via a digi
+ *  Extract port for KAM TNCs
+ *  Handle igate injection ID formats: "callsign-ssid,I" & "callsign-0ssid"
+ *
+ * TAPR-2 Format:
+ * KC2ELS-1*>SX0PWT,RELAY,WIDE:`2`$l##>/>"4)}
+ *
+ * AEA Format:
+ * KC2ELS-1*>RELAY>WIDE>SX0PWT:`2`$l##>/>"4)}
+ */
+
+int valid_path(char *path) {
+    int i,len,hops,j;
+    int type,ast,allast,ins;
+    char ch;
+
+
+    len  = (int)strlen(path);
+    type = 0;       // 0: unknown, 1: AEA '>', 2: TAPR2 ',', 3: mixed
+    hops = 1;
+    ast  = 0;
+    allast = 0;
+
+    // There are some multi-port TNCs that deliver the port at the end
+    // of the path. For now we discard this information. If there is
+    // multi-port TNC support some day, we should write the port into
+    // our database.
+    // KAM:        /V /H
+    // KPC-9612:   /0 /1 /2
+    if (len > 2 && path[len-2] == '/') {
+        ch = path[len-1];
+        if (ch == 'V' || ch == 'H' || ch == '0' || ch == '1' || ch == '2') {
+            path[len-2] = '\0';
+            len  = (int)strlen(path);
+        }
+    }
+
+
+    // One way of adding igate injection ID is to add "callsign-ssid,I".
+    // We need to remove the ",I" portion so it doesn't count as another
+    // digi here.  This should be at the end of the path.
+    if (len > 2 && path[len-2] == ',' && path[len-1] == 'I') {  // Found ",I"
+        //fprintf(stderr,"%s\n",path);
+        //fprintf(stderr,"Found ',I'\n");
+        path[len-2] = '\0';
+        len  = (int)strlen(path);
+        //fprintf(stderr,"%s\n\n",path);
+    }
+    // Now look for the same thing but with a '*' character at the end.
+    // This should be at the end of the path.
+    if (len > 3 && path[len-3] == ',' && path[len-2] == 'I' && path[len-1] == '*') {  // Found ",I*"
+        //fprintf(stderr,"%s\n",path);
+        //fprintf(stderr,"Found ',I*'\n");
+        path[len-3] = '\0';
+        len  = (int)strlen(path);
+        //fprintf(stderr,"%s\n\n",path);
+    }
+
+
+    // Another method of adding igate injection ID is to add a '0' in front of
+    // the SSID.  For WE7U it would change to WE7U-00, for WE7U-15 it would
+    // change to WE7U-015.  Take out this zero so the rest of the decoding will
+    // work.  This should be at the end of the path.
+    // Also look for the same thing but with a '*' character at the end.
+    if (len > 6) {
+        for (i=len-1; i>len-6; i--) {
+            if (path[i] == '-' && path[i+1] == '0') {
+                //fprintf(stderr,"%s\n",path);
+                for (j=i+1; j<len; j++) {
+                    path[j] = path[j+1];    // Shift everything left by one
+                }
+                len = (int)strlen(path);
+                //fprintf(stderr,"%s\n\n",path);
+            }
+            // Check whether we just chopped off the '0' from "-0".
+            // If so, chop off the dash as well.
+            if (path[i] == '-' && path[i+1] == '\0') {
+                //fprintf(stderr,"%s\tChopping off dash\n",path);
+                path[i] = '\0';
+                len = (int)strlen(path);
+                //fprintf(stderr,"%s\n",path);
+            }
+            // Check for "-*", change to '*' only
+            if (path[i] == '-' && path[i+1] == '*') {
+                //fprintf(stderr,"%s\tChopping off dash\n",path);
+                path[i] = '*';
+                path[i+1] = '\0';
+                len = (int)strlen(path);
+                //fprintf(stderr,"%s\n",path);
+            }
+            // Check for "-0" or "-0*".  Change to "" or "*".
+            if ( path[i] == '-' && path[i+1] == '0' ) {
+                //fprintf(stderr,"%s\tShifting left by two\n",path);
+                for (j=i; j<len; j++) {
+                    path[j] = path[j+2];    // Shift everything left by two
+                }
+                len = (int)strlen(path);
+                //fprintf(stderr,"%s\n",path);
+            }
+        }
+    }
+
+
+    for (i=0,j=0; i<len; i++) {
+        ch = path[i];
+
+        if (ch == '>' || ch == ',') {   // found digi call separator
+            // We're at the start of a callsign entry in the path
+
+            if (ast > 1 || (ast == 1 && i-j > 10) || (ast == 0 && (i == j || i-j > 9))) {
+                return(0);              // more than one asterisk in call or wrong call size
+            }
+            ast = 0;                    // reset local asterisk counter
+            
+            j = i+1;                    // set to start of next call
+            if (ch == ',')
+                type |= 0x02;           // set TAPR2 flag
+            else
+                type |= 0x01;           // set AEA flag (found '>')
+            hops++;                     // count hops
+        }
+
+        else {                          // digi call character or asterisk
+            // We're in the middle of a callsign entry
+
+            if (ch == '*') {
+                ast++;                  // count asterisks in call
+                allast++;               // count asterisks in path
+            }
+            else if ((ch <'A' || ch > 'Z')      // Not A-Z
+                    && (ch <'a' || ch > 'z')    // Not a-z
+                    && (ch <'0' || ch > '9')    // Not 0-9
+                    && ch != '-') {
+                // Note that Q-construct and internet callsigns can
+                // have a-z in them, AX.25 callsigns cannot unless
+                // they are in a 3rd-party packet.
+
+                return(0);          // wrong character in path
+            }
+        }
+    }
+    if (ast > 1 || (ast == 1 && i-j > 10) || (ast == 0 && (i == j || i-j > 9))) {
+        return(0);                      // more than one asterisk or wrong call size
+    }
+
+    if (type == 0x03) {
+        return(0);                      // wrong format, both '>' and ',' in path
+    }
+
+    if (hops > 9) {                     // [APRS Reference chapter 3]
+        return(0);                      // too much hops, destination + 0-8 digipeater addresses
+    }
+
+    if (type == 0x01) {
+        int delimiters[20];
+        int k = 0;
+        char dest[15];
+        char rest[100];
+
+        for (i=0; i<len; i++) {
+            if (path[i] == '>') {
+                path[i] = ',';          // Exchange separator character
+                delimiters[k++] = i;    // Save the delimiter indexes
+            }
+        }
+
+        // We also need to move the destination callsign to the end.
+        // AEA has them in a different order than TAPR-2 format.
+        // We'll move the destination address between delimiters[0]
+        // and [1] to the end of the string.
+
+        //fprintf(stderr,"Orig. Path:%s\n",path);
+        // Save the destination
+        xastir_snprintf(dest,sizeof(dest),"%s",&path[delimiters[--k]+1]);
+        dest[strlen(path) - delimiters[k] - 1] = '\0'; // Terminate it
+        dest[14] = '\0';    // Just to make sure
+        path[delimiters[k]] = '\0'; // Delete it from the original path
+        //fprintf(stderr,"Destination: %s\n",dest);
+
+        // TAPR-2 Format:
+        // KC2ELS-1*>SX0PWT,RELAY,WIDE:`2`$l##>/>"4)}
+        //
+        // AEA Format:
+        // KC2ELS-1*>RELAY>WIDE>SX0PWT:`2`$l##>/>"4)}
+        //          9     15   20
+
+        // We now need to insert the destination into the middle of
+        // the string.  Save part of it in another variable first.
+        xastir_snprintf(rest,
+            sizeof(rest),
+            "%s",
+            path);
+        //fprintf(stderr,"Rest:%s\n",rest);
+        xastir_snprintf(path,len+1,"%s,%s",dest,rest);
+        //fprintf(stderr,"New Path:%s\n",path);
+    }
+
+    if (allast < 1) {                   // try to insert a missing asterisk
+        ins  = 0;
+        hops = 0;
+
+        for (i=0; i<len; i++) {
+
+            for (j=i; j<len; j++) {             // search for separator
+                if (path[j] == ',')
+                    break;
+            }
+
+            if (hops > 0 && (j - i) == 5) {     // WIDE3
+                if (  path[ i ] == 'W' && path[i+1] == 'I' && path[i+2] == 'D' 
+                   && path[i+3] == 'E' && path[i+4] >= '0' && path[i+4] <= '9') {
+                    ins = j;
+                }
+            }
+
+/*
+Don't do this!  It can mess up relay/wide1-1 digipeating by adding
+an asterisk later in the path than the first unused digi.
+            if (hops > 0 && (j - i) == 7) {     // WIDE3-2
+                if (  path[ i ] == 'W' && path[i+1] == 'I' && path[i+2] == 'D' 
+                   && path[i+3] == 'E' && path[i+4] >= '0' && path[i+4] <= '9'
+                   && path[i+5] == '-' && path[i+6] >= '0' && path[i+6] <= '9'
+                   && (path[i+4] != path[i+6]) ) {
+                    ins = j;
+                }
+            }
+*/
+
+            if (hops > 0 && (j - i) == 6) {     // TRACE3
+                if (  path[ i ] == 'T' && path[i+1] == 'R' && path[i+2] == 'A' 
+                   && path[i+3] == 'C' && path[i+4] == 'E'
+                   && path[i+5] >= '0' && path[i+5] <= '9') {
+                    if (hops == 1)
+                        ins = j;
+                    else
+                        ins = i-1;
+                }
+            }
+
+/*
+Don't do this!  It can mess up relay/wide1-1 digipeating by adding
+an asterisk later in the path than the first unused digi.
+            if (hops > 0 && (j - i) == 8) {     // TRACE3-2
+                if (  path[ i ] == 'T' && path[i+1] == 'R' && path[i+2] == 'A' 
+                   && path[i+3] == 'C' && path[i+4] == 'E' && path[i+5] >= '0'
+                   && path[i+5] <= '9' && path[i+6] == '-' && path[i+7] >= '0'
+                   && path[i+7] <= '9' && (path[i+5] != path[i+7]) ) {
+                    if (hops == 1)
+                        ins = j;
+                    else
+                        ins = i-1;
+                }
+            }
+*/
+
+            hops++;
+            i = j;                      // skip to start of next call
+        }
+        if (ins > 0) {
+            for (i=len;i>=ins;i--) {
+                path[i+1] = path[i];    // generate space for '*'
+                // we work on a separate path copy which is long enough to do it
+            }
+            path[ins] = '*';            // and insert it
+        }
+    }
+    return(1);  // Path is good
+}
+
+
+
+
+char *remove_leading_spaces(char *data) {
+    int i,j;
+    int count;
+
+    if (data == NULL)
+        return NULL;
+
+    if (strlen(data) == 0)
+        return NULL;
+
+    count = 0;
+    // Count the leading space characters
+    for (i = 0; i < (int)strlen(data); i++) {
+        if (data[i] == ' ') {
+            count++;
+        }
+        else {  // Found a non-space
+            break;
+        }
+    }
+
+    // Check whether entire string was spaces
+    if (count == (int)strlen(data)) {
+        // Empty the string
+        data[0] = '\0';
+    }
+    else if (count > 0) {  // Found some spaces
+        i = 0;
+        for( j = count; j < (int)strlen(data); j++ ) {
+            data[i++] = data[j];    // Move string left
+        }
+        data[i] = '\0'; // Terminate the new string
+    }
+
+    return(data);
+}
+
+int is_num_chr(char ch) {
+    return((int)isdigit(ch));
+}
+
+char *remove_trailing_spaces(char *data) {
+    int i;
+
+    if (data == NULL)
+        return NULL;
+
+    if (strlen(data) == 0)
+        return NULL;
+
+    for(i=strlen(data)-1;i>=0;i--)
+        if(data[i] == ' ')
+            data[i] = '\0';
+        else
+            break;
+
+        return(data);
+}
+
+
+
+char *remove_trailing_asterisk(char *data) {
+    int i;
+
+    if (data == NULL)
+        return NULL;
+
+    if (strlen(data) == 0)
+        return NULL;
+
+// Should the test here be i>=0 ??
+    for(i=strlen(data)-1;i>0;i--) {
+        if(data[i] == '*')
+            data[i] = '\0';
+    }
+    return(data);
+}
+
+//--------------------------------------------------------------------
+//Removes all control codes ( <0x20 or >0x7e ) from a string, including
+// CR's, LF's, tab's, etc.
+//
+void makePrintable(char *cp) {
+    int i,j;
+    int len = (int)strlen(cp);
+    unsigned char *ucp = (unsigned char *)cp;
+
+    for (i=0, j=0; i<=len; i++) {
+        ucp[i] &= 0x7f;                 // Clear 8th bit
+        if ( ((ucp[i] >= (unsigned char)0x20) && (ucp[i] <= (unsigned char)0x7e))
+              || ((char)ucp[i] == '\0') )     // Check for printable or terminating 0
+            ucp[j++] = ucp[i] ;        // Copy to (possibly) new location if printable
+    }
+}
+
+
+
+
+/*
+ *  Check for a valid AX.25 call
+ *      Valid calls consist of up to 6 uppercase alphanumeric characters
+ *      plus optional SSID (four-bit integer)       [APRS Reference, AX.25 Reference]
+ */
+int valid_call(char *call) {
+    int len, ok;
+    int i, del, has_num, has_chr;
+    char c;
+
+    has_num = 0;
+    has_chr = 0;
+    ok      = 1;
+    len = (int)strlen(call);
+
+    if (len == 0)
+        return(0);                              // wrong size
+
+    while (call[0]=='c' && call[1]=='m' && call[2]=='d' && call[3]==':') {
+        // Erase TNC prompts from beginning of callsign.  This may
+        // not be the right place to do this, but it came in handy
+        // here, so that's where I put it. -- KB6MER
+
+        for(i=0; call[i+4]; i++)
+            call[i]=call[i+4];
+
+        call[i++]=0;
+        call[i++]=0;
+        call[i++]=0;
+        call[i++]=0;
+        len=strlen(call);
+
+    }
+
+    if (len > 9)
+        return(0);      // Too long for valid call (6-2 max e.g. KB6MER-12)
+
+    del = 0;
+    for (i=len-2;ok && i>0 && i>=len-3;i--) {   // search for optional SSID
+        if (call[i] =='-')
+            del = i;                            // found the delimiter
+    }
+    if (del) {                                  // we have a SSID, so check it
+        if (len-del == 2) {                     // 2 char SSID
+            if (call[del+1] < '1' || call[del+1] > '9')                         //  -1 ...  -9
+                del = 0;
+        }
+        else {                                  // 3 char SSID
+            if (call[del+1] != '1' || call[del+2] < '0' || call[del+2] > '5')   // -10 ... -15
+                del = 0;
+        }
+    }
+
+    if (del)
+        len = del;                              // length of base call
+
+    for (i=0;ok && i<len;i++) {                 // check for uppercase alphanumeric
+        c = call[i];
+
+        if (c >= 'A' && c <= 'Z')
+            has_chr = 1;                        // we need at least one char
+        else if (c >= '0' && c <= '9')
+            has_num = 1;                        // we need at least one number
+        else
+            ok = 0;                             // wrong character in call
+    }
+
+//    if (!has_num || !has_chr)                 // with this we also discard NOCALL etc.
+    if (!has_chr)                               
+        ok = 0;
+
+    ok = (ok && strcmp(call,"NOCALL") != 0);    // check for errors
+    ok = (ok && strcmp(call,"ERROR!") != 0);
+    ok = (ok && strcmp(call,"WIDE")   != 0);
+    ok = (ok && strcmp(call,"RELAY")  != 0);
+    ok = (ok && strcmp(call,"MAIL")   != 0);
+
+    return(ok);
+}
+
+
+/*
+ *  Check whether callsign is mine.  "exact == 1" checks the SSID
+ *  for a match as well.  "exact == 0" checks only the base
+ *  callsign.
+ */
+int is_my_call(char *call, int exact) {
+    char *p_del;
+    int ok;
+
+
+    // U.S. special-event callsigns can be as short as three
+    // characters, any less and we don't have a valid callsign.  We
+    // don't check for that restriction here though.
+
+    if (exact) {
+        // We're looking for an exact match
+        ok = (int)( !strcmp(call,_aprs_mycall ) );
+        //fprintf(stderr,"My exact call found: %s\n",call);
+    }
+    else {
+        // We're looking for a similar match.  Compare only up to
+        // the '-' in each (if present).
+        int len1,len2;
+
+        p_del = index(call,'-');
+        if (p_del == NULL)
+            len1 = (int)strlen(call);
+        else
+            len1 = p_del - call;
+
+        p_del = index(_aprs_mycall,'-');
+        if (p_del == NULL)
+            len2 = (int)strlen(_aprs_mycall);
+        else
+            len2 = p_del - _aprs_mycall;
+
+        ok = (int)(len1 == len2 && !strncmp(call,_aprs_mycall,(size_t)len1));
+        //fprintf(stderr,"My base call found: %s\n",call);
+    }
+    return(ok);
+}
+
+
+
+
+/*
+ *  Check for a valid internet name.
+ *  Accept darned-near anything here as long as it is the proper
+ *  length and printable.
+ */
+int valid_inet_name(char *name, char *info, char *origin, int origin_size) {
+    int len, i, ok;
+    char *ptr;
+    
+    len = (int)strlen(name);
+
+    if (len > 9 || len == 0)            // max 9 printable ASCII characters
+        return(0);                      // wrong size
+
+    for (i=0;i<len;i++)
+        if (!isprint((int)name[i]))
+            return(0);                  // not printable
+
+    // Modifies "origin" if a match found
+    //
+    if (len >= 5 && strncmp(name,"aprsd",5) == 0) {
+        snprintf(origin, origin_size, "INET");
+        origin[4] = '\0';   // Terminate it
+        return(1);                      // aprsdXXXX is ok
+    }
+
+    // Modifies "origin" if a match found
+    //
+    if (len == 6) {                     // check for NWS
+        ok = 1;
+        for (i=0;i<6;i++)
+            if (name[i] <'A' || name[i] > 'Z')  // 6 uppercase characters
+                ok = 0;
+        ok = ok && (info != NULL);      // check if we can test info
+        if (ok) {
+            ptr = strstr(info,":NWS-"); // "NWS-" in info field (non-compressed alert)
+            ok = (ptr != NULL);
+
+            if (!ok) {
+                ptr = strstr(info,":NWS_"); // "NWS_" in info field (compressed alert)
+                ok = (ptr != NULL);
+            }
+        }
+        if (ok) {
+            snprintf(origin, origin_size, "INET-NWS");
+            origin[8] = '\0';
+            return(1);                      // weather alerts
+        }
+    }
+
+    return(1);  // Accept anything else if we get to this point in
+                // the code.  After all, the message came from the
+                // internet, not from RF.
+}
+
+
+
+/*
+ *  Extract third-party traffic from information field before processing
+ */
+int extract_third_party(char *call,
+                        char *path,
+                        int path_size,
+                        char **info,
+                        char *origin,
+                        int origin_size) {
+    int ok;
+    char *p_call;
+    char *p_path;
+
+    p_call = NULL;                              // to make the compiler happy...
+    p_path = NULL;                              // to make the compiler happy...
+    ok = 0;
+    if (!is_my_call(call,1)) { // Check SSID also
+        // todo: add reporting station call to database ??
+        //       but only if not identical to reported call
+        (*info) = (*info) +1;                   // strip '}' character
+        p_call = strtok((*info),">");           // extract call
+        if (p_call != NULL) {
+            p_path = strtok(NULL,":");          // extract path
+            if (p_path != NULL) {
+                (*info) = strtok(NULL,"");      // rest is information field
+                if ((*info) != NULL)            // the above looks dangerous, but works on same string
+                    if (strlen(p_path) < 100)
+                        ok = 1;                 // we have found all three components
+            }
+        }
+    }
+
+    if (ok) {
+
+        snprintf(path,
+            path_size,
+            "%s",
+            p_path);
+
+        ok = valid_path(path);                  // check the path and convert it to TAPR format
+        // Note that valid_path() also removes igate injection identifiers
+
+    }
+
+    if (ok) {                                         // check callsign
+        (void)remove_trailing_asterisk(p_call);       // is an asterisk valid here ???
+        if (valid_inet_name(p_call,(*info),origin,origin_size)) { // accept some of the names used in internet
+            // Treat it as object with special origin
+            snprintf(call,
+                MAX_CALLSIGN+1,
+                "%s",
+                p_call);
+        }
+        else if (valid_call(p_call)) {              // accept real AX.25 calls
+            snprintf(call,
+                MAX_CALLSIGN+1,
+                "%s",
+                p_call);
+        }
+        else {
+            ok = 0;
+        }
+    }
+    return(ok);
+}
+
+
+
+// DK7IN 99
+/*
+ *  Extract Compressed Position Report Data Formats from begin of line
+ *    [APRS Reference, chapter 9]
+ *
+ * If a position is found, it is deleted from the data.  If a
+ * compressed position is found, delete the three csT bytes as well,
+ * even if all spaces.
+ * Returns 0 if the packet is NOT a properly compressed position
+ * packet, returns 1 if ok.
+ */
+int extract_comp_position(AprsDataRow *p_station, char **info, /*@unused@*/ int type) {
+    int ok;
+    int x1, x2, x3, x4, y1, y2, y3, y4;
+    int c = 0;
+    int s = 0;
+    int T = 0;
+    int len;
+    char *my_data;
+    float lon = 0;
+    float lat = 0;
+    float range;
+    int skip = 0;
+    char L;
+
+    my_data = (*info);
+
+    // Check leading char.  Must be one of these:
+    // '/'
+    // '\'
+    // A-Z
+    // a-j
+    //
+    L = my_data[0];
+    if (   L == '/'
+        || L == '\\'
+        || ( L >= 'A' && L <= 'Z' )
+        || ( L >= 'a' && L <= 'j' ) ) {
+        // We're good so far
+    }
+    else {
+        // Note one of the symbol table or overlay characters, so
+        // there's something funky about this packet.  It's not a
+        // properly formatted compressed position.
+        return(0);
+    }
+
+    //fprintf(stderr,"my_data: %s\n",my_data);
+
+    // If c = space, csT bytes are ignored.  Minimum length:  8
+    // bytes for lat/lon, 2 for symbol, 3 for csT for a total of 13.
+    len = strlen(my_data);
+    ok = (int)(len >= 13);
+
+    if (ok) {
+        y1 = (int)my_data[1] - '!';
+        y2 = (int)my_data[2] - '!';
+        y3 = (int)my_data[3] - '!';
+        y4 = (int)my_data[4] - '!';
+        x1 = (int)my_data[5] - '!';
+        x2 = (int)my_data[6] - '!';
+        x3 = (int)my_data[7] - '!';
+        x4 = (int)my_data[8] - '!';
+
+        // csT bytes
+        if (my_data[10] == ' ') // Space
+            c = -1; // This causes us to ignore csT
+        else {
+            c = (int)my_data[10] - '!';
+            s = (int)my_data[11] - '!';
+            T = (int)my_data[12] - '!';
+        }
+        skip = 13;
+
+        // Convert ' ' to '0'.  Not specified in APRS Reference!  Do
+        // we need it?
+        if (x1 == -1) x1 = '\0';
+        if (x2 == -1) x2 = '\0';
+        if (x3 == -1) x3 = '\0';
+        if (x4 == -1) x4 = '\0';
+        if (y1 == -1) y1 = '\0';
+        if (y2 == -1) y2 = '\0';
+        if (y3 == -1) y3 = '\0';
+        if (y4 == -1) y4 = '\0';
+
+        ok = (int)(ok && (x1 >= '\0' && x1 < 91));  //  /YYYYXXXX$csT
+        ok = (int)(ok && (x2 >= '\0' && x2 < 91));  //  0123456789012
+        ok = (int)(ok && (x3 >= '\0' && x3 < 91));
+        ok = (int)(ok && (x4 >= '\0' && x4 < 91));
+        ok = (int)(ok && (y1 >= '\0' && y1 < 91));
+        ok = (int)(ok && (y2 >= '\0' && y2 < 91));
+        ok = (int)(ok && (y3 >= '\0' && y3 < 91));
+        ok = (int)(ok && (y4 >= '\0' && y4 < 91));
+
+        T &= 0x3F;      // DK7IN: force Compression Byte to valid format
+                        // mask off upper two unused bits, they should be zero!?
+
+        ok = (int)(ok && (c == -1 || ((c >=0 && c < 91) && (s >= 0 && s < 91) && (T >= 0 && T < 64))));
+
+        if (ok) {
+            lat = (((y1 * 91 + y2) * 91 + y3) * 91 + y4 ) / 380926.0; // in deg, 0:  90°N
+            lon = (((x1 * 91 + x2) * 91 + x3) * 91 + x4 ) / 190463.0; // in deg, 0: 180°W
+            lat *= 60 * 60 * 100;                       // in 1/100 sec
+            lon *= 60 * 60 * 100;                       // in 1/100 sec
+
+            // The below check should _not_ be done.  Compressed
+            // format can resolve down to about 1 foot worldwide
+            // (0.3 meters).
+            //if ((((long)(lat+4) % 60) > 8) || (((long)(lon+4) % 60) > 8))
+            //    ok = 0;   // check max resolution 0.01 min to
+                            // catch even more errors
+        }
+    }
+
+    if (ok) {
+        overlay_symbol(my_data[9], my_data[0], p_station);      // Symbol / Table
+
+        // Callsign check here includes checking SSID for an exact
+        // match
+//        if (!is_my_call(p_station->call_sign,1)) {  // don't change my position, I know it better...
+        if ( !(is_my_station(p_station)) ) {  // don't change my position, I know it better...
+            // Record the uncompressed lat/long that we just
+            // computed.
+            p_station->coord_lat = (long)((lat));               // in 1/100 sec
+            p_station->coord_lon = (long)((lon));               // in 1/100 sec
+        }
+
+        if (c >= 0) {                                   // ignore csT if c = ' '
+            if (c < 90) {   // Found course/speed or altitude bytes
+                if ((T & 0x18) == 0x10) {   // check for GGA (with altitude)
+                    xastir_snprintf(p_station->altitude, sizeof(p_station->altitude), "%06.0f",pow(1.002,(double)(c*91+s))*0.3048);
+                }
+                else { // Found compressed course/speed bytes
+
+                    // Convert 0 degrees to 360 degrees so that
+                    // Xastir will see it as a valid course and do
+                    // dead-reckoning properly on this station
+                    if (c == 0) {
+                        c = 90;
+                    }
+
+                    // Compute course in degrees
+                    xastir_snprintf(p_station->course,
+                        sizeof(p_station->course),
+                        "%03d",
+                        c*4);
+
+                    // Compute speed in knots
+                    xastir_snprintf(p_station->speed,
+                        sizeof(p_station->speed),
+                        "%03.0f",
+                        pow( 1.08,(double)s ) - 1.0);
+
+                    //fprintf(stderr,"Decoded speed:%s, course:%s\n",p_station->speed,p_station->course);
+
+                }
+            }
+            else {    // Found pre-calculated radio range bytes
+                if (c == 90) {
+                    // pre-calculated radio range
+                    range = 2 * pow(1.08,(double)s);    // miles
+
+                    // DK7IN: dirty hack...  but better than nothing
+                    if (s <= 5)                         // 2.9387 mi
+                        xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "000");
+                    else if (s <= 17)                   // 7.40 mi
+                        xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "111");
+                    else if (s <= 36)                   // 31.936 mi
+                        xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "222");
+                    else if (s <= 75)                   // 642.41 mi
+                        xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "333");
+                    else                       // max 90:  2037.8 mi
+                        xastir_snprintf(p_station->power_gain, sizeof(p_station->power_gain), "PHG%s0", "444");
+                }
+            }
+        }
+        (*info) += skip;    // delete position from comment
+    }
+
+
+    //fprintf(stderr,"  extract_comp_position end: %s\n",*info);
+
+    return(ok);
+}
+
+
+/*
+ *  Extract bearing and number/range/quality from beginning of info field
+ */
+int extract_bearing_NRQ(char *info, char *bearing, char *nrq) {
+    int i,found,len;
+
+    len = (int)strlen(info);
+    found = 0;
+    if (len >= 8) {
+        found = 1;
+        for(i=1; found && i<8; i++)         // check data format
+            if(!(isdigit((int)info[i]) || (i==4 && info[i]=='/')))
+                found=0;
+    }
+    if (found) {
+        substr(bearing,info+1,3);
+        substr(nrq,info+5,3);
+
+        for (i=0;i<=len-8;i++)        // delete bearing/nrq from info field
+            info[i] = info[i+8];
+    }
+
+    if (!found) {
+        bearing[0] ='\0';
+        nrq[0]='\0';
+    }
+    return(found);
+}
+
+
+
+
+// APRS Data Extensions               [APRS Reference p.27]
+//  .../...  Course & Speed, may be followed by others (see p.27)
+//  .../...  Wind Dir and Speed
+//  PHG....  Station Power and Effective Antenna Height/Gain
+//  RNG....  Pre-Calculated Radio Range
+//  DFS....  DF Signal Strength and Effective Antenna Height/Gain
+//  T../C..  Area Object Descriptor
+
+/* Extract one of several possible APRS Data Extensions */
+void process_data_extension(AprsDataRow *p_station, char *data, /*@unused@*/ int type) {
+    char temp1[7+1];
+    char temp2[3+1];
+    char temp3[10+1];
+    char bearing[3+1];
+    char nrq[3+1];
+
+    if (p_station->aprs_symbol.aprs_type == '\\' && p_station->aprs_symbol.aprs_symbol == 'l') {
+            /* This check needs to come first because the area object extension can look
+               exactly like what extract_speed_course will attempt to decode. */
+            extract_area(p_station, data);
+    }
+    else {
+        clear_area(p_station); // we got a packet with a non area symbol, so clear the data
+
+        if (extract_speed_course(data,temp1,temp2)) {  // ... from Mic-E, etc.
+        //fprintf(stderr,"extracted speed/course\n");
+
+            if (atof(temp2) > 0) {
+            //fprintf(stderr,"course is non-zero\n");
+            xastir_snprintf(p_station->speed,
+                sizeof(p_station->speed),
+                "%06.2f",
+                atof(temp1));
+            xastir_snprintf(p_station->course,  // in degrees
+                sizeof(p_station->course),
+                "%s",
+                temp2);
+            }
+
+            if (extract_bearing_NRQ(data, bearing, nrq)) {  // Beam headings from DF'ing
+                //fprintf(stderr,"extracted bearing and NRQ\n");
+                xastir_snprintf(p_station->bearing,
+                    sizeof(p_station->bearing),
+                    "%s",
+                    bearing);
+                xastir_snprintf(p_station->NRQ,
+                    sizeof(p_station->NRQ),
+                    "%s",
+                    nrq);
+                p_station->signal_gain[0] = '\0';   // And blank out the shgd values
+            }
+        }
+        // Don't try to extract speed & course if a compressed
+        // object.  Test for beam headings for compressed packets
+        // here
+        else if (extract_bearing_NRQ(data, bearing, nrq)) {  // Beam headings from DF'ing
+
+            //fprintf(stderr,"extracted bearing and NRQ\n");
+            xastir_snprintf(p_station->bearing,
+                    sizeof(p_station->bearing),
+                    "%s",
+                    bearing);
+            xastir_snprintf(p_station->NRQ,
+                    sizeof(p_station->NRQ),
+                    "%s",
+                    nrq);
+            p_station->signal_gain[0] = '\0';   // And blank out the shgd values
+        }
+        else {
+            if (extract_powergain_range(data,temp1)) {
+
+//fprintf(stderr,"Found power_gain: %s\n", temp1);
+
+                xastir_snprintf(p_station->power_gain,
+                    sizeof(p_station->power_gain),
+                    "%s",
+                    temp1);
+
+                if (extract_bearing_NRQ(data, bearing, nrq)) {  // Beam headings from DF'ing
+                    //fprintf(stderr,"extracted bearing and NRQ\n");
+                    xastir_snprintf(p_station->bearing,
+                        sizeof(p_station->bearing),
+                        "%s",
+                        bearing);
+                    xastir_snprintf(p_station->NRQ,
+                        sizeof(p_station->NRQ),
+                        "%s",
+                        nrq);
+                    p_station->signal_gain[0] = '\0';   // And blank out the shgd values
+                }
+            }
+            else {
+                if (extract_omnidf(data,temp1)) {
+                    xastir_snprintf(p_station->signal_gain,
+                        sizeof(p_station->signal_gain),
+                        "%s",
+                        temp1);   // Grab the SHGD values
+                    p_station->bearing[0] = '\0';   // And blank out the bearing/NRQ values
+                    p_station->NRQ[0] = '\0';
+
+                    // The spec shows speed/course before DFS, but example packets that
+                    // come with DOSaprs show DFSxxxx/speed/course.  We'll take care of
+                    // that possibility by trying to decode speed/course again.
+                    if (extract_speed_course(data,temp1,temp2)) {  // ... from Mic-E, etc.
+                    //fprintf(stderr,"extracted speed/course\n");
+                        if (atof(temp2) > 0) {
+                            //fprintf(stderr,"course is non-zero\n");
+                            xastir_snprintf(p_station->speed,
+                                sizeof(p_station->speed),
+                                "%06.2f",
+                                atof(temp1));
+                            xastir_snprintf(p_station->course,
+                                sizeof(p_station->course),
+                                "%s",
+                                temp2);                    // in degrees
+                        }
+                    }
+
+                    // The spec shows that omnidf and bearing/NRQ can be in the same
+                    // packet, which makes no sense, but we'll try to decode it that
+                    // way anyway.
+                    if (extract_bearing_NRQ(data, bearing, nrq)) {  // Beam headings from DF'ing
+                        //fprintf(stderr,"extracted bearing and NRQ\n");
+                        xastir_snprintf(p_station->bearing,
+                            sizeof(p_station->bearing),
+                            "%s",
+                            bearing);
+                        xastir_snprintf(p_station->NRQ,
+                            sizeof(p_station->NRQ),
+                            "%s",
+                            nrq);
+                        //p_station->signal_gain[0] = '\0';   // And blank out the shgd values
+                    }
+                }
+            }
+        }
+
+        if (extract_signpost(data, temp2)) {
+            //fprintf(stderr,"extracted signpost data\n");
+            xastir_snprintf(p_station->signpost,
+                sizeof(p_station->signpost),
+                "%s",
+                temp2);
+        }
+
+        if (extract_probability_min(data, temp3, sizeof(temp3))) {
+            //fprintf(stderr,"extracted probability_min data: %s\n",temp3);
+            xastir_snprintf(p_station->probability_min,
+                sizeof(p_station->probability_min),
+                "%s",
+                temp3);
+        }
+        if (extract_probability_max(data, temp3, sizeof(temp3))) {
+            //fprintf(stderr,"extracted probability_max data: %s\n",temp3);
+            xastir_snprintf(p_station->probability_max,
+                sizeof(p_station->probability_max),
+                "%s",
+                temp3);
+        }
+    }
+}
+
+
+/*
+ *  Extract altitude from APRS info field          "/A=012345"    in feet
+ */
+int extract_altitude(char *info, char *altitude) {
+    int i,ofs,found,len;
+
+    found=0;
+    len = (int)strlen(info);
+    for(ofs=0; !found && ofs<len-8; ofs++)      // search for start sequence
+        if (strncmp(info+ofs,"/A=",3)==0) {
+            found=1;
+            // Are negative altitudes even defined?  Yes!  In Mic-E spec to -10,000 meters
+            if(!isdigit((int)info[ofs+3]) && info[ofs+3]!='-')  // First char must be digit or '-'
+                found=0;
+            for(i=4; found && i<9; i++)         // check data format for next 5 chars
+                if(!isdigit((int)info[ofs+i]))
+                    found=0;
+    }
+    if (found) {
+        ofs--;  // was one too much on exit from for loop
+        substr(altitude,info+ofs+3,6);
+        for (i=ofs;i<=len-9;i++)        // delete altitude from info field
+            info[i] = info[i+9];
+    }
+    else
+        altitude[0] = '\0';
+    return(found);
+}
+
+
+
+
+/* extract all available information from info field */
+void process_info_field(AprsDataRow *p_station, char *info, /*@unused@*/ int type) {
+    char temp_data[6+1];       
+//    char time_data[MAX_TIME];
+
+    if (extract_altitude(info,temp_data)) {                         // get altitude
+        xastir_snprintf(p_station->altitude, sizeof(p_station->altitude), "%.2f",atof(temp_data)*0.3048);
+        //fprintf(stderr,"%.2f\n",atof(temp_data)*0.3048);
+    }
+    // do other things...
+}
+
+
+
+
+
+
+/*
+ *  Extract Uncompressed Position Report from begin of line
+ *
+ * If a position is found, it is deleted from the data.
+ */
+int extract_position(AprsDataRow *p_station, char **info, int type) {
+    int ok;
+    char temp_lat[8+1];
+    char temp_lon[9+1];
+    char temp_grid[8+1];
+    char *my_data;
+    float gridlat;
+    float gridlon;
+    my_data = (*info);
+
+    if (type != APRS_GRID){ // Not a grid
+        ok = (int)(strlen(my_data) >= 19);
+        ok = (int)(ok && my_data[4]=='.' && my_data[14]=='.'
+            && (toupper(my_data[7]) =='N' || toupper(my_data[7]) =='S')
+            && (toupper(my_data[17])=='E' || toupper(my_data[17])=='W'));
+        // errors found:  [4]: X   [7]: n s   [17]: w e
+        if (ok) {
+            ok =             is_num_chr(my_data[0]);           // 5230.31N/01316.88E>
+            ok = (int)(ok && is_num_chr(my_data[1]));          // 0123456789012345678
+            ok = (int)(ok && is_num_or_sp(my_data[2]));
+            ok = (int)(ok && is_num_or_sp(my_data[3]));
+            ok = (int)(ok && is_num_or_sp(my_data[5]));
+            ok = (int)(ok && is_num_or_sp(my_data[6]));
+            ok = (int)(ok && is_num_chr(my_data[9]));
+            ok = (int)(ok && is_num_chr(my_data[10]));
+            ok = (int)(ok && is_num_chr(my_data[11]));
+            ok = (int)(ok && is_num_or_sp(my_data[12]));
+            ok = (int)(ok && is_num_or_sp(my_data[13]));
+            ok = (int)(ok && is_num_or_sp(my_data[15]));
+            ok = (int)(ok && is_num_or_sp(my_data[16]));
+        }
+                                            
+        if (ok) {
+            overlay_symbol(my_data[18], my_data[8], p_station);
+            p_station->pos_amb = 0;
+            // spaces in latitude set position ambiguity, spaces in longitude do not matter
+            // we will adjust the lat/long to the center of the rectangle of ambiguity
+            if (my_data[2] == ' ') {      // nearest degree
+                p_station->pos_amb = 4;
+                my_data[2]  = my_data[12] = '3';
+                my_data[3]  = my_data[5]  = my_data[6]  = '0';
+                my_data[13] = my_data[15] = my_data[16] = '0';
+            }
+            else if (my_data[3] == ' ') { // nearest 10 minutes
+                p_station->pos_amb = 3;
+                my_data[3]  = my_data[13] = '5';
+                my_data[5]  = my_data[6]  = '0';
+                my_data[15] = my_data[16] = '0';
+            }
+            else if (my_data[5] == ' ') { // nearest minute
+                p_station->pos_amb = 2;
+                my_data[5]  = my_data[15] = '5';
+                my_data[6]  = '0';
+                my_data[16] = '0';
+            }
+            else if (my_data[6] == ' ') { // nearest 1/10th minute
+                p_station->pos_amb = 1;
+                my_data[6]  = my_data[16] = '5';
+            }
+
+            xastir_snprintf(temp_lat,
+                sizeof(temp_lat),
+                "%s",
+                my_data);
+            temp_lat[7] = toupper(my_data[7]);
+            temp_lat[8] = '\0';
+
+            xastir_snprintf(temp_lon,
+                sizeof(temp_lon),
+                "%s",
+                my_data+9);
+            temp_lon[8] = toupper(my_data[17]);
+            temp_lon[9] = '\0';
+
+            // Callsign check here also checks SSID for an exact
+            // match
+//            if (!is_my_call(p_station->call_sign,1)) {      // don't change my position, I know it better...
+            if ( !(is_my_station(p_station)) ) {      // don't change my position, I know it better...
+
+                p_station->coord_lat = convert_lat_s2l(temp_lat);   // ...in case of position ambiguity
+                p_station->coord_lon = convert_lon_s2l(temp_lon);
+            }
+
+            (*info) += 19;                  // delete position from comment
+        }
+    }
+    else { // It is a grid 
+        // first sanity checks, need more
+        ok = (int)(is_num_chr(my_data[2]));
+        ok = (int)(ok && is_num_chr(my_data[3]));
+        ok = (int)(ok && ((my_data[0]>='A')&&(my_data[0]<='R')));
+        ok = (int)(ok && ((my_data[1]>='A')&&(my_data[1]<='R')));
+        if (ok) {
+            xastir_snprintf(temp_grid,
+                sizeof(temp_grid),
+                "%s",
+                my_data);
+            // this test treats >6 digit grids as 4 digit grids; >6 are uncommon.
+            // the spec mentioned 4 or 6, I'm not sure >6 is even allowed.
+            if ( (temp_grid[6] != ']') || (temp_grid[4] == 0) || (temp_grid[5] == 0)){
+                p_station->pos_amb = 6; // 1deg lat x 2deg lon 
+                temp_grid[4] = 'L';
+                temp_grid[5] = 'L';
+            }
+            else {
+                p_station->pos_amb = 5; // 2.5min lat x 5min lon
+                temp_grid[4] = toupper(temp_grid[4]); 
+                temp_grid[5] = toupper(temp_grid[5]);
+            }
+            // These equations came from what I read in the qgrid source code and
+            // various mailing list archives.
+            gridlon= (20.*((float)temp_grid[0]-65.) + 2.*((float)temp_grid[2]-48.) + 5.*((float)temp_grid[4]-65.)/60.) - 180.;
+            gridlat= (10.*((float)temp_grid[1]-65.) + ((float)temp_grid[3]-48.) + 5.*(temp_grid[5]-65.)/120.) - 90.;
+            // could check for my callsign here, and avoid changing it...
+            p_station->coord_lat = (unsigned long)(32400000l + (360000.0 * (-gridlat)));
+            p_station->coord_lon = (unsigned long)(64800000l + (360000.0 * gridlon));
+            p_station->aprs_symbol.aprs_type = '/';
+            p_station->aprs_symbol.aprs_symbol = 'G';
+        }        // is it valid grid or not - "ok"
+        // could cut off the grid square from the comment here, but why bother?
+    } // is it grid or not
+    return(ok);
+}
+
+
+
+
+
+// Add a status line to the linked-list of status records
+// associated with a station.  Note that a blank status line is
+// allowed, but we don't store that unless we have seen a non-blank
+// status line previously.
+//
+void add_status(AprsDataRow *p_station, char *status_string) {
+    AprsCommentRow *ptr;
+    int add_it = 0;
+    int len;
+
+
+    len = strlen(status_string);
+
+    // Eliminate line-end chars
+    if (len > 1) {
+        if ( (status_string[len-1] == '\n')
+                || (status_string[len-1] == '\r') ) {
+            status_string[len-1] = '\0';
+        }
+    }
+
+    // Shorten it
+    (void)remove_trailing_spaces(status_string);
+    (void)remove_leading_spaces(status_string);
+    len = strlen(status_string);
+
+    // Check for valid pointer
+    if (p_station != NULL) {
+
+// We should probably create a new station record for this station
+// if there isn't one.  This allows us to collect as much info about
+// a station as we can until a posit comes in for it.  Right now we
+// don't do this.  If we decide to do this in the future, we also
+// need a method to find out the info about that station without
+// having to click on an icon, 'cuz the symbol won't be on our map
+// until we have a posit.
+
+        //fprintf(stderr,"Station:%s\tStatus:%s\n",p_station->call_sign,status_string);
+
+        // Check whether we have any data stored for this station
+        if (p_station->status_data == NULL) {
+            if (len > 0) {
+                // No status stored yet and new status is non-NULL,
+                // so add it to the list.
+                add_it++;
+            }
+        }
+        else {  // We have status data stored already
+                // Check for an identical string
+            AprsCommentRow *ptr2;
+            int ii = 0;
+            ptr = p_station->status_data;
+            ptr2 = ptr;
+            while (ptr != NULL) {
+
+                // Note that both text_ptr and comment_string can be
+                // empty strings.
+
+                if (strcasecmp(ptr->text_ptr, status_string) == 0) {
+                    // Found a matching string
+                    //fprintf(stderr,"Found match:
+                    //%s:%s\n",p_station->call_sign,status_string);
+
+// Instead of updating the timestamp, we'll delete the record from
+// the list and add it to the top in the code below.  Make sure to
+// tweak the "ii" pointer so that we don't end up shortening the
+// list unnecessarily.
+                    if (ptr == p_station->status_data) {
+
+                        // Only update the timestamp: We're at the
+                        // beginning of the list already.
+                        ptr->sec_heard = sec_now();
+
+                        return; // No need to add a new record
+                    }
+                    else {  // Delete the record
+                        AprsCommentRow *ptr3;
+
+                        // Keep a pointer to the record
+                        ptr3 = ptr;
+
+                        // Close the chain, skipping this record
+                        ptr2->next = ptr3->next;
+
+                        // Skip "ptr" over the record we wish to
+                        // delete
+                        ptr = ptr3->next;
+
+                        // Free the record
+                        free(ptr3->text_ptr);
+                        free(ptr3);
+
+                        // Muck with the counter 'cuz we just
+                        // deleted one record
+                        ii--;
+                    }
+                }
+                ptr2 = ptr; // Back one record
+                if (ptr != NULL) {
+                    ptr = ptr->next;
+                }
+                ii++;
+            }
+
+
+            // No matching string found, or new timestamp found for
+            // old record.  Add it to the top of the list.
+            add_it++;
+            //fprintf(stderr,"No match:
+            //%s:%s\n",p_station->call_sign,status_string);
+
+            // We counted the records.  If we have more than
+            // MAX_STATUS_LINES records we'll delete/free the last
+            // one to make room for the next.  This keeps us from
+            // storing unique status records ad infinitum for active
+            // stations, limiting the total space used.
+            //
+            if (ii >= MAX_STATUS_LINES) {
+                // We know we didn't get a match, and that our list
+                // is full (as full as we want it to be).  Traverse
+                // the list again, looking for ptr2->next->next ==
+                // NULL.  If found, free last record and set the
+                // ptr2->next pointer to NULL.
+                ptr2 = p_station->status_data;
+                while (ptr2->next->next != NULL) {
+                    ptr2 = ptr2->next;
+                }
+                // At this point, we have a pointer to the last
+                // record in ptr2->next.  Free it and the text
+                // string in it.
+                free(ptr2->next->text_ptr);
+                free(ptr2->next);
+                ptr2->next = NULL;
+            } 
+        }
+
+        if (add_it) {   // We add to the beginning so we don't have
+                        // to traverse the linked list.  This also
+                        // puts new records at the beginning of the
+                        // list to keep them in sorted order.
+
+            ptr = p_station->status_data;  // Save old pointer to records
+            p_station->status_data = (AprsCommentRow *)malloc(sizeof(AprsCommentRow));
+            CHECKMALLOC(p_station->status_data);
+
+            p_station->status_data->next = ptr;    // Link in old records or NULL
+
+            // Malloc the string space we'll need, attach it to our
+            // new record
+            p_station->status_data->text_ptr = (char *)malloc(sizeof(char) * (len+1));
+            CHECKMALLOC(p_station->status_data->text_ptr);
+
+            // Fill in the string
+            xastir_snprintf(p_station->status_data->text_ptr,
+                len+1,
+                "%s",
+                status_string);
+
+            // Fill in the timestamp
+            p_station->status_data->sec_heard = sec_now();
+
+            //fprintf(stderr,"Station:%s\tStatus:%s\n\n",p_station->call_sign,p_station->status_data->text_ptr);
+        }
+    }
+}
+
+
+// Add a comment line to the linked-list of comment records
+// associated with a station.  Note that a blank comment is allowed
+// and necessary for the times when we wish to blank out the comment
+// on an object/item, but we don't store that unless we have seen a
+// non-blank comment line previously.
+//
+void add_comment(AprsDataRow *p_station, char *comment_string) {
+    AprsCommentRow *ptr;
+    int add_it = 0;
+    int len;
+
+
+    len = strlen(comment_string);
+
+    // Eliminate line-end chars
+    if (len > 1) {
+        if ( (comment_string[len-1] == '\n')
+                || (comment_string[len-1] == '\r') ) {
+            comment_string[len-1] = '\0';
+        }
+    }
+
+    // Shorten it
+    (void)remove_trailing_spaces(comment_string);
+    (void)remove_leading_spaces(comment_string);
+
+    len = strlen(comment_string);
+
+    // Check for valid pointer
+    if (p_station != NULL) {
+
+        // Check whether we have any data stored for this station
+        if (p_station->comment_data == NULL) {
+            if (len > 0) {
+                // No comments stored yet and new comment is
+                // non-NULL, so add it to the list.
+                add_it++;
+            }
+        }
+        else {  // We have comment data stored already
+                // Check for an identical string
+            AprsCommentRow *ptr2;
+            int ii = 0;
+            ptr = p_station->comment_data;
+            ptr2 = ptr;
+            while (ptr != NULL) {
+
+                // Note that both text_ptr and comment_string can be
+                // empty strings.
+
+                if (strcasecmp(ptr->text_ptr, comment_string) == 0) {
+                    // Found a matching string
+//fprintf(stderr,"Found match: %s:%s\n",p_station->call_sign,comment_string);
+
+// Instead of updating the timestamp, we'll delete the record from
+// the list and add it to the top in the code below.  Make sure to
+// tweak the "ii" pointer so that we don't end up shortening the
+// list unnecessarily.
+                    if (ptr == p_station->comment_data) {
+                        // Only update the timestamp:  We're at the
+                        // beginning of the list already.
+                        ptr->sec_heard = sec_now();
+
+                        return; // No need to add a new record
+                    }
+                    else {  // Delete the record
+                        AprsCommentRow *ptr3;
+
+                        // Keep a pointer to the record
+                        ptr3 = ptr;
+
+                        // Close the chain, skipping this record
+                        ptr2->next = ptr3->next;
+
+                        // Skip "ptr" over the record we with to
+                        // delete
+                        ptr = ptr3->next;
+
+                        // Free the record
+                        free(ptr3->text_ptr);
+                        free(ptr3);
+
+                        // Muck with the counter 'cuz we just
+                        // deleted one record
+                        ii--;
+                    }
+                }
+                ptr2 = ptr; // Keep this pointer one record back as
+                            // we progress.
+
+                if (ptr != NULL) {
+                    ptr = ptr->next;
+                }
+
+                ii++;
+            }
+            // No matching string found, or new timestamp found for
+            // old record.  Add it to the top of the list.
+            add_it++;
+            //fprintf(stderr,"No match: %s:%s\n",p_station->call_sign,comment_string);
+
+            // We counted the records.  If we have more than
+            // MAX_COMMENT_LINES records we'll delete/free the last
+            // one to make room for the next.  This keeps us from
+            // storing unique comment records ad infinitum for
+            // active stations, limiting the total space used.
+            //
+            if (ii >= MAX_COMMENT_LINES) {
+
+                // We know we didn't get a match, and that our list
+                // is full (as we want it to be).  Traverse the list
+                // again, looking for ptr2->next->next == NULL.  If
+                // found, free that last record and set the
+                // ptr2->next pointer to NULL.
+                ptr2 = p_station->comment_data;
+                while (ptr2->next->next != NULL) {
+                    ptr2 = ptr2->next;
+                }
+                // At this point, we have a pointer to the last
+                // record in ptr2->next.  Free it and the text
+                // string in it.
+                free(ptr2->next->text_ptr);
+                free(ptr2->next);
+                ptr2->next = NULL;
+            } 
+        }
+
+        if (add_it) {   // We add to the beginning so we don't have
+                        // to traverse the linked list.  This also
+                        // puts new records at the beginning of the
+                        // list to keep them in sorted order.
+
+            ptr = p_station->comment_data;  // Save old pointer to records
+            p_station->comment_data = (AprsCommentRow *)malloc(sizeof(AprsCommentRow));
+            CHECKMALLOC(p_station->comment_data);
+
+            p_station->comment_data->next = ptr;    // Link in old records or NULL
+
+            // Malloc the string space we'll need, attach it to our
+            // new record
+            p_station->comment_data->text_ptr = (char *)malloc(sizeof(char) * (len+1));
+            CHECKMALLOC(p_station->comment_data->text_ptr);
+
+            // Fill in the string
+            xastir_snprintf(p_station->comment_data->text_ptr,
+                len+1,
+                "%s",
+                comment_string);
+
+            // Fill in the timestamp
+            p_station->comment_data->sec_heard = sec_now();
+        }
+    }
+}
+
+
+
+// Extract single weather data item from "data".  Returns it in
+// "temp".  Modifies "data" to remove the found data from the
+// string.  Returns a 1 if found, 0 if not found.
+//
+// PE1DNN
+// If the item is contained in the string but does not contain a
+// value then regard the item as "not found" in the weather string.
+//
+int extract_weather_item(char *data, char type, int datalen, char *temp) {
+    int i,ofs,found,len;
+
+
+//fprintf(stderr,"%s\n",data);
+
+    found=0;
+    len = (int)strlen(data);
+    for(ofs=0; !found && ofs<len-datalen; ofs++)      // search for start sequence
+        if (data[ofs]==type) {
+            found=1;
+            if (!is_weather_data(data+ofs+1, datalen))
+                found=0;
+        }
+    if (found) {   // ofs now points after type character
+        substr(temp,data+ofs,datalen);
+        for (i=ofs-1;i<len-datalen;i++)        // delete item from info field
+            data[i] = data[i+datalen+1];
+        if((temp[0] == ' ') || (temp[0] == '.')) {
+            // found it, but it doesn't contain a value!
+            // Clean up and report "not found" - PE1DNN
+            temp[0] = '\0';
+            found = 0;
+        }
+        else
+        {
+//                fprintf(stderr,"extract_weather_item: %s\n",temp);
+        }
+    }
+    else
+        temp[0] = '\0';
+    return(found);
+}
+
+
+
+
+
+// test-extract single weather data item from information field.  In
+// other words:  Does not change the input string, but does test
+// whether the data is present.  Returns a 1 if found, 0 if not
+// found.
+//
+// PE1DNN
+// If the item is contained in the string but does not contain a
+// value then regard the item as "not found" in the weather string.
+//
+int test_extract_weather_item(char *data, char type, int datalen) {
+    int ofs,found,len;
+
+    found=0;
+    len = (int)strlen(data);
+    for(ofs=0; !found && ofs<len-datalen; ofs++)      // search for start sequence
+        if (data[ofs]==type) {
+            found=1;
+            if (!is_weather_data(data+ofs+1, datalen))
+                found=0;
+        }
+
+    // We really should test for numbers here (with an optional
+    // leading '-'), and test across the length of the substring.
+    //
+    if(found && ((data[ofs+1] == ' ') || (data[ofs+1] == '.'))) {
+        // found it, but it doesn't contain a value!
+        // report "not found" - PE1DNN
+        found = 0;
+    }
+
+    //fprintf(stderr,"test_extract: %c %d\n",type,found);
+    return(found);
+}
+
+
+int get_weather_record(AprsDataRow *fill) {    // get or create weather storage
+    int ok=1;
+
+    if (fill->weather_data == NULL) {      // new weather data, allocate storage and init
+        fill->weather_data = malloc(sizeof(AprsWeatherRow));
+        if (fill->weather_data == NULL) {
+            fprintf(stderr,"Couldn't allocate memory in get_weather_record()\n");
+            ok = 0;
+        }
+        else {
+            init_weather(fill->weather_data);
+        }
+    }
+    return(ok);
+}
+
+
+
+
+// DK7IN 77
+// raw weather report            in information field
+// positionless weather report   in information field
+// complete weather report       with lat/lon
+//  see APRS Reference page 62ff
+//
+// Added 'F' for Fuel Temp and 'f' for Fuel Moisture in order to
+// decode these two new parameters used for RAWS weather station
+// objects.
+//
+// By the time we call this function we've already extracted any
+// time/position info at the beginning of the string.
+//
+int extract_weather(AprsDataRow *p_station, char *data, int compr) {
+    char time_data[MAX_TIME];
+    char temp[5];
+    int  ok = 1;
+    AprsWeatherRow *weather;
+    char course[4];
+    char speed[4];
+    int in_knots = 0;
+
+//WE7U
+// Try copying the string to a temporary string, then do some
+// extractions to see if a few weather items are present?  This
+// would allow us to have the weather items in any order, and if
+// enough of them were present, we consider it to be a weather
+// packet?  We'd need to qualify all of the data to make sure we had
+// the proper number of digits for each.  The trick is to make sure
+// we don't decide it's a weather packet if it's not.  We don't know
+// what people might send in packets in the future.
+
+    if (compr) {        // compressed position report
+        // Look for weather data in fixed locations first
+        if (strlen(data) >= 8
+                && data[0] =='g' && is_weather_data(&data[1],3)
+                && data[4] =='t' && is_weather_data(&data[5],3)) {
+
+            // Snag WX course/speed from compressed position data.
+            // This speed is in knots.  This assumes that we've
+            // already extracted speed/course from the compressed
+            // packet.  extract_comp_position() extracts
+            // course/speed as well.
+            xastir_snprintf(speed,
+                sizeof(speed),
+                "%s",
+                p_station->speed);
+            xastir_snprintf(course,
+                sizeof(course),
+                "%s",
+                p_station->course);
+            in_knots = 1;
+
+            //fprintf(stderr,"Found compressed wx\n");
+        }
+        // Look for weather data in non-fixed locations (RAWS WX
+        // Stations?)
+        else if ( strlen(data) >= 8
+                && test_extract_weather_item(data,'g',3)
+                && test_extract_weather_item(data,'t',3) ) {
+
+            // Snag WX course/speed from compressed position data.
+            // This speed is in knots.  This assumes that we've
+            // already extracted speed/course from the compressed
+            // packet.  extract_comp_position() extracts
+            // course/speed as well.
+            xastir_snprintf(speed,
+                sizeof(speed),
+                "%s",
+                p_station->speed);
+            xastir_snprintf(course,
+                sizeof(course),
+                "%s",
+                p_station->course);
+            in_knots = 1;
+
+            //fprintf(stderr,"Found compressed WX in non-fixed locations! %s:%s\n",
+            //    p_station->call_sign,data);
+
+        }
+        else {  // No weather data found
+            ok = 0;
+
+            //fprintf(stderr,"No compressed wx\n");
+        }
+    }
+    else {    // Look for non-compressed weather data
+        // Look for weather data in defined locations first
+        if (strlen(data)>=15 && data[3]=='/'
+                && is_weather_data(data,3) && is_weather_data(&data[4],3)
+                && data[7] =='g' && is_weather_data(&data[8], 3)
+                && data[11]=='t' && is_weather_data(&data[12],3)) {    // Complete Weather Report
+
+            // Get speed/course.  Speed is in knots.
+            (void)extract_speed_course(data,speed,course);
+            in_knots = 1;
+
+            // Either one not found?  Try again.
+            if ( (speed[0] == '\0') || (course[0] == '\0') ) {
+
+                // Try to get speed/course from 's' and 'c' fields
+                // (another wx format).  Speed is in mph.
+                (void)extract_weather_item(data,'c',3,course); // wind direction (in degrees)
+                (void)extract_weather_item(data,'s',3,speed);  // sustained one-minute wind speed (in mph)
+                in_knots = 0;
+            }
+
+            //fprintf(stderr,"Found Complete Weather Report\n");
+        }
+        // Look for date/time and weather in fixed locations first
+        else if (strlen(data)>=16
+                && data[0] =='c' && is_weather_data(&data[1], 3)
+                && data[4] =='s' && is_weather_data(&data[5], 3)
+                && data[8] =='g' && is_weather_data(&data[9], 3)
+                && data[12]=='t' && is_weather_data(&data[13],3)) { // Positionless Weather Data
+//fprintf(stderr,"Found positionless wx data\n");
+            // Try to snag speed/course out of first 7 bytes.  Speed
+            // is in knots.
+            (void)extract_speed_course(data,speed,course);
+            in_knots = 1;
+
+            // Either one not found?  Try again.
+            if ( (speed[0] == '\0') || (course[0] == '\0') ) {
+//fprintf(stderr,"Trying again for course/speed\n");
+                // Also try to get speed/course from 's' and 'c' fields
+                // (another wx format)
+                (void)extract_weather_item(data,'c',3,course); // wind direction (in degrees)
+                (void)extract_weather_item(data,'s',3,speed);  // sustained one-minute wind speed (in mph)
+                in_knots = 0;
+            }
+
+            //fprintf(stderr,"Found weather\n");
+        }
+        // Look for weather data in non-fixed locations (RAWS WX
+        // Stations?)
+        else if (strlen (data) >= 16
+                && test_extract_weather_item(data,'h',2)
+                && test_extract_weather_item(data,'g',3)
+                && test_extract_weather_item(data,'t',3) ) {
+
+            // Try to snag speed/course out of first 7 bytes.  Speed
+            // is in knots.
+            (void)extract_speed_course(data,speed,course);
+            in_knots = 1;
+
+            // Either one not found?  Try again.
+            if ( (speed[0] == '\0') || (course[0] == '\0') ) {
+
+                // Also try to get speed/course from 's' and 'c' fields
+                // (another wx format)
+                (void)extract_weather_item(data,'c',3,course); // wind direction (in degrees)
+                (void)extract_weather_item(data,'s',3,speed);  // sustained one-minute wind speed (in mph)
+                in_knots = 0;
+            }
+            //fprintf(stderr,"Found WX in non-fixed locations!  %s:%s\n",
+            //    p_station->call_sign,data);
+        }
+        else {  // No weather data found
+            ok = 0;
+
+            //fprintf(stderr,"No wx found\n");
+        }
+    }
+
+    if (ok) {
+        ok = get_weather_record(p_station);     // get existing or create new weather record
+    }
+
+    if (ok) {
+        weather = p_station->weather_data;
+
+        // Copy into weather speed variable.  Convert knots to mph
+        // if necessary.
+        if (in_knots) {
+            xastir_snprintf(weather->wx_speed,
+                sizeof(weather->wx_speed),
+                "%03.0f",
+                atoi(speed) * 1.1508);  // Convert knots to mph
+        }
+        else {
+            // Already in mph.  Copy w/no conversion.
+            xastir_snprintf(weather->wx_speed,
+                sizeof(weather->wx_speed),
+                "%s",
+                speed);
+        }
+
+        xastir_snprintf(weather->wx_course,
+            sizeof(weather->wx_course),
+            "%s",
+            course);
+
+        if (compr) {        // course/speed was taken from normal data, delete that
+            // fix me: we delete a potential real speed/course now
+            // we should differentiate between normal and weather data in compressed position decoding...
+//            p_station->speed_time[0]     = '\0';
+            p_station->speed[0]          = '\0';
+            p_station->course[0]         = '\0';
+        }
+
+        (void)extract_weather_item(data,'g',3,weather->wx_gust);      // gust (peak wind speed in mph in the last 5 minutes)
+
+        (void)extract_weather_item(data,'t',3,weather->wx_temp);      // temperature (in deg Fahrenheit), could be negative
+
+        (void)extract_weather_item(data,'r',3,weather->wx_rain);      // rainfall (1/100 inch) in the last hour
+
+        (void)extract_weather_item(data,'p',3,weather->wx_prec_24);   // rainfall (1/100 inch) in the last 24 hours
+
+        (void)extract_weather_item(data,'P',3,weather->wx_prec_00);   // rainfall (1/100 inch) since midnight
+
+        if (extract_weather_item(data,'h',2,weather->wx_hum))         // humidity (in %, 00 = 100%)
+                xastir_snprintf(weather->wx_hum, sizeof(weather->wx_hum), "%03d",(atoi(weather->wx_hum)+99)%100+1);
+
+        if (extract_weather_item(data,'b',5,weather->wx_baro))  // barometric pressure (1/10 mbar / 1/10 hPascal)
+            xastir_snprintf(weather->wx_baro,
+                sizeof(weather->wx_baro),
+                "%0.1f",
+                (float)(atoi(weather->wx_baro)/10.0));
+
+        // If we parsed a speed/course, a second 's' parameter means
+        // snowfall.  Try to parse it, but only in the case where
+        // we've parsed speed out of this packet already.
+        if ( (speed[0] != '\0') && (course[0] != '\0') ) {
+            (void)extract_weather_item(data,'s',3,weather->wx_snow);      // snowfall, inches in the last 24 hours
+        }
+
+        (void)extract_weather_item(data,'L',3,temp);                  // luminosity (in watts per square meter) 999 and below
+
+        (void)extract_weather_item(data,'l',3,temp);                  // luminosity (in watts per square meter) 1000 and above
+
+        (void)extract_weather_item(data,'#',3,temp);                  // raw rain counter
+
+        (void)extract_weather_item(data,'F',3,weather->wx_fuel_temp); // Fuel Temperature in Â°F (RAWS)
+
+        if (extract_weather_item(data,'f',2,weather->wx_fuel_moisture))// Fuel Moisture (RAWS) (in %, 00 = 100%)
+            xastir_snprintf(weather->wx_fuel_moisture,
+                sizeof(weather->wx_fuel_moisture),
+                "%03d",
+                (atoi(weather->wx_fuel_moisture)+99)%100+1);
+
+//    extract_weather_item(data,'w',3,temp);                          // ?? text wUII
+
+    // now there should be the name of the weather station...
+
+        // Create a timestamp from the current time
+        xastir_snprintf(weather->wx_time,
+            sizeof(weather->wx_time),
+            "%s",
+            get_time(time_data));
+
+        // Set the timestamp in the weather record so that we can
+        // decide whether or not to "ghost" the weather data later.
+        weather->wx_sec_time=sec_now();
+//        weather->wx_data=1;  // we don't need this
+
+//        case ('.'):/* skip */
+//            wx_strpos+=4;
+//            break;
+
+//        default:
+//            wx_done=1;
+//            weather->wx_type=data[wx_strpos];
+//            if(strlen(data)>wx_strpos+1)
+//                xastir_snprintf(weather->wx_station,    
+//                    sizeof(weather->wx_station),
+//                    "%s",
+//                    data+wx_strpos+1);
+//            break;
+    }
+    return(ok);
+}
+
+
+
+// Initial attempt at decoding tropical storm, tropical depression,
+// and hurricane data.
+//
+// This data can be in an Object report, but can also be in an Item
+// or position report.
+// "/TS" = Tropical Storm
+// "/HC" = Hurricane
+// "/TD" = Tropical Depression
+// "/TY" = Typhoon
+// "/ST" = Super Typhoon
+// "/SC" = Severe Cyclone
+
+// The symbol will be either "\@" for current position, or "/@" for
+// predicted position.
+//
+int extract_storm(AprsDataRow *p_station, char *data, int compr) {
+    char time_data[MAX_TIME];
+    int  ok = 1;
+    AprsWeatherRow *weather;
+    char course[4];
+    char speed[4];  // Speed in knots
+    char *p, *p2;
+
+
+// Should probably encode the storm type in the weather object and
+// print it out in plain text in the Station Info dialog.
+
+    if ((p = strstr(data, "/TS")) != NULL) {
+        // We have a Tropical Storm
+//fprintf(stderr,"Tropical Storm! %s\n",data);
+    }
+    else if ((p = strstr(data, "/TD")) != NULL) {
+        // We have a Tropical Depression
+//fprintf(stderr,"Tropical Depression! %s\n",data);
+    }
+    else if ((p = strstr(data, "/HC")) != NULL) {
+        // We have a Hurricane
+//fprintf(stderr,"Hurricane! %s\n",data);
+    }
+    else if ((p = strstr(data, "/TY")) != NULL) {
+        // We have a Typhoon
+//fprintf(stderr,"Hurricane! %s\n",data);
+    }
+    else if ((p = strstr(data, "/ST")) != NULL) {
+        // We have a Super Typhoon
+//fprintf(stderr,"Hurricane! %s\n",data);
+    }
+    else if ((p = strstr(data, "/SC")) != NULL) {
+        // We have a Severe Cyclone
+//fprintf(stderr,"Hurricane! %s\n",data);
+    }
+    else {  // Not one of the three we're trying to decode
+        ok = 0;
+        return(ok);
+    }
+
+//fprintf(stderr,"\n%s\n",data);
+
+    // Back up 7 spots to try to extract the next items
+    p2 = p - 7;
+    if (p2 >= data) {
+        // Attempt to extract course/speed.  Speed in knots.
+        if (!extract_speed_course(p2,speed,course)) {
+            // No speed/course to extract
+//fprintf(stderr,"No speed/course found\n");
+            ok = 0;
+            return(ok);
+        }
+    }
+    else {  // Not enough characters for speed/course.  Must have
+            // guessed wrong on what type of data it is.
+//fprintf(stderr,"No speed/course found 2\n");
+        ok = 0;
+        return(ok);
+    }
+
+
+//fprintf(stderr,"%s\n",data);
+
+    if (ok) {
+
+        // If we got this far, we have speed/course and know what type
+        // of storm it is.
+//fprintf(stderr,"Speed: %s, Course: %s\n",speed,course);
+
+        ok = get_weather_record(p_station);     // get existing or create new weather record
+    }
+
+    if (ok) {
+//        p_station->speed_time[0]     = '\0';
+
+        p_station->weather_data->wx_storm = 1;  // We found a storm
+
+        // Note that speed is in knots.  If we were stuffing it into
+        // "wx_speed" we'd have to convert it to MPH.
+        if (strcmp(speed,"   ") != 0 && strcmp(speed,"...") != 0) {
+            xastir_snprintf(p_station->speed,
+                sizeof(p_station->speed),
+                "%s",
+                speed);
+        }
+        else {
+            p_station->speed[0] = '\0';
+        }
+
+        if (strcmp(course,"   ") != 0 && strcmp(course,"...") != 0)
+            xastir_snprintf(p_station->course,
+                sizeof(p_station->course),
+                "%s",
+                course);
+        else
+            p_station->course[0] = '\0';
+        weather = p_station->weather_data;
+        p2++;   // Skip the description text, "/TS", "/HC", "/TD", "/TY", "/ST", or "/SC"
+
+        // Extract the sustained wind speed in knots
+        if(extract_weather_item(p2,'/',3,weather->wx_speed))
+            // Convert from knots to MPH
+            xastir_snprintf(weather->wx_speed,
+                sizeof(weather->wx_speed),
+                "%0.1f",
+                (float)(atoi(weather->wx_speed)) * 1.1508);
+
+//fprintf(stderr,"%s\n",data);
+
+        // Extract gust speed in knots
+        if (extract_weather_item(p2,'^',3,weather->wx_gust)) // gust (peak wind speed in knots)
+            // Convert from knots to MPH
+            xastir_snprintf(weather->wx_gust,
+                sizeof(weather->wx_gust),
+                "%0.1f",
+                (float)(atoi(weather->wx_gust)) * 1.1508);
+
+//fprintf(stderr,"%s\n",data);
+
+        // Pressure is already in millibars/hPa.  No conversion
+        // needed.
+        if (extract_weather_item(p2,'/',4,weather->wx_baro))  // barometric pressure (1/10 mbar / 1/10 hPascal)
+            xastir_snprintf(weather->wx_baro,
+                sizeof(weather->wx_baro),
+                "%0.1f",
+                (float)(atoi(weather->wx_baro)));
+
+//fprintf(stderr,"%s\n",data);
+
+        (void)extract_weather_item(p2,'>',3,weather->wx_hurricane_radius); // Nautical miles
+
+//fprintf(stderr,"%s\n",data);
+
+        (void)extract_weather_item(p2,'&',3,weather->wx_trop_storm_radius); // Nautical miles
+
+//fprintf(stderr,"%s\n",data);
+
+        (void)extract_weather_item(p2,'%',3,weather->wx_whole_gale_radius); // Nautical miles
+
+//fprintf(stderr,"%s\n",data);
+
+        // Create a timestamp from the current time
+        xastir_snprintf(weather->wx_time,
+            sizeof(weather->wx_time),
+            "%s",
+            get_time(time_data));
+
+        // Set the timestamp in the weather record so that we can
+        // decide whether or not to "ghost" the weather data later.
+        weather->wx_sec_time=sec_now();
+    }
+    return(ok);
+}
+
+
+
+/*
+ *  Free station memory for one entry
+ */
+void delete_station_memory(AprsDataRow *p_del) {
+    if (p_del == NULL)
+        return;
+    
+    remove_name(p_del);
+    remove_time(p_del);
+    free(p_del);
+    station_count--;
+}
+
+/*
+ *  Create new uninitialized element in station list
+ *  and insert it before p_name after p_time entries.
+ *
+ *  Returns NULL if malloc error.
+ */
+/*@null@*/ AprsDataRow *insert_new_station(AprsDataRow *p_name, AprsDataRow *p_time) {
+    AprsDataRow *p_new;
+
+
+    p_new = (AprsDataRow *)malloc(sizeof(AprsDataRow));
+
+    if (p_new != NULL) {                // we really got the memory
+        p_new->call_sign[0] = '\0';     // just to be sure
+        p_new->n_next = NULL;
+        p_new->n_prev = NULL;
+        p_new->t_newer = NULL;
+        p_new->t_older = NULL;
+        insert_name(p_new,p_name);      // insert element into name ordered list
+        insert_time(p_new,p_time);      // insert element into time ordered list
+    }
+    else {  // p_new == NULL
+        fprintf(stderr,"ERROR: we got no memory for station storage\n");
+    }
+
+
+    return(p_new);                      // return pointer to new element
+}
+
+
+// Station storage is done in a double-linked list. In fact there are two such
+// pointer structures, one for sorting by name and one for sorting by time.
+// We store both the pointers to the next and to the previous elements.  DK7IN
+
+/*
+ *  Setup station storage structure
+ */
+void init_station_data(void) {
+
+    station_count = 0;                  // empty station list
+    n_first = NULL;                     // pointer to next element in name sorted list
+    n_last  = NULL;                     // pointer to previous element in name sorted list
+    t_oldest = NULL;                     // pointer to oldest element in time sorted list
+    t_newest  = NULL;                     // pointer to newest element in time sorted list
+    last_sec = sec_now();               // check value for detecting changed seconds in time
+    next_time_sn = 0;                   // serial number for unique time index
+//    current_trail_color = 0x00;         // first trail color used will be 0x01
+    last_station_remove = sec_now();    // last time we checked for stations to remove
+    
+}
+
+
+/*
+ *  Initialize station data
+ */        
+void init_station(AprsDataRow *p_station) {
+    // the list pointers should already be set
+       
+    p_station->oldest_trackpoint  = NULL;         // no trail
+    p_station->newest_trackpoint  = NULL;         // no trail
+    p_station->trail_color        = 0;
+    p_station->weather_data       = NULL;         // no weather
+    p_station->coord_lat          = 0l;           //  90°N  \ undefined
+    p_station->coord_lon          = 0l;           // 180°W  / position
+    p_station->pos_amb            = 0;            // No ambiguity
+    p_station->error_ellipse_radius = 600;        // In cm, default 6 meters
+    p_station->lat_precision      = 60;           // In 100ths of seconds latitude (60 = 0.01 minutes)
+    p_station->lon_precision      = 60;           // In 100ths of seconds longitude (60 = 0.01 minutes)
+    p_station->call_sign[0]       = '\0';         // ?????
+    p_station->tactical_call_sign = NULL;
+    p_station->sec_heard          = 0;
+    p_station->time_sn            = 0;
+    p_station->flag               = 0;            // set all flags to inactive
+    p_station->object_retransmit  = -1;           // transmit forever
+    p_station->last_transmit_time = sec_now();    // Used for object/item decaying algorithm
+    p_station->transmit_time_increment = 0;       // Used in data_add()
+//    p_station->last_modified_time = 0;            // Used for object/item dead-reckoning
+    p_station->record_type        = '\0';
+    p_station->heard_via_tnc_port = 0;
+    p_station->heard_via_tnc_last_time = 0;
+    p_station->last_port_heard    = 0;
+    p_station->num_packets        = 0;
+    p_station->aprs_symbol.aprs_type = '\0';
+    p_station->aprs_symbol.aprs_symbol = '\0';
+    p_station->aprs_symbol.special_overlay = '\0';
+    p_station->aprs_symbol.area_object.type           = AREA_NONE;
+    p_station->aprs_symbol.area_object.color          = AREA_GRAY_LO;
+    p_station->aprs_symbol.area_object.sqrt_lat_off   = 0;
+    p_station->aprs_symbol.area_object.sqrt_lon_off   = 0;
+    p_station->aprs_symbol.area_object.corridor_width = 0;
+//    p_station->station_time_type  = '\0';
+    p_station->origin[0]          = '\0';        // no object
+    p_station->packet_time[0]     = '\0';
+    p_station->node_path_ptr      = NULL;
+    p_station->pos_time[0]        = '\0';
+//    p_station->altitude_time[0]   = '\0';
+    p_station->altitude[0]        = '\0';
+//    p_station->speed_time[0]      = '\0';
+    p_station->speed[0]           = '\0';
+    p_station->course[0]          = '\0';
+    p_station->bearing[0]         = '\0';
+    p_station->NRQ[0]             = '\0';
+    p_station->power_gain[0]      = '\0';
+    p_station->signal_gain[0]     = '\0';
+    p_station->signpost[0]        = '\0';
+    p_station->probability_min[0] = '\0';
+    p_station->probability_max[0] = '\0';
+//    p_station->station_time[0]    = '\0';
+    p_station->sats_visible[0]    = '\0';
+    p_station->status_data        = NULL;
+    p_station->comment_data       = NULL;
+    p_station->df_color           = -1;
+    
+    // Show that there are no other points associated with this
+    // station. We could also zero all the entries of the 
+    // multipoints[][] array, but nobody should be looking there
+    // unless this is non-zero.
+    // KG4NBB
+    
+    p_station->num_multipoints = 0;
+    p_station->multipoint_data = NULL;
+}
+
+
+
+void init_tactical_hash(int clobber) {
+
+    // make sure we don't leak
+    if (tactical_hash) {
+        if (clobber) {
+            hashtable_destroy(tactical_hash, 1);
+            tactical_hash=create_hashtable(TACTICAL_HASH_SIZE,
+                tactical_hash_from_key,
+                tactical_keys_equal);
+        }
+    }
+    else {
+        tactical_hash=create_hashtable(TACTICAL_HASH_SIZE,
+            tactical_hash_from_key,
+            tactical_keys_equal);
+    }
+}
+
+
+
+
+char *get_tactical_from_hash(char *callsign) {
+    char *result;
+
+    if (callsign == NULL || *callsign == '\0') {
+        fprintf(stderr,"Empty callsign passed to get_tactical_from_hash()\n");
+        return(NULL);
+    }
+
+    if (!tactical_hash) {  // no table to search
+//fprintf(stderr,"Creating hash table\n");
+        init_tactical_hash(1); // so create one
+        return NULL;
+    }
+
+//    fprintf(stderr,"   searching for %s...",callsign);
+
+    result=hashtable_search(tactical_hash,callsign);
+
+        if (result) {
+//            fprintf(stderr,"\t\tFound it, %s, len=%d, %s\n",
+//                callsign,
+//                strlen(callsign),
+//                result);
+        } else {
+//            fprintf(stderr,"\t\tNot found, %s, len=%d\n",
+//                callsign,
+//                strlen(callsign));
+        }
+
+    return (result);
+}
+
+
+// Distance calculation (Great Circle) using the Haversine formula
+// (2-parameter arctan version), which gives better accuracy than
+// the "Law of Cosines" for short distances.  It should be
+// equivalent to the "Law of Cosines for Spherical Trigonometry" for
+// longer distances.  Haversine is a great-circle calculation.
+//
+//
+// Inputs:  lat1/long1/lat2/long2 in radians (double)
+//
+// Outputs: Distance in meters between them (double)
+//
+double calc_distance_haversine_radian(double lat1, double lon1, double lat2, double lon2) {
+    double dlon, dlat;
+    double a, c, d;
+    double R = EARTH_RADIUS_METERS;
+    #define square(x) (x)*(x)
+
+
+    dlon = lon2 - lon1;
+    dlat = lat2 - lat1;
+    a = square((sin(dlat/2.0))) + cos(lat1) * cos(lat2) * square((sin(dlon/2.0)));
+    c = 2.0 * atan2(sqrt(a), sqrt(1.0-a));
+    d = R * c;
+
+    return(d);
+}
+
+
+
+
+
+
+/*
+ *  Insert existing element into name ordered list before p_name.
+ *  If p_name is NULL then we add it to the end instead.
+ */
+void insert_name(AprsDataRow *p_new, AprsDataRow *p_name) {
+
+    // Set up pointer to next record (or NULL), sorted by name
+    p_new->n_next = p_name;
+
+    if (p_name == NULL) {       // Add to end of list
+
+        p_new->n_prev = n_last;
+
+        if (n_last == NULL)     // If we have an empty list
+            n_first = p_new;    // Add it to the head of the list
+
+        else    // List wasn't empty, add to the end of the list.
+            n_last->n_next = p_new;
+
+        n_last = p_new;
+    }
+
+    else {  // Insert new record ahead of p_name record
+
+        p_new->n_prev = p_name->n_prev;
+
+        if (p_name->n_prev == NULL)     // add to begin of list
+            n_first = p_new;
+        else
+            p_name->n_prev->n_next = p_new;
+
+        p_name->n_prev = p_new;
+    }
+}
+
+
+
+// Update all of the pointers so that they accurately reflect the
+// current state of the station database.
+//
+// NOTE:  This part of the code could be made smarter so that the
+// pointers are updated whenever they are found to be out of whack,
+// instead of zeroing all of them and starting from scratch each
+// time.  Alternate:  Follow the current pointer if non-NULL then go
+// up/down the list to find the current switchover point between
+// letters.
+//
+// Better:  Tie into the station insert function.  If a new letter
+// is inserted, or a new station at the beginning of a letter group,
+// run this function to keep things up to date.  That way we won't
+// have to traverse in both directions to find a callsign in the
+// search_station_name() function.
+//
+// If hash_key_in is -1, we need to redo all of the hash keys.  If
+// it is between 0 and 16383, then we need to redo just that one
+// hash key.  The 2nd parameter is either NULL for a removed record,
+// or a pointer to a new station record in the case of an addition.
+//
+void station_shortcuts_update_function(int hash_key_in, AprsDataRow *p_rem) {
+    int ii;
+    AprsDataRow *ptr;
+    int prev_hash_key = 0x0000;
+    int hash_key;
+
+
+// I just changed the function so that we can pass in the hash_key
+// that we wish to update:  We should be able to speed things up by
+// updating one hash key instead of all 16384 pointers.
+
+    if ( (hash_key_in != -1)
+            && (hash_key_in >= 0)
+            && (hash_key_in < 16384) ) {
+
+        // We're adding/changing a hash key entry
+        station_shortcuts[hash_key_in] = p_rem;
+//fprintf(stderr,"%i ",hash_key_in);
+    }
+    else {  // We're removing a hash key entry.
+
+        // Clear and rebuild the entire hash table.
+
+//??????????????????????????????????????????????????
+    // Clear all of the pointers before we begin????
+//??????????????????????????????????????????????????
+        for (ii = 0; ii < 16384; ii++) {
+            station_shortcuts[ii] = NULL;
+        }
+
+        ptr = n_first;  // Start of list
+
+
+        // Loop through entire list, writing the pointer into the
+        // station_shortcuts array whenever a new character is
+        // encountered.  Do this until the end of the array or the end
+        // of the list.
+        //
+        while ( (ptr != NULL) && (prev_hash_key < 16384) ) {
+
+            // We create the hash key out of the lower 7 bits of the
+            // first two characters, creating a 14-bit key (1 of 16384)
+            //
+            hash_key = (int)((ptr->call_sign[0] & 0x7f) << 7);
+            hash_key = hash_key | (int)(ptr->call_sign[1] & 0x7f);
+
+            if (hash_key > prev_hash_key) {
+
+                // We found the next hash_key.  Store the pointer at the
+                // correct location.
+                if (hash_key < 16384) {
+                    station_shortcuts[hash_key] = ptr;
+
+                }
+                prev_hash_key = hash_key;
+            }
+            ptr = ptr->n_next;
+        }
+
+    }
+
+}
+
+
+
+
+/*
+ *  Create new initialized element for call in station list
+ *  and insert it before p_name after p_time entries.
+ *
+ *  Returns NULL if mallc error.
+ */
+/*@null@*/ AprsDataRow *add_new_station(AprsDataRow *p_name, AprsDataRow *p_time, char *call) {
+    AprsDataRow *p_new;
+    int hash_key;   // We use a 14-bit hash key
+    char *tactical_call;
+
+
+
+    if (call[0] == '\0') {
+        // Do nothing.  No update needed.  Callsign is empty.
+        return(NULL);
+    }
+
+        
+    if(_aprs_show_new_station_alert)
+    {
+           const gchar *msg = g_strdup_printf("New station: %s", call); 
+           hildon_banner_show_information(_window, NULL, msg);
+    }
+
+    p_new = insert_new_station(p_name,p_time);  // allocate memory
+
+    if (p_new == NULL) {
+
+        // Couldn't allocate space for the station
+        return(NULL);
+    }
+
+    init_station(p_new);                    // initialize new station record
+    
+    
+    xastir_snprintf(p_new->call_sign,
+        sizeof(p_new->call_sign),
+        "%s",
+        call);
+    station_count++;
+
+
+
+    // Do some quick checks to see if we just inserted a new hash
+    // key or inserted at the beginning of a hash key (making the
+    // old pointer incorrect).  If so, update our pointers to match.
+
+    // We create the hash key out of the lower 7 bits of the first
+    // two characters, creating a 14-bit key (1 of 16384)
+    //
+    hash_key = (int)((call[0] & 0x7f) << 7);
+    hash_key = hash_key | (int)(call[1] & 0x7f);
+
+    if (station_shortcuts[hash_key] == NULL) {
+        // New hash key entry point found.  Fill in the pointer.
+        station_shortcuts_update_function(hash_key, p_new);
+    }
+    else if (p_new->n_prev == NULL) {
+        // We just inserted at the beginning of the list.  Assume
+        // that we inserted at the beginning of our hash_key
+        // segment.
+        station_shortcuts_update_function(hash_key, p_new);
+    }
+    else {
+        // Check whether either of the first two chars of the new
+        // callsign and the previous callsign are different.  If so,
+        // we need to update the hash table entry for our new record
+        // 'cuz we're at the start of a new hash table entry.
+        if (p_new->n_prev->call_sign[0] != call[0]
+                || p_new->n_prev->call_sign[1] != call[1]) {
+
+            station_shortcuts_update_function(hash_key, p_new);
+        }
+    }
+
+    // Check whether we have a tactical call to assign to this
+    // station in our tactical hash table.
+
+    tactical_call = get_tactical_from_hash(call);
+
+
+    // If tactical call found and not blank
+    if (tactical_call && tactical_call[0] != '\0') {
+
+        // Malloc some memory to hold it in the station record.
+        p_new->tactical_call_sign = (char *)malloc(MAX_TACTICAL_CALL+1);
+        CHECKMALLOC(p_new->tactical_call_sign);
+
+        snprintf(p_new->tactical_call_sign,
+            MAX_TACTICAL_CALL+1,
+            "%s",
+            tactical_call);
+
+        //if (tactical_call[0] == '\0')
+        //    fprintf(stderr,"Blank tactical call\n");
+    }
+
+    return(p_new);                      // return pointer to new element
+}
+
+
+
+/*
+ *  Add data from APRS information field to station database
+ *  Returns a 1 if successful
+ */
+int data_add(gint type,
+             gchar *call_sign,
+             gchar *path,
+             gchar *data,
+             TAprsPort port,
+             gchar *origin,
+             gint third_party,
+             gint station_is_mine,
+             gint object_is_mine) {
+
+
+    AprsDataRow *p_station;
+    AprsDataRow *p_time;
+    char call[MAX_CALLSIGN+1];
+    char new_station;
+    long last_lat, last_lon;
+    char last_alt[MAX_ALTITUDE];
+    char last_speed[MAX_SPEED+1];
+    char last_course[MAX_COURSE+1];
+    time_t last_stn_sec;
+    short last_flag;
+    char temp_data[40]; // short term string storage, MAX_CALLSIGN, ...
+//    long l_lat, l_lon;
+    double distance;
+//    char station_id[600];
+    int found_pos;
+    float value;
+    AprsWeatherRow *weather;
+    int moving;
+    int changed_pos;
+    int screen_update;
+    int ok, store;
+    int ok_to_display;
+    int compr_pos;
+    char *p = NULL; // KC2ELS - used for WIDEn-N
+    int direct = 0;
+    int object_is_mine_previous = 0;
+    int new_origin_is_mine = 0;
+    int num_digits = 0; // Number of digits after decimal point in NMEA string
+    
+// TODO update based on time
+//    static time_t lastScreenUpdate;
+
+
+    // call and path had been validated before
+    // Check "data" against the max APRS length, and dump the packet if too long.
+    if ( (data != NULL) && (strlen(data) > MAX_INFO_FIELD_SIZE) ) {   
+       // Overly long packet.  Throw it away.
+
+        return(0);  // Not an ok packet
+    }
+
+    // Check for some reasonable string in call_sign parameter
+    if (call_sign == NULL || strlen(call_sign) == 0) {
+
+        return(0);
+    }
+
+    if (origin && is_my_call(origin, 1)) {
+        new_origin_is_mine++;   // The new object/item is owned by me
+    }
+
+    weather = NULL; // only to make the compiler happy...
+    found_pos = 1;
+    snprintf(call,
+        sizeof(call),
+        "%s",
+        call_sign);
+    p_station = NULL;
+    new_station = (char)FALSE;                          // to make the compiler happy...
+    last_lat = 0L;
+    last_lon = 0L;
+    last_stn_sec = sec_now();
+    last_alt[0]    = '\0';
+    last_speed[0]  = '\0';
+    last_course[0] = '\0';
+    last_flag      = 0;
+    ok = 0;
+    store = 0;
+    p_time = NULL;                                      // add to end of time sorted list (newest)
+    compr_pos = 0;
+
+
+    if (search_station_name(&p_station,call,1)) 
+    {       // If we found the station in our list
+
+//     fprintf(stderr, "DEBUG: Station:,Found,:%s:\n", call);
+       
+        // Check whether it's already a locally-owned object/item
+        if (is_my_object_item(p_station)) {
+
+            // We don't want to re-order it in the time-ordered list
+            // so that it'll expire from the queue normally.  Don't
+            // call "move_station_time()" here.
+
+            // We need an exception later in this function for the
+            // case where we've moved an object/item (by how much?).
+            // We need to update the time in this case so that it'll
+            // expire later (in fact it could already be expired
+            // when we move it).  We should be able to move expired
+            // objects/items to make them active again.  Perhaps
+            // some other method as well?
+
+            new_station = (char)FALSE;
+            object_is_mine_previous++;
+        }
+        else {
+            move_station_time(p_station,p_time);        // update time, change position in time sorted list
+            new_station = (char)FALSE;                  // we have seen this one before
+        }
+
+        if (is_my_station(p_station)) {
+            station_is_mine++; // Station/object/item is owned/controlled by me
+        }
+    }
+    else 
+    {
+//     fprintf(stderr, "DEBUG: Station:,New,:%s:\n", call);
+
+        p_station = add_new_station(p_station,p_time,call);     // create storage
+        new_station = (char)TRUE;                       // for new station
+
+    }
+    
+
+    if (p_station != NULL) 
+    {
+
+        last_lat = p_station->coord_lat;                // remember last position
+        last_lon = p_station->coord_lon;
+        last_stn_sec = p_station->sec_heard;
+        snprintf(last_alt,
+            sizeof(last_alt),
+            "%s",
+            p_station->altitude);
+        snprintf(last_speed,
+            sizeof(last_speed),
+            "%s",
+            p_station->speed);
+        snprintf(last_course,
+            sizeof(last_course),    
+            "%s",
+            p_station->course);
+        last_flag = p_station->flag;
+
+        // Wipe out old data so that it doesn't hang around forever
+        p_station->altitude[0] = '\0';
+        p_station->speed[0] = '\0';
+        p_station->course[0] = '\0';
+
+        ok = 1;                         // succeed as default
+
+
+        switch (type) {
+
+            case (APRS_MICE):           // Mic-E format
+            case (APRS_FIXED):          // '!'
+            case (APRS_MSGCAP):         // '='
+
+                if (!extract_position(p_station,&data,type)) {          // uncompressed lat/lon
+                    compr_pos = 1;
+                    if (!extract_comp_position(p_station,&data,type))   // compressed lat/lon
+                        ok = 0;
+                    else
+                        p_station->pos_amb = 0; // No ambiguity in compressed posits
+                }
+
+                if (ok) {
+
+                    // Create a timestamp from the current time
+                    snprintf(p_station->pos_time,
+                        sizeof(p_station->pos_time),
+                        "%s", get_time(temp_data));
+                    (void)extract_storm(p_station,data,compr_pos);
+                    (void)extract_weather(p_station,data,compr_pos);    // look for weather data first
+                    process_data_extension(p_station,data,type);        // PHG, speed, etc.
+                    process_info_field(p_station,data,type);            // altitude
+
+                    if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
+                        extract_multipoints(p_station, data, type, 1);
+                    add_comment(p_station,data);
+
+                    p_station->record_type = NORMAL_APRS;
+                    if (type == APRS_MSGCAP)
+                        p_station->flag |= ST_MSGCAP;           // set "message capable" flag
+                    else
+                        p_station->flag &= (~ST_MSGCAP);        // clear "message capable" flag
+
+                    // Assign a non-default value for the error
+                    // ellipse?
+                    if (type == APRS_MICE || !compr_pos) {
+                        p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+                        p_station->lat_precision = 60;
+                        p_station->lon_precision = 60;
+                    }
+                    else {
+                        p_station->error_ellipse_radius = 600; // Default of 6m
+                        p_station->lat_precision = 6;
+                        p_station->lon_precision = 6;
+                    }
+
+                }
+                break;
+
+/*
+            case (APRS_DOWN):           // '/'
+                ok = extract_time(p_station, data, type);               // we need a time
+                if (ok) {
+                    if (!extract_position(p_station,&data,type)) {      // uncompressed lat/lon
+                        compr_pos = 1;
+                        if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
+                            ok = 0;
+                        else
+                            p_station->pos_amb = 0; // No ambiguity in compressed posits
+                    }
+                }
+
+                if (ok) {
+
+                    // Create a timestamp from the current time
+                    xastir_snprintf(p_station->pos_time,
+                        sizeof(p_station->pos_time),
+                        "%s",
+                        get_time(temp_data));
+                    process_data_extension(p_station,data,type);        // PHG, speed, etc.
+                    process_info_field(p_station,data,type);            // altitude
+
+                    if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
+                        extract_multipoints(p_station, data, type, 1);
+                    add_comment(p_station,data);
+
+                    p_station->record_type = DOWN_APRS;
+                    p_station->flag &= (~ST_MSGCAP);            // clear "message capable" flag
+
+                    // Assign a non-default value for the error
+                    // ellipse?
+                    if (!compr_pos) {
+                        p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+                        p_station->lat_precision = 60;
+                        p_station->lon_precision = 60;
+                    }
+                    else {
+                        p_station->error_ellipse_radius = 600; // Default of 6m
+                        p_station->lat_precision = 6;
+                        p_station->lon_precision = 6;
+                    }
+                }
+                break;
+*/
+
+            case (APRS_DF):             // '@'
+            case (APRS_MOBILE):         // '@'
+
+                ok = extract_time(p_station, data, type);               // we need a time
+                if (ok) {
+                    if (!extract_position(p_station,&data,type)) {      // uncompressed lat/lon
+                        compr_pos = 1;
+                        if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
+                            ok = 0;
+                        else
+                            p_station->pos_amb = 0; // No ambiguity in compressed posits
+                    }
+                }
+                if (ok) {
+
+                    process_data_extension(p_station,data,type);        // PHG, speed, etc.
+                    process_info_field(p_station,data,type);            // altitude
+
+                    if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
+                        extract_multipoints(p_station, data, type, 1);
+                    add_comment(p_station,data);
+
+                    if(type == APRS_MOBILE)
+                        p_station->record_type = MOBILE_APRS;
+                    else
+                        p_station->record_type = DF_APRS;
+                    //@ stations have messaging per spec
+                    p_station->flag |= (ST_MSGCAP);            // set "message capable" flag
+
+                    // Assign a non-default value for the error
+                    // ellipse?
+                    if (!compr_pos) {
+                        p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+                        p_station->lat_precision = 60;
+                        p_station->lon_precision = 60;
+                    }
+                    else {
+                        p_station->error_ellipse_radius = 600; // Default of 6m
+                        p_station->lat_precision = 6;
+                        p_station->lon_precision = 6;
+                    }
+                }
+                break;
+
+            case (APRS_GRID):
+
+                ok = extract_position(p_station, &data, type);
+                if (ok) { 
+
+
+                    process_info_field(p_station,data,type);            // altitude
+
+                    if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
+                        extract_multipoints(p_station, data, type, 1);
+                    add_comment(p_station,data);
+
+                    // Assign a non-default value for the error
+                    // ellipse?
+//                    p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+
+// WE7U
+// This needs to change based on the number of grid letters/digits specified
+//                    p_station->lat_precision = 60;
+//                    p_station->lon_precision = 60;
+                }
+                else {
+                }
+                break;
+
+            case (STATION_CALL_DATA):
+
+                p_station->record_type = NORMAL_APRS;
+                found_pos = 0;
+                break;
+
+            case (APRS_STATUS):         // '>' Status Reports     [APRS Reference, chapter 16]
+
+                (void)extract_time(p_station, data, type);              // we need a time
+                // todo: could contain Maidenhead or beam heading+power
+
+                if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
+                    extract_multipoints(p_station, data, type, 1);
+                add_status(p_station,data);
+
+                p_station->flag |= (ST_STATUS);                         // set "Status" flag
+                p_station->record_type = NORMAL_APRS;                   // ???
+                found_pos = 0;
+                break;
+
+            case (OTHER_DATA):          // Other Packets          [APRS Reference, chapter 19]
+
+                // non-APRS beacons, treated as status reports until we get a real one
+
+                if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
+                    extract_multipoints(p_station, data, type, 1);
+                if ((p_station->flag & (~ST_STATUS)) == 0) {            // only store if no status yet
+
+                    add_status(p_station,data);
+
+                    p_station->record_type = NORMAL_APRS;               // ???
+                }
+                found_pos = 0;
+                break;
+
+            case (APRS_OBJECT):
+
+                // If old match is a killed Object (owner doesn't
+                // matter), new one is an active Object and owned by
+                // us, remove the old record and create a new one
+                // for storing this Object.  Do the same for Items
+                // in the next section below.
+                //
+                // The easiest implementation might be to remove the
+                // old record and then call this routine again with
+                // the same parameters, which will cause a brand-new
+                // record to be created.
+                //
+                // The new record we're processing is an active
+                // object, as data_add() won't be called on a killed
+                // object.
+                //
+//                if ( is_my_call(origin,1)  // If new Object is owned by me (including SSID)
+                if (new_origin_is_mine
+                        && !(p_station->flag & ST_ACTIVE)
+                        && (p_station->flag & ST_OBJECT) ) {  // Old record was a killed Object
+                    remove_name(p_station);  // Remove old killed Object
+//                    redo_list = (int)TRUE;
+                    return( data_add(type, call_sign, path, data, port, origin, third_party, 1, 1) );
+                }
+                ok = extract_time(p_station, data, type);               // we need a time
+                if (ok) {
+                    if (!extract_position(p_station,&data,type)) {      // uncompressed lat/lon
+                        compr_pos = 1;
+                        if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
+                            ok = 0;
+                        else
+                            p_station->pos_amb = 0; // No ambiguity in compressed posits
+                    }
+                }
+                p_station->flag |= ST_OBJECT;                           // Set "Object" flag
+                if (ok) {
+
+                    // If object was owned by me but another station
+                    // is transmitting it now, write entries into
+                    // the object.log file showing that we don't own
+                    // this object anymore.
+
+//                    if ( (is_my_call(p_station->origin,1))  // If station was owned by me (including SSID)
+//                            && (!is_my_call(origin,1)) ) {  // But isn't now
+  // TODO                  
+/*
+                       if (is_my_object_item(p_station)    // If station was owned by me (include SSID)
+                            && !new_origin_is_mine) {   // But isn't now
+
+                        disown_object_item(call_sign, origin);
+
+                    }
+*/
+                    // If station is owned by me (including SSID)
+                    // but it's a new object/item
+//                    if ( (is_my_call(p_station->origin,1))
+                    if (new_origin_is_mine
+                            && (p_station->transmit_time_increment == 0) ) {
+                        // This will get us transmitting this object
+                        // on the decaying algorithm schedule.
+                        // We've transmitted it once if we've just
+                        // gotten to this code.
+                        p_station->transmit_time_increment = OBJECT_CHECK_RATE;
+//fprintf(stderr,"data_add(): Setting transmit_time_increment to %d\n", OBJECT_CHECK_RATE);
+                    }
+                    // Create a timestamp from the current time
+                    xastir_snprintf(p_station->pos_time,
+                        sizeof(p_station->pos_time),
+                        "%s",
+                        get_time(temp_data));
+
+                    xastir_snprintf(p_station->origin,
+                        sizeof(p_station->origin),
+                        "%s",
+                        origin);                   // define it as object
+                    (void)extract_storm(p_station,data,compr_pos);
+                    (void)extract_weather(p_station,data,compr_pos);    // look for wx info
+                    process_data_extension(p_station,data,type);        // PHG, speed, etc.
+                    process_info_field(p_station,data,type);            // altitude
+
+                    if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
+                        extract_multipoints(p_station, data, type, 0);
+                    add_comment(p_station,data);
+
+                    // the last char always was missing...
+                    //p_station->comments[ strlen(p_station->comments) - 1 ] = '\0';  // Wipe out '\n'
+                    // moved that to decode_ax25_line
+                    // and don't added a '\n' in interface.c
+                    p_station->record_type = NORMAL_APRS;
+                    p_station->flag &= (~ST_MSGCAP);            // clear "message capable" flag
+
+                    // Assign a non-default value for the error
+                    // ellipse?
+                    if (!compr_pos) {
+                        p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+                        p_station->lat_precision = 60;
+                        p_station->lon_precision = 60;
+                    }
+                    else {
+                        p_station->error_ellipse_radius = 600; // Default of 6m
+                        p_station->lat_precision = 6;
+                        p_station->lon_precision = 6;
+                    }
+                }
+                break;
+
+            case (APRS_ITEM):
+
+                // If old match is a killed Item (owner doesn't
+                // matter), new one is an active Item and owned by
+                // us, remove the old record and create a new one
+                // for storing this Item.  Do the same for Objects
+                // in the previous section above.
+                //
+                // The easiest implementation might be to remove the
+                // old record and then call this routine again with
+                // the same parameters, which will cause a brand-new
+                // record to be created.
+                //
+                // The new record we're processing is an active
+                // Item, as data_add() won't be called on a killed
+                // Item.
+                //
+//                if ( is_my_call(origin,1)  // If new Item is owned by me (including SSID)
+                if (new_origin_is_mine
+                        && !(p_station->flag & ST_ACTIVE)
+                        && (p_station->flag & ST_ITEM) ) {  // Old record was a killed Item
+                    remove_name(p_station);  // Remove old killed Item
+//                    redo_list = (int)TRUE;
+                    return( data_add(type, call_sign, path, data, port, origin, third_party, 1, 1) );
+                }
+                if (!extract_position(p_station,&data,type)) {          // uncompressed lat/lon
+                    compr_pos = 1;
+                    if (!extract_comp_position(p_station,&data,type))   // compressed lat/lon
+                        ok = 0;
+                    else
+                        p_station->pos_amb = 0; // No ambiguity in compressed posits
+                }
+                p_station->flag |= ST_ITEM;                             // Set "Item" flag
+                if (ok) {
+
+                    // If item was owned by me but another station
+                    // is transmitting it now, write entries into
+                    // the object.log file showing that we don't own
+                    // this item anymore.
+// TODO
+/*
+//                    if ( (is_my_call(p_station->origin,1))  // If station was owned by me (including SSID)
+//                            && (!is_my_call(origin,1)) ) {  // But isn't now
+                    if (is_my_object_item(p_station)
+                            && !new_origin_is_mine) {  // But isn't now
+
+                        disown_object_item(call_sign,origin);
+                    }
+ */
+
+                    // If station is owned by me (including SSID)
+                    // but it's a new object/item
+//                    if ( (is_my_call(p_station->origin,1))
+                    if (is_my_object_item(p_station)
+                            && (p_station->transmit_time_increment == 0) ) {
+                        // This will get us transmitting this object
+                        // on the decaying algorithm schedule.
+                        // We've transmitted it once if we've just
+                        // gotten to this code.
+                        p_station->transmit_time_increment = OBJECT_CHECK_RATE;
+//fprintf(stderr,"data_add(): Setting transmit_time_increment to %d\n", OBJECT_CHECK_RATE);
+                    }
+                    // Create a timestamp from the current time
+                    xastir_snprintf(p_station->pos_time,
+                        sizeof(p_station->pos_time),
+                        "%s",
+                        get_time(temp_data));
+                    xastir_snprintf(p_station->origin,
+                        sizeof(p_station->origin),
+                        "%s",
+                        origin);                   // define it as item
+                    (void)extract_storm(p_station,data,compr_pos);
+                    (void)extract_weather(p_station,data,compr_pos);    // look for wx info
+                    process_data_extension(p_station,data,type);        // PHG, speed, etc.
+                    process_info_field(p_station,data,type);            // altitude
+
+                    if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
+                        extract_multipoints(p_station, data, type, 0);
+                    add_comment(p_station,data);
+
+                    // the last char always was missing...
+                    //p_station->comments[ strlen(p_station->comments) - 1 ] = '\0';  // Wipe out '\n'
+                    // moved that to decode_ax25_line
+                    // and don't added a '\n' in interface.c
+                    p_station->record_type = NORMAL_APRS;
+                    p_station->flag &= (~ST_MSGCAP);            // clear "message capable" flag
+
+                    // Assign a non-default value for the error
+                    // ellipse?
+                    if (!compr_pos) {
+                        p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+                        p_station->lat_precision = 60;
+                        p_station->lon_precision = 60;
+                    }
+                    else {
+                        p_station->error_ellipse_radius = 600; // Default of 6m
+                        p_station->lat_precision = 6;
+                        p_station->lon_precision = 6;
+                    }
+                }
+                break;
+
+            case (APRS_WX1):    // weather in '@' or '/' packet
+
+                ok = extract_time(p_station, data, type);               // we need a time
+                if (ok) {
+                    if (!extract_position(p_station,&data,type)) {      // uncompressed lat/lon
+                        compr_pos = 1;
+                        if (!extract_comp_position(p_station,&data,type)) // compressed lat/lon
+                            ok = 0;
+                        else
+                            p_station->pos_amb = 0; // No ambiguity in compressed posits
+                    }
+                }
+                if (ok) {
+
+                    (void)extract_storm(p_station,data,compr_pos);
+                    (void)extract_weather(p_station,data,compr_pos);
+                    p_station->record_type = (char)APRS_WX1;
+
+                    process_info_field(p_station,data,type);            // altitude
+
+                    if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
+                        extract_multipoints(p_station, data, type, 1);
+                    add_comment(p_station,data);
+
+                    // Assign a non-default value for the error
+                    // ellipse?
+                    if (!compr_pos) {
+                        p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+                        p_station->lat_precision = 60;
+                        p_station->lon_precision = 60;
+                    }
+                    else {
+                        p_station->error_ellipse_radius = 600; // Default of 6m
+                        p_station->lat_precision = 6;
+                        p_station->lon_precision = 6;
+                    }
+                }
+                break;
+
+            case (APRS_WX2):            // '_'
+
+                ok = extract_time(p_station, data, type);               // we need a time
+                if (ok) {
+                    (void)extract_storm(p_station,data,compr_pos);
+                    (void)extract_weather(p_station,data,0);            // look for weather data first
+                    p_station->record_type = (char)APRS_WX2;
+                    found_pos = 0;
+
+                    process_info_field(p_station,data,type);            // altitude
+
+                    if ( (p_station->coord_lat > 0) && (p_station->coord_lon > 0) )
+                        extract_multipoints(p_station, data, type, 1);
+                }
+                break;
+
+            case (APRS_WX4):            // '#'          Peet Bros U-II (km/h)
+            case (APRS_WX6):            // '*'          Peet Bros U-II (mph)
+            case (APRS_WX3):            // '!'          Peet Bros Ultimeter 2000 (data logging mode)
+            case (APRS_WX5):            // '$ULTW'      Peet Bros Ultimeter 2000 (packet mode)
+
+               
+                if (get_weather_record(p_station)) {    // get existing or create new weather record
+                    weather = p_station->weather_data;
+                    if (type == APRS_WX3)   // Peet Bros Ultimeter 2000 data logging mode
+                    {
+                        decode_U2000_L (1,(unsigned char *)data,weather);
+                    }
+                    else if (type == APRS_WX5) // Peet Bros Ultimeter 2000 packet mode
+                        decode_U2000_P(1,(unsigned char *)data,weather);
+                    else    // Peet Bros Ultimeter-II
+                        decode_Peet_Bros(1,(unsigned char *)data,weather,type);
+                    p_station->record_type = (char)type;
+                    // Create a timestamp from the current time
+                    xastir_snprintf(weather->wx_time,
+                        sizeof(weather->wx_time),
+                        "%s",
+                        get_time(temp_data));
+                    weather->wx_sec_time = sec_now();
+                    found_pos = 0;
+                }
+
+                break;
+
+
+// GPRMC, digits after decimal point
+// ---------------------------------
+// 2  = 25.5 meter error ellipse
+// 3  =  6.0 meter error ellipse
+// 4+ =  6.0 meter error ellipse
+
+
+            case (GPS_RMC):             // $GPRMC
+
+// WE7U
+// Change this function to return HDOP and the number of characters
+// after the decimal point.
+                ok = extract_RMC(p_station,data,call_sign,path,&num_digits);
+
+                if (ok) {
+                    // Assign a non-default value for the error
+                    // ellipse?
+//
+// WE7U
+// Degrade based on the precision provided in the sentence.  If only
+// 2 digits after decimal point, give it 2550 as a radius (25.5m).
+// Best (smallest) circle should be 600 as we have no augmentation
+// flag to check here for anything better.
+//
+                    switch (num_digits) {
+
+                        case 0:
+                            p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+                            p_station->lat_precision = 6000;
+                            p_station->lon_precision = 6000;
+                            break;
+
+                        case 1:
+                            p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+                            p_station->lat_precision = 600;
+                            p_station->lon_precision = 600;
+                            break;
+
+                        case 2:
+                            p_station->error_ellipse_radius = 600; // Default of 6m
+                            p_station->lat_precision = 60;
+                            p_station->lon_precision = 60;
+                            break;
+
+                        case 3:
+                            p_station->error_ellipse_radius = 600; // Default of 6m
+                            p_station->lat_precision = 6;
+                            p_station->lon_precision = 6;
+                            break;
+
+                        case 4:
+                        case 5:
+                        case 6:
+                        case 7:
+                            p_station->error_ellipse_radius = 600; // Default of 6m
+                            p_station->lat_precision = 0;
+                            p_station->lon_precision = 0;
+                            break;
+
+                        default:
+                            p_station->error_ellipse_radius = 600; // Default of 6m
+                            p_station->lat_precision = 60;
+                            p_station->lon_precision = 60;
+                            break;
+                    }
+                }
+                break;
+
+
+// GPGGA, digits after decimal point, w/o augmentation
+// ---------------------------------------------------
+// 2   = 25.5 meter error ellipse
+// 3   =  6.0 meter error ellipse unless HDOP>4, then 10.0 meters
+// 4+  =  6.0 meter error ellipse unless HDOP>4, then 10.0 meters
+// 
+// 
+// GPGGA, digits after decimal point, w/augmentation
+// --------------------------------------------------
+// 2   = 25.5 meter error ellipse
+// 3   =  2.5 meter error ellipse unless HDOP>4, then 10.0 meters
+// 4+  =  0.6 meter error ellipse unless HDOP>4, then 10.0 meters
+
+
+            case (GPS_GGA):             // $GPGGA
+
+// WE7U
+// Change this function to return HDOP and the number of characters
+// after the decimal point.
+                ok = extract_GGA(p_station,data,call_sign,path,&num_digits);
+
+                if (ok) {
+                    // Assign a non-default value for the error
+                    // ellipse?
+//
+// WE7U
+// Degrade based on the precision provided in the sentence.  If only
+// 2 digits after decimal point, give it 2550 as a radius (25.5m).
+// 3 digits: 6m w/o augmentation unless HDOP >4 = 10m, 2.5m w/augmentation.
+// 4+ digits: 6m w/o augmentation unless HDOP >4 = 10m, 0.6m w/augmentation.
+//
+                     switch (num_digits) {
+
+                        case 0:
+                            p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+                            p_station->lat_precision = 6000;
+                            p_station->lon_precision = 6000;
+                            break;
+
+                        case 1:
+                            p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+                            p_station->lat_precision = 600;
+                            p_station->lon_precision = 600;
+                            break;
+
+                        case 2:
+                            p_station->error_ellipse_radius = 600; // Default of 6m
+                            p_station->lat_precision = 60;
+                            p_station->lon_precision = 60;
+                            break;
+
+                        case 3:
+                            p_station->error_ellipse_radius = 600; // Default of 6m
+                            p_station->lat_precision = 6;
+                            p_station->lon_precision = 6;
+                            break;
+
+                        case 4:
+                        case 5:
+                        case 6:
+                        case 7:
+                            p_station->error_ellipse_radius = 600; // Default of 6m
+                            p_station->lat_precision = 0;
+                            p_station->lon_precision = 0;
+                            break;
+
+                        default:
+                            p_station->error_ellipse_radius = 600; // Default of 6m
+                            p_station->lat_precision = 60;
+                            p_station->lon_precision = 60;
+                            break;
+                    }
+                }
+                break;
+
+
+// GPGLL, digits after decimal point
+// ---------------------------------
+// 2  = 25.5 meter error ellipse
+// 3  =  6.0 meter error ellipse
+// 4+ =  6.0 meter error ellipse
+
+
+            case (GPS_GLL):             // $GPGLL
+                ok = extract_GLL(p_station,data,call_sign,path,&num_digits);
+
+                if (ok) {
+                    // Assign a non-default value for the error
+                    // ellipse?
+//
+// WE7U
+// Degrade based on the precision provided in the sentence.  If only
+// 2 digits after decimal point, give it 2550 as a radius, otherwise
+// give it 600.
+//
+                     switch (num_digits) {
+
+                        case 0:
+                            p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+                            p_station->lat_precision = 6000;
+                            p_station->lon_precision = 6000;
+                            break;
+                        case 1:
+                            p_station->error_ellipse_radius = 2550; // 25.5m, or about 60ft resolution
+                            p_station->lat_precision = 600;
+                            p_station->lon_precision = 600;
+                            break;
+
+                        case 2:
+                            p_station->error_ellipse_radius = 600; // Default of 6m
+                            p_station->lat_precision = 60;
+                            p_station->lon_precision = 60;
+                            break;
+
+                        case 3:
+                            p_station->error_ellipse_radius = 600; // Default of 6m
+                            p_station->lat_precision = 6;
+                            p_station->lon_precision = 6;
+                            break;
+
+                        case 4:
+                        case 5:
+                        case 6:
+                        case 7:
+                            p_station->error_ellipse_radius = 600; // Default of 6m
+                            p_station->lat_precision = 0;
+                            p_station->lon_precision = 0;
+                            break;
+
+                        default:
+                            p_station->error_ellipse_radius = 600; // Default of 6m
+                            p_station->lat_precision = 60;
+                            p_station->lon_precision = 60;
+                            break;
+                    }
+                }
+                break;
+
+            default:
+
+                fprintf(stderr,"ERROR: UNKNOWN TYPE in data_add\n");
+                ok = 0;
+                break;
+        }
+
+        // Left this one in, just in case.  Perhaps somebody might
+        // attach a multipoint string onto the end of a packet we
+        // might not expect.  For this case we need to check whether
+        // we have multipoints first, as we don't want to erase the
+        // work we might have done with a previous call to
+        // extract_multipoints().
+        if (ok && (p_station->coord_lat > 0)
+                && (p_station->coord_lon > 0)
+                && (p_station->num_multipoints == 0) ) {  // No multipoints found yet
+
+            extract_multipoints(p_station, data, type, 0);
+        }
+    }
+
+    if (!ok) {  // non-APRS beacon, treat it as Other Packet   [APRS Reference, chapter 19]
+
+        // GPRMC etc. without a position is here too, but it should not be stored as status!
+
+        // store it as status report until we get a real one
+        if ((p_station->flag & (~ST_STATUS)) == 0) {         // only store it if no status yet
+            add_status(p_station,data-1);
+
+            p_station->record_type = NORMAL_APRS;               // ???
+        }
+        ok = 1;            
+        found_pos = 0;
+    }
+    
+    curr_sec = sec_now();
+    if (ok) {
+
+        // data packet is valid
+        // announce own echo, we soon discard that packet...
+//        if (!new_station && is_my_call(p_station->call_sign,1) // Check SSID as well
+        if (!new_station
+                && is_my_station(p_station) // Check SSID as well
+                && strchr(path,'*') != NULL) {
+            upd_echo(path);   // store digi that echoes my signal...
+            statusline("Echo from digipeater",0);   // Echo from digipeater
+
+        }
+        // check if data is just a secondary echo from another digi
+        if ((last_flag & ST_VIATNC) == 0
+                || (curr_sec - last_stn_sec) > 15
+                || p_station->coord_lon != last_lon 
+                || p_station->coord_lat != last_lat)
+            store = 1;                     // don't store secondary echos
+    }
+    
+    if (!ok && new_station)
+        delete_station_memory(p_station);       // remove unused record
+    if (store) {
+        // we now have valid data to store into database
+        // make time index unique by adding a serial number
+
+        if (station_is_mine) {
+            // This station/object/item is owned by me.  Set the
+            // flag which shows that we own/control this
+            // station/object/item.  We use this flag later in lieu
+            // of the is_my_call() function in order to speed things
+            // up.
+            //
+            p_station->flag |= ST_MYSTATION;
+        }
+        
+        // Check whether it's a locally-owned object/item
+        if ( object_is_mine
+                || (   new_origin_is_mine
+                    && (p_station->flag & ST_ACTIVE)
+                    && (p_station->flag & ST_OBJECT) ) ) {
+
+            p_station->flag |= ST_MYOBJITEM;
+
+            // Do nothing else.  We don't want to update the
+            // last-heard time so that it'll expire from the queue
+            // normally, unless it is a new object/item.
+            //
+            if (new_station) {
+                p_station->sec_heard = curr_sec;
+            }
+
+            // We need an exception later in this function for the
+            // case where we've moved an object/item (by how much?).
+            // We need to update the time in this case so that it'll
+            // expire later (in fact it could already be expired
+            // when we move it).  We should be able to move expired
+            // objects/items to make them active again.  Perhaps
+            // some other method as well?.
+        }
+        else {
+            // Reset the "my object" flag
+            p_station->flag &= ~ST_MYOBJITEM;
+
+            p_station->sec_heard = curr_sec;    // Give it a new timestamp
+        }
+        
+        if (curr_sec != last_sec) {     // todo: if old time calculate time_sn from database
+            last_sec = curr_sec;
+            next_time_sn = 0;           // restart time serial number
+        }
+
+        p_station->time_sn = next_time_sn++;            // todo: warning if serial number too high
+        if (port == APRS_PORT_TTY) {                     // heard via TNC
+            if (!third_party) { // Not a third-party packet
+                p_station->flag |= ST_VIATNC;               // set "via TNC" flag
+                p_station->heard_via_tnc_last_time = curr_sec;
+                p_station->heard_via_tnc_port = port;
+            }
+            else {  // Third-party packet
+                // Leave the previous setting of "flag" alone.
+                // Specifically do NOT set the ST_VIATNC flag if it
+                // was a third-party packet.
+            }
+        }
+        else {  // heard other than TNC
+            if (new_station) {  // new station
+                p_station->flag &= (~ST_VIATNC);  // clear "via TNC" flag
+//fprintf(stderr,"New_station: Cleared ST_VIATNC flag: %s\n", p_station->call_sign);
+                p_station->heard_via_tnc_last_time = 0l;
+            }
+        }
+        p_station->last_port_heard = port;
+        
+        // Create a timestamp from the current time
+        snprintf(p_station->packet_time, sizeof(p_station->packet_time),
+            "%s", get_time(temp_data)); // get_time returns value in temp_data
+        
+        p_station->flag |= ST_ACTIVE;
+        if (third_party)
+            p_station->flag |= ST_3RD_PT;               // set "third party" flag
+        else
+            p_station->flag &= (~ST_3RD_PT);            // clear "third party" flag
+        if (origin != NULL && strcmp(origin,"INET") == 0)  // special treatment for inet names
+            snprintf(p_station->origin,
+                sizeof(p_station->origin),
+                "%s",
+                origin);           // to keep them separated from calls
+        if (origin != NULL && strcmp(origin,"INET-NWS") == 0)  // special treatment for NWS
+            snprintf(p_station->origin,
+                sizeof(p_station->origin),
+                "%s",
+                origin);           // to keep them separated from calls
+        if (origin == NULL || origin[0] == '\0')        // normal call
+            p_station->origin[0] = '\0';                // undefine possible former object with same name
+
+
+        //--------------------------------------------------------------------
+
+        // KC2ELS
+        // Okay, here are the standards for ST_DIRECT:
+        // 1.  The packet must have been received via TNC.
+        // 2.  The packet must not have any * flags.
+        // 3.  If present, the first WIDEn-N (or TRACEn-N) must have n=N.
+        // A station retains the ST_DIRECT setting.  If
+        // "st_direct_timeout" seconds have passed since we set
+        // that bit then APRSD queries and displays based on the
+        // ST_DIRECT bit will skip that station.
+
+// In order to make this scheme work for stations that straddle both
+// RF and INET, we need to make sure that node_path_ptr doesn't get
+// overwritten with an INET path if there's an RF path already in
+// there and it has been less than st_direct_timeout seconds since
+// the station was last heard on RF.
+
+        if ((port == APRS_PORT_TTY)             // Heard via TNC
+                && !third_party                // Not a 3RD-Party packet
+                && path != NULL                // Path is not NULL
+                && strchr(path,'*') == NULL) { // No asterisk found
+
+            // Look for WIDE or TRACE
+            if ((((p = strstr(path,"WIDE")) != NULL) 
+                    && (p+=4)) || 
+                    (((p = strstr(path,"TRACE")) != NULL) 
+                    && (p+=5))) {
+
+                // Look for n=N on WIDEn-N/TRACEn-N digi field
+                if ((*p != '\0') && isdigit((int)*p)) {
+                    if ((*(p+1) != '\0') && (*(p+1) == '-')) {
+                        if ((*(p+2) != '\0') && g_ascii_isdigit(*(p+2))) {
+                            if (*(p) == *(p+2)) {
+                                direct = 1;
+                            }
+                            else {
+                                direct = 0;
+                            }
+                        }
+                        else {
+                            direct = 0;
+                        }
+                    }
+                    else {
+                        direct = 0;
+                    }
+                }
+                else {
+                    direct = 1;
+                }
+            }
+            else {
+                direct = 1;
+            }
+        }
+        else {
+            direct = 0;
+        }
+        
+        
+        if (direct == 1) {
+            // This packet was heard direct.  Set the ST_DIRECT bit
+            // and save the timestamp away.
+            p_station->direct_heard = curr_sec;
+            p_station->flag |= (ST_DIRECT);
+        }
+        else {
+            // This packet was NOT heard direct.  Check whether we
+            // need to expire the ST_DIRECT bit.  A lot of fixed
+            // stations transmit every 30 minutes.  One hour gives
+            // us time to receive a direct packet from them among
+            // all the digipeated packets.
+
+            if ((p_station->flag & ST_DIRECT) != 0 &&
+                    curr_sec > (p_station->direct_heard + st_direct_timeout)) {
+                p_station->flag &= (~ST_DIRECT);
+            }
+        }
+
+        // If heard on TNC, overwrite node_path_ptr if any of these
+        // conditions are met:
+        //     *) direct == 1 (packet was heard direct)
+        //     *) ST_DIRECT flag == 0 (packet hasn't been heard
+        //     direct recently)
+        //     *) ST_DIRECT is set, st_direct_timeout has expired
+        //     (packet hasn't been heard direct recently)
+        //
+        // These rules will allow us to keep directly heard paths
+        // saved for at least an hour (st_direct_timeout), and not
+        // get overwritten with digipeated paths during that time.
+        //
+        if ((port == APRS_PORT_TTY)  // Heard via TNC
+                && !third_party     // Not a 3RD-Party packet
+                && path != NULL) {  // Path is not NULL
+
+            // Heard on TNC interface and not third party.  Check
+            // the other conditions listed in the comments above to
+            // decide whether we should overwrite the node_path_ptr
+            // variable.
+            //
+            if ( direct   // This packet was heard direct
+                 || (p_station->flag & ST_DIRECT) == 0  // Not heard direct lately
+                 || ( (p_station->flag & ST_DIRECT) != 0 // Not heard direct lately
+                      && (curr_sec > (p_station->direct_heard+st_direct_timeout) ) ) ) {
+
+                // Free any old path we might have
+                if (p_station->node_path_ptr != NULL)
+                    free(p_station->node_path_ptr);
+                // Malloc and store the new path
+                p_station->node_path_ptr = (char *)malloc(strlen(path) + 1);
+                CHECKMALLOC(p_station->node_path_ptr);
+
+                substr(p_station->node_path_ptr,path,strlen(path));
+            }
+        }
+        // If a 3rd-party packet heard on TNC, overwrite
+        // node_path_ptr only if heard_via_tnc_last_time is older
+        // than one hour (zero counts as well!), plus clear the
+        // ST_DIRECT and ST_VIATNC bits in this case.  This makes us
+        // keep the RF path around for at least one hour after the
+        // station is heard.
+        //
+        else if ((port == APRS_PORT_TTY)  // Heard via TNC
+                && third_party      // It's a 3RD-Party packet
+                && path != NULL) {  // Path is not NULL
+
+            // 3rd-party packet heard on TNC interface.  Check if
+            // heard_via_tnc_last_time is older than an hour.  If
+            // so, overwrite the path and clear a few bits to show
+            // that it has timed out on RF and we're now receiving
+            // that station from an igate.
+            //
+            if (curr_sec > (p_station->heard_via_tnc_last_time + 60*60)) {
+
+                // Yep, more than one hour old or is a zero,
+                // overwrite the node_path_ptr variable with the new
+                // one.  We're only hearing this station on INET
+                // now.
+
+                // Free any old path we might have
+                if (p_station->node_path_ptr != NULL)
+                    free(p_station->node_path_ptr);
+                // Malloc and store the new path
+                p_station->node_path_ptr = (char *)malloc(strlen(path) + 1);
+                CHECKMALLOC(p_station->node_path_ptr);
+
+                substr(p_station->node_path_ptr,path,strlen(path));
+
+                // Clear the ST_VIATNC bit
+                p_station->flag &= ~ST_VIATNC;
+            }
+
+            // If direct_heard is over an hour old, clear the
+            // ST_DIRECT flag.  We're only hearing this station on
+            // INET now.
+            // 
+            if (curr_sec > (p_station->direct_heard + st_direct_timeout)) {
+
+                // Yep, more than one hour old or is a zero, clear
+                // the ST_DIRECT flag.
+                p_station->flag &= ~ST_DIRECT;
+            }
+        }
+        // If heard on INET then overwrite node_path_ptr only if
+        // heard_via_tnc_last_time is older than one hour (zero
+        // counts as well!), plus clear the ST_DIRECT and ST_VIATNC
+        // bits in this case.  This makes us keep the RF path around
+        // for at least one hour after the station is heard.
+        //
+        else if (port != APRS_PORT_TTY  // From an INET interface
+                && !third_party        // Not a 3RD-Party packet
+                && path != NULL) {     // Path is not NULL
+               
+            // Heard on INET interface.  Check if
+            // heard_via_tnc_last_time is older than an hour.  If
+            // so, overwrite the path and clear a few bits to show
+            // that it has timed out on RF and we're now receiving
+            // that station from the INET feeds.
+            //
+            if (curr_sec > (p_station->heard_via_tnc_last_time + 60*60)) {
+
+                // Yep, more than one hour old or is a zero,
+                // overwrite the node_path_ptr variable with the new
+                // one.  We're only hearing this station on INET
+                // now.
+
+                // Free any old path we might have
+                if (p_station->node_path_ptr != NULL)
+                    free(p_station->node_path_ptr);
+                // Malloc and store the new path
+                p_station->node_path_ptr = (char *)malloc(strlen(path) + 1);
+                CHECKMALLOC(p_station->node_path_ptr);
+
+                substr(p_station->node_path_ptr,path,strlen(path));
+
+                // Clear the ST_VIATNC bit
+                p_station->flag &= ~ST_VIATNC;
+
+            }
+            
+            // If direct_heard is over an hour old, clear the
+            // ST_DIRECT flag.  We're only hearing this station on
+            // INET now.
+            // 
+            if (curr_sec > (p_station->direct_heard + st_direct_timeout)) {
+
+                // Yep, more than one hour old or is a zero, clear
+                // the ST_DIRECT flag.
+                p_station->flag &= ~ST_DIRECT;
+            }
+        }
+        //---------------------------------------------------------------------
+
+        p_station->num_packets += 1;
+
+
+        screen_update = 0;
+        if (new_station) {
+            if (strlen(p_station->speed) > 0 && atof(p_station->speed) > 0) {
+                p_station->flag |= (ST_MOVING); // it has a speed, so it's moving
+                moving = 1;
+            }
+
+            if (p_station->coord_lat != 0 && p_station->coord_lon != 0) {   // discard undef positions from screen
+                if (!altnet || is_altnet(p_station) ) {
+
+                    screen_update = 1;
+                    // TODO - check needed
+                }
+            
+            }
+        }
+        else {        // we had seen this station before...
+               
+               if (found_pos && position_defined(p_station->coord_lat,p_station->coord_lon,1)) { // ignore undefined and 0N/0E
+                if (p_station->newest_trackpoint != NULL) {
+                    moving = 1;                         // it's moving if it has a trail
+                }
+                else {
+                    if (strlen(p_station->speed) > 0 && atof(p_station->speed) > 0) {
+                        moving = 1;                     // declare it moving, if it has a speed
+                    }
+                    else {
+                        // Here's where we detect movement
+                        if (position_defined(last_lat,last_lon,1)
+                                && (p_station->coord_lat != last_lat || p_station->coord_lon != last_lon)) {
+                            moving = 1;                 // it's moving if it has changed the position
+                        }
+                        else {
+                            moving = 0;
+                        }
+                    }
+                }
+                changed_pos = 0;
+                if (moving == 1) {                      
+                    p_station->flag |= (ST_MOVING);
+                    // we have a moving station, process trails
+                    if (atoi(p_station->speed) < TRAIL_MAX_SPEED) {     // reject high speed data (undef gives 0)
+                        // we now may already have the 2nd position, so store the old one first
+                        if (p_station->newest_trackpoint == NULL) {
+                            if (position_defined(last_lat,last_lon,1)) {  // ignore undefined and 0N/0E
+                                (void)store_trail_point(p_station,
+                                                        last_lon,
+                                                        last_lat,
+                                                        last_stn_sec,
+                                                        last_alt,
+                                                        last_speed,
+                                                        last_course,
+                                                        last_flag);
+                            }
+                        }
+                        //if (   p_station->coord_lon != last_lon
+                        //    || p_station->coord_lat != last_lat ) {
+                        // we don't store redundant points (may change this
+                                                // later ?)
+                                                //
+                        // There are often echoes delayed 15 minutes
+                        // or so it looks ugly on the trail, so I
+                        // want to discard them This also discards
+                        // immediate echoes. Duplicates back in time
+                        // up to TRAIL_ECHO_TIME minutes are
+                        // discarded.
+                        //
+                        if (!is_trailpoint_echo(p_station)) {
+                            (void)store_trail_point(p_station,
+                                                    p_station->coord_lon,
+                                                    p_station->coord_lat,
+                                                    p_station->sec_heard,
+                                                    p_station->altitude,
+                                                    p_station->speed,
+                                                    p_station->course,
+                                                    p_station->flag);
+                            changed_pos = 1;
+
+                            // Check whether it's a locally-owned object/item
+                            if (object_is_mine) {
+
+                                // Update time, change position in
+                                // time-sorted list to change
+                                // expiration time.
+                                move_station_time(p_station,p_time);
+
+                                // Give it a new timestamp
+                                p_station->sec_heard = curr_sec;
+                                //fprintf(stderr,"Updating last heard time\n");
+                            }
+                        }
+                    }
+
+//                    if (track_station_on == 1)          // maybe we are tracking a station
+//                        track_station(da,tracking_station_call,p_station);
+                } // moving...
+
+                // now do the drawing to the screen
+                ok_to_display = !altnet || is_altnet(p_station); // Optimization step, needed twice below.
+// TODO
+//                screen_update = 0;
+//                if (changed_pos == 1 && Display_.trail && ((p_station->flag & ST_INVIEW) != 0)) {
+//                    if (ok_to_display) {
+//                        draw_trail(da,p_station,1);         // update trail
+//                        screen_update = 1;
+//                    }
+
+                }
+
+                if (changed_pos == 1 || !position_defined(last_lat,last_lon,0)) {
+                    if (ok_to_display) {
+
+                        screen_update = 1; // TODO -check needed
+                    }
+
+                }
+            } // defined position
+        }
+    
+        if (screen_update) {
+            if (p_station->last_port_heard == APRS_PORT_TTY) {   // Data from local TNC
+
+                redraw_on_new_data = 2; // Update all symbols NOW!
+            }
+          else {
+                redraw_on_new_data = 0; // Update each 60 secs
+          }
+     }
+
+
+    if(ok)
+    {
+
+        plot_aprs_station( p_station, TRUE );
+
+    }
+
+    return(ok);
+}   // End of data_add() function
+
+
+int is_xnum_or_dash(char *data, int max){
+    int i;
+    int ok;
+
+    ok=1;
+    for(i=0; i<max;i++)
+        if(!(isxdigit((int)data[i]) || data[i]=='-')) {
+            ok=0;
+            break;
+        }
+
+    return(ok);
+}
+
+
+
+/*
+ *  Decode Mic-E encoded data
+ */
+int decode_Mic_E(char *call_sign,char *path,char *info,int port,int third_party) {
+    int  ii;
+    int  offset;
+    unsigned char s_b1;
+    unsigned char s_b2;
+    unsigned char s_b3;
+    unsigned char s_b4;
+    unsigned char s_b5;
+    unsigned char s_b6;
+    unsigned char s_b7;
+    int  north,west,long_offset;
+    int  d,m,h;
+    char temp[MAX_LINE_SIZE+1];     // Note: Must be big in case we get long concatenated packets
+    char new_info[MAX_LINE_SIZE+1]; // Note: Must be big in case we get long concatenated packets
+    int  course;
+    int  speed;
+    int  msg1,msg2,msg3,msg;
+    int  info_size;
+    long alt;
+    int  msgtyp;
+    char rig_type[10];
+    int ok;
+        
+    // MIC-E Data Format   [APRS Reference, chapter 10]
+
+    // todo:  error check
+    //        drop wrong positions from receive errors...
+    //        drop 0N/0E position (p.25)
+    
+    /* First 7 bytes of info[] contains the APRS data type ID,    */
+    /* longitude, speed, course.                    */
+    /* The 6-byte destination field of path[] contains latitude,    */
+    /* N/S bit, E/W bit, longitude offset, message code.        */
+    /*
+
+    MIC-E Destination Field Format:
+    -------------------------------
+    Ar1DDDD0 Br1DDDD0 Cr1MMMM0 Nr1MMMM0 Lr1HHHH0 Wr1HHHH0 CrrSSID0
+    D = Latitude Degrees.
+    M = Latitude Minutes.
+    H = Latitude Hundredths of Minutes.
+    ABC = Message bits, complemented.
+    N = N/S latitude bit (N=1).
+    W = E/W longitude bit (W=1).
+    L = 100's of longitude degrees (L=1 means add 100 degrees to longitude
+    in the Info field).
+    C = Command/Response flag (see AX.25 specification).
+    r = reserved for future use (currently 0).
+    
+    */
+    /****************************************************************************
+    * I still don't handle:                                                     *
+    *    Custom message bits                                                    *
+    *    SSID special routing                                                   *
+    *    Beta versions of the MIC-E (which use a slightly different format).    *
+    *                                                                           *
+    * DK7IN : lat/long with custom msg works, altitude/course/speed works       *
+    *****************************************************************************/
+
+
+    // Note that the first MIC-E character was not passed to us, so we're
+    // starting just past it.
+    // Check for valid symbol table character.  Should be '/' or '\'
+    // or 0-9, A-Z.
+    //
+    if (        info[7] == '/'                          // Primary table
+            ||  info[7] == '\\'                         // Alternate table
+            || (info[7] >= '0' && info[7] <= '9')       // Overlay char
+            || (info[7] >= 'A' && info[7] <= 'Z') ) {   // Overlay char
+
+        // We're good, keep going
+
+    }
+    else { // Symbol table or overlay char incorrect
+
+        if (info[6] == '/' || info[6] == '\\') {    // Found it back one char in string
+            // Don't print out the full info string here because it
+            // can contain unprintable characters.  In fact, we
+            // should check the chars we do print out to make sure
+            // they're printable, else print a space char.
+        }
+
+        return(1);  // No good, not MIC-E format or corrupted packet.  Return 1
+                    // so that it won't get added to the database at all.
+    }
+
+    // Check for valid symbol.  Should be between '!' and '~' only.
+    if (info[6] < '!' || info[6] > '~') {
+
+        return(1);  // No good, not MIC-E format or corrupted packet.  Return 1
+                    // so that it won't get added to the database at all.
+    }
+
+    // Check for minimum MIC-E size.
+    if (strlen(info) < 8) {
+
+        return(1);  // No good, not MIC-E format or corrupted packet.  Return 1
+                    // so that it won't get added to the database at all.
+    }
+
+    // Check for 8-bit characters in the first eight slots.  Not
+    // allowed per Mic-E chapter of the spec.
+    for (ii = 0; ii < 8; ii++) {
+        if ((unsigned char)info[ii] > 0x7f) {
+            // 8-bit data was found in the lat/long/course/speed
+            // portion.  Bad packet.  Drop it.
+//fprintf(stderr, "%s: 8-bits found in Mic-E packet initial portion. Dropping it.\n", call_sign);
+            return(1);
+        }
+    }
+
+    // Check whether we have more data.  If flag character is 0x1d
+    // (8-bit telemetry flag) then don't do the 8-bit check below.
+    if (strlen(info) > 8) {
+
+        // Check for the 8-bit telemetry flag
+        if ((unsigned char)info[8] == 0x1d) {
+            // 8-bit telemetry found, skip the check loop below
+        }
+        else {  // 8-bit telemetry flag was not found.  Check that
+                // we only have 7-bit characters through the rest of
+                // the packet.
+
+            for (ii = 8; ii < (int)strlen(info); ii++) {
+
+                if ((unsigned char)info[ii] > 0x7f) {
+                    // 8-bit data was found.  Bad packet.  Drop it.
+//fprintf(stderr, "%s: 8-bits found in Mic-E packet final portion (not 8-bit telemetry). Dropping it.\n", call_sign);
+                    return(1);
+                }
+            }
+        }
+    }
+
+    //fprintf(stderr,"Path1:%s\n",path);
+
+    msg1 = (int)( ((unsigned char)path[0] & 0x40) >>4 );
+    msg2 = (int)( ((unsigned char)path[1] & 0x40) >>5 );
+    msg3 = (int)( ((unsigned char)path[2] & 0x40) >>6 );
+    msg = msg1 | msg2 | msg3;   // We now have the complemented message number in one variable
+    msg = msg ^ 0x07;           // And this is now the normal message number
+    msgtyp = 0;                 // DK7IN: Std message, I have to add custom msg decoding
+
+    //fprintf(stderr,"Msg: %d\n",msg);
+
+    /* Snag the latitude from the destination field, Assume TAPR-2 */
+    /* DK7IN: latitude now works with custom message */
+    s_b1 = (unsigned char)( (path[0] & 0x0f) + (char)0x2f );
+    //fprintf(stderr,"path0:%c\ts_b1:%c\n",path[0],s_b1);
+    if (path[0] & 0x10)     // A-J
+        s_b1 += (unsigned char)1;
+
+    if (s_b1 > (unsigned char)0x39)        // K,L,Z
+        s_b1 = (unsigned char)0x20;
+    //fprintf(stderr,"s_b1:%c\n",s_b1);
+    s_b2 = (unsigned char)( (path[1] & 0x0f) + (char)0x2f );
+    //fprintf(stderr,"path1:%c\ts_b2:%c\n",path[1],s_b2);
+    if (path[1] & 0x10)     // A-J
+        s_b2 += (unsigned char)1;
+
+    if (s_b2 > (unsigned char)0x39)        // K,L,Z
+        s_b2 = (unsigned char)0x20;
+    //fprintf(stderr,"s_b2:%c\n",s_b2);
+    s_b3 = (unsigned char)( (path[2] & (char)0x0f) + (char)0x2f );
+    if (path[2] & 0x10)     // A-J
+        s_b3 += (unsigned char)1;
+
+    if (s_b3 > (unsigned char)0x39)        // K,L,Z
+        s_b3 = (unsigned char)0x20;
+    s_b4 = (unsigned char)( (path[3] & 0x0f) + (char)0x30 );
+    if (s_b4 > (unsigned char)0x39)        // L,Z
+        s_b4 = (unsigned char)0x20;
+
+    s_b5 = (unsigned char)( (path[4] & 0x0f) + (char)0x30 );
+    if (s_b5 > (unsigned char)0x39)        // L,Z
+        s_b5 = (unsigned char)0x20;
+
+    s_b6 = (unsigned char)( (path[5] & 0x0f) + (char)0x30 );
+    //fprintf(stderr,"path5:%c\ts_b6:%c\n",path[5],s_b6);
+    if (s_b6 > (unsigned char)0x39)        // L,Z
+        s_b6 = (unsigned char)0x20;
+    //fprintf(stderr,"s_b6:%c\n",s_b6);
+    s_b7 =  (unsigned char)path[6];        // SSID, not used here
+    //fprintf(stderr,"path6:%c\ts_b7:%c\n",path[6],s_b7);
+    //fprintf(stderr,"\n");
+
+    // Special tests for 'L' due to position ambiguity deviances in
+    // the APRS spec table.  'L' has the 0x40 bit set, but they
+    // chose in the spec to have that represent position ambiguity
+    // _without_ the North/West/Long Offset bit being set.  Yuk!
+    // Please also note that the tapr.org Mic-E document (not the
+    // APRS spec) has the state of the bit wrong in columns 2 and 3
+    // of their table.  Reverse them.
+    if (path[3] == 'L')
+        north = 0;
+    else 
+        north = (int)((path[3] & 0x40) == (char)0x40);  // N/S Lat Indicator
+
+    if (path[4] == 'L')
+        long_offset = 0;
+    else
+        long_offset = (int)((path[4] & 0x40) == (char)0x40);  // Longitude Offset
+
+    if (path[5] == 'L')
+        west = 0;
+    else
+        west = (int)((path[5] & 0x40) == (char)0x40);  // W/E Long Indicator
+
+    //fprintf(stderr,"north:%c->%d\tlat:%c->%d\twest:%c->%d\n",path[3],north,path[4],long_offset,path[5],west);
+
+    /* Put the latitude string into the temp variable */
+    snprintf(temp, sizeof(temp), "%c%c%c%c.%c%c%c%c",s_b1,s_b2,s_b3,s_b4,s_b5,s_b6,
+            (north ? 'N': 'S'), info[7]);   // info[7] = symbol table
+
+    /* Compute degrees longitude */
+    snprintf(new_info, sizeof(new_info), "%s", temp);
+    d = (int) info[0]-28;
+
+    if (long_offset)
+        d += 100;
+
+    if ((180<=d)&&(d<=189))  // ??
+        d -= 80;
+
+    if ((190<=d)&&(d<=199))  // ??
+        d -= 190;
+
+    /* Compute minutes longitude */
+    m = (int) info[1]-28;
+    if (m>=60)
+        m -= 60;
+
+    /* Compute hundredths of minutes longitude */
+    h = (int) info[2]-28;
+    /* Add the longitude string into the temp variable */
+    xastir_snprintf(temp, sizeof(temp), "%03d%02d.%02d%c%c",d,m,h,(west ? 'W': 'E'), info[6]);
+    strncat(new_info,
+        temp,
+        sizeof(new_info) - strlen(new_info));
+
+    /* Compute speed in knots */
+    speed = (int)( ( info[3] - (char)28 ) * (char)10 );
+    speed += ( (int)( (info[4] - (char)28) / (char)10) );
+    if (speed >= 800)
+        speed -= 800;       // in knots
+
+    /* Compute course */
+    course = (int)( ( ( (info[4] - (char)28) % 10) * (char)100) + (info[5] - (char)28) );
+    if (course >= 400)
+        course -= 400;
+
+    /*  ???
+        fprintf(stderr,"info[4]-28 mod 10 - 4 = %d\n",( ( (int)info[4]) - 28) % 10 - 4);
+        fprintf(stderr,"info[5]-28 = %d\n", ( (int)info[5]) - 28 );
+    */
+    xastir_snprintf(temp, sizeof(temp), "%03d/%03d",course,speed);
+    strncat(new_info,
+        temp,
+        sizeof(new_info) - strlen(new_info));
+    offset = 8;   // start of rest of info
+
+    /* search for rig type in Mic-E data */
+    rig_type[0] = '\0';
+    if (info[offset] != '\0' && (info[offset] == '>' || info[offset] == ']')) {
+        /* detected type code:     > TH-D7    ] TM-D700 */
+        if (info[offset] == '>')
+            xastir_snprintf(rig_type,
+                sizeof(rig_type),
+                " TH-D7");
+        else
+            xastir_snprintf(rig_type,
+                sizeof(rig_type),
+                " TM-D700");
+
+        offset++;
+    }
+
+    info_size = (int)strlen(info);
+    /* search for compressed altitude in Mic-E data */  // {
+    if (info_size >= offset+4 && info[offset+3] == '}') {  // {
+        /* detected altitude  ___} */
+        alt = ((((long)info[offset] - (long)33) * (long)91 +(long)info[offset+1] - (long)33) * (long)91
+                    + (long)info[offset+2] - (long)33) - 10000;  // altitude in meters
+        alt /= 0.3048;                                // altitude in feet, as in normal APRS
+
+        //32808 is -10000 meters, or 10 km (deepest ocean), which is as low as a MIC-E
+        //packet may go.  Upper limit is mostly a guess.
+        if ( (alt > 500000) || (alt < -32809) ) {  // Altitude is whacko.  Skip it.
+            offset += 4;
+        }
+        else {  // Altitude is ok
+            xastir_snprintf(temp, sizeof(temp), " /A=%06ld",alt);
+            offset += 4;
+            strncat(new_info,
+                temp,
+                sizeof(new_info) - strlen(new_info));
+        }
+    }
+
+    /* start of comment */
+    if (strlen(rig_type) > 0) {
+        xastir_snprintf(temp, sizeof(temp), "%s",rig_type);
+        strncat(new_info,
+            temp,
+            sizeof(new_info) - strlen(new_info));
+    }
+
+    strncat(new_info,
+        " Mic-E ",
+        sizeof(new_info) - strlen(new_info));
+    if (msgtyp == 0) {
+        switch (msg) {
+            case 1:
+                strncat(new_info,
+                    "Enroute",
+                    sizeof(new_info) - strlen(new_info));
+                break;
+
+            case 2:
+                strncat(new_info,
+                    "In Service",
+                    sizeof(new_info) - strlen(new_info));
+                break;
+
+            case 3:
+                strncat(new_info,
+                    "Returning",
+                    sizeof(new_info) - strlen(new_info));
+                break;
+
+            case 4:
+                strncat(new_info,
+                    "Committed",
+                    sizeof(new_info) - strlen(new_info));
+                break;
+
+            case 5:
+                strncat(new_info,
+                    "Special",
+                    sizeof(new_info) - strlen(new_info));
+                break;
+
+            case 6:
+                strncat(new_info,
+                    "Priority",
+                    sizeof(new_info) - strlen(new_info));
+                break;
+
+            case 7:
+                strncat(new_info,
+                    "Emergency",
+                    sizeof(new_info) - strlen(new_info));
+
+                // Functionality removed
+
+                break;
+
+            default:
+                strncat(new_info,
+                    "Off Duty",
+                    sizeof(new_info) - strlen(new_info));
+        }
+    }
+    else {
+        xastir_snprintf(temp, sizeof(temp), "Custom%d",msg);
+        strncat(new_info,
+            temp,
+            sizeof(new_info) - strlen(new_info));
+    }
+
+    if (info[offset] != '\0') {
+        /* Append the rest of the message to the expanded MIC-E message */
+        for (ii=offset; ii<info_size; ii++)
+            temp[ii-offset] = info[ii];
+
+        temp[info_size-offset] = '\0';
+        strncat(new_info,
+            " ",
+            sizeof(new_info) - strlen(new_info));
+        strncat(new_info,
+            temp,
+            sizeof(new_info) - strlen(new_info));
+    }
+
+
+    // We don't transmit Mic-E protocol from Xastir, so we know it's
+    // not our station's packets or our object/item packets,
+    // therefore the last two parameters here are both zero.
+    //
+    ok = data_add(APRS_MICE,call_sign,path,new_info,port,NULL,third_party, 0, 0);
+
+
+    return(ok);
+}   // End of decode_Mic_E()
+
+
+
+
+void delete_object(char *name) {
+       
+       // TODO 
+/*
+       
+       DataRow *p_station;
+
+//fprintf(stderr,"delete_object\n");
+
+    p_station = NULL;
+    if (search_station_name(&p_station,name,1)) {       // find object name
+        p_station->flag &= (~ST_ACTIVE);                // clear flag
+        p_station->flag &= (~ST_INVIEW);                // clear "In View" flag
+        if (position_on_screen(p_station->coord_lat,p_station->coord_lon))
+            redraw_on_new_data = 2;                     // redraw now
+            // there is some problem...  it is not redrawn immediately! ????
+            // but deleted from list immediatetly
+        redo_list = (int)TRUE;                          // and update lists
+    }
+    */
+}
+
+
+/*
+ *  Decode APRS Information Field and dispatch it depending on the Data Type ID
+ *
+ *         call = Callsign or object/item name string
+ *         path = Path string
+ *      message = Info field (corrupted already if object/item packet)
+ *       origin = Originating callsign if object/item, otherwise NULL
+ *         from = DATA_VIA_LOCAL/DATA_VIA_TNC/DATA_VIA_NET/DATA_VIA_FILE
+ *         port = Port number
+ *  third_party = Set to one if third-party packet
+ * orig_message = Unmodified info field
+ *
+ */
+void decode_info_field(gchar *call,
+                       gchar *path,
+                       gchar *message,
+                       gchar *origin, 
+                       TAprsPort port,
+                       gint third_party,
+                       gchar *orig_message) 
+{
+//    char line[MAX_LINE_SIZE+1];
+    int  ok_igate_net;
+    int  ok_igate_rf;
+    int  done, ignore;
+    char data_id;
+    int station_is_mine = 0;
+    int object_is_mine = 0;
+
+    /* remember fixed format starts with ! and can be up to 24 chars in the message */ // ???
+
+    done         = 0;       // if 1, packet was decoded
+    ignore       = 0;       // if 1, don't treat undecoded packets as status text
+    ok_igate_net = 0;       // if 1, send packet to internet
+    ok_igate_rf  = 0;       // if 1, igate packet to RF if "from" is in nws-stations.txt
+
+    if ( is_my_call(call, 1) ) {
+        station_is_mine++; // Station is controlled by me
+    }
+    if ( (message != NULL) && (strlen(message) > MAX_LINE_SIZE) ) { 
+       // Overly long message, throw it away.
+        done = 1;
+    }
+    else if (message == NULL || strlen(message) == 0) {      
+       // we could have an empty message
+        (void)data_add(STATION_CALL_DATA,call,path,NULL,port,origin,third_party, station_is_mine, 0);
+        done = 1;                                       
+        // don't report it to internet
+    }
+
+    // special treatment for objects/items.
+    if (!done && origin[0] != '\0') {
+
+        // If station/object/item is owned by me (including SSID)
+        if ( is_my_call(origin, 1) ) {
+            object_is_mine++;
+        }
+         
+        if (message[0] == '*') {    // set object
+            (void)data_add(APRS_OBJECT,call,path,message+1,port,origin,third_party, station_is_mine, object_is_mine);
+            if (strlen(origin) > 0 && strncmp(origin,"INET",4)!=0) {
+                ok_igate_net = 1;   // report it to internet
+            }
+            ok_igate_rf = 1;
+            done = 1;
+        }
+
+        else if (message[0] == '!') {   
+               // set item
+            (void)data_add(APRS_ITEM,call,path,message+1,port,origin,third_party, station_is_mine, object_is_mine);
+            if (strlen(origin) > 0 && strncmp(origin,"INET",4)!=0) {
+                ok_igate_net = 1;   // report it to internet
+            }
+            ok_igate_rf = 1;
+            done = 1;
+        }
+
+        else if (message[0] == '_') {   // delete object/item
+// TODO
+/*
+            AprsDataRow *p_station;
+
+            delete_object(call);    // ?? does not vanish from map immediately !!???
+
+            // If object was owned by me but another station is
+            // transmitting it now, write entries into the
+            // object.log file showing that we don't own this object
+            // anymore.
+            p_station = NULL;
+
+            if (search_station_name(&p_station,call,1)) {
+                if (is_my_object_item(p_station)    // If station was owned by me (including SSID)
+                        && (!object_is_mine) ) {  // But isn't now
+                    disown_object_item(call,origin);
+                }
+            }
+
+            if (strlen(origin) > 0 && strncmp(origin,"INET",4)!=0) {
+                ok_igate_net = 1;   // report it to internet
+            }
+
+            ok_igate_rf = 1;
+*/
+            done = 1;
+            
+        }
+    }
+    
+    if (!done) {
+        int rdf_type;
+
+        data_id = message[0];           // look at the APRS Data Type ID (first char in information field)
+        message += 1;                   // extract data ID from information field
+        ok_igate_net = 1;               // as default report packet to internet
+
+
+        switch (data_id) {
+            case '=':   // Position without timestamp (with APRS messaging)
+
+                //WE7U
+                // Need to check for weather info in this packet type as well?
+
+                done = data_add(APRS_MSGCAP,call,path,message,port,origin,third_party, station_is_mine, 0);
+                ok_igate_rf = done;
+                break;
+
+            case '!':   // Position without timestamp (no APRS messaging) or Ultimeter 2000 WX
+                if (message[0] == '!' && is_xnum_or_dash(message+1,40))   // Ultimeter 2000 WX
+                    done = data_add(APRS_WX3,call,path,message+1,port,origin,third_party, station_is_mine, 0);
+                else
+                    done = data_add(APRS_FIXED,call,path,message,port,origin,third_party, station_is_mine, 0);
+                ok_igate_rf = done;
+                break;
+
+            case '/':   // Position with timestamp (no APRS messaging)
+
+//WE7U
+// Need weather decode in this section similar to the '@' section
+// below.
+
+                if ((toupper(message[14]) == 'N' || toupper(message[14]) == 'S') &&
+                    (toupper(message[24]) == 'W' || toupper(message[24]) == 'E')) { // uncompressed format
+                    if (message[29] == '/') {
+                        if (message[33] == 'g' && message[37] == 't')
+                            done = data_add(APRS_WX1,call,path,message,port,origin,third_party, station_is_mine, 0);
+                        else
+                            done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
+                    }
+                    else
+                        done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
+                }
+                else {                                                // compressed format
+                    if (message[16] >= '!' && message[16] <= 'z') {     // csT is speed/course
+                        if (message[20] == 'g' && message[24] == 't')   // Wx data
+                            done = data_add(APRS_WX1,call,path,message,port,origin,third_party, station_is_mine, 0);
+                        else
+                            done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
+                    }
+                    else
+                        done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
+                }
+//                done = data_add(APRS_DOWN,call,path,message,from,port,origin,third_party, station_is_mine, 0);
+                ok_igate_rf = done;
+                break;
+
+            case '@':   // Position with timestamp (with APRS messaging)
+                // DK7IN: could we need to test the message length first?
+                if ((toupper(message[14]) == 'N' || toupper(message[14]) == 'S') &&
+                    (toupper(message[24]) == 'W' || toupper(message[24]) == 'E')) {       // uncompressed format
+                    if (message[29] == '/') {
+                        if (message[33] == 'g' && message[37] == 't')
+                            done = data_add(APRS_WX1,call,path,message,port,origin,third_party, station_is_mine, 0);
+                        else
+                            done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
+                    }
+                    else
+                        done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
+                }
+                else {                                                // compressed format
+                    if (message[16] >= '!' && message[16] <= 'z') {     // csT is speed/course
+                        if (message[20] == 'g' && message[24] == 't')   // Wx data
+                            done = data_add(APRS_WX1,call,path,message,port,origin,third_party, station_is_mine, 0);
+                        else
+                            done = data_add(APRS_MOBILE,call,path,message,port,origin,third_party, station_is_mine, 0);
+                    }
+                    else
+                        done = data_add(APRS_DF,call,path,message,port,origin,third_party, station_is_mine, 0);
+                }
+                ok_igate_rf = done;
+                break;
+
+            case '[':   // Maidenhead grid locator beacon (obsolete- but used for meteor scatter)
+                done = data_add(APRS_GRID,call,path,message,port,origin,third_party, station_is_mine, 0);
+                ok_igate_rf = done;
+                break;
+            case 0x27:  // Mic-E  Old GPS data (or current GPS data in Kenwood TM-D700)
+            case 0x60:  // Mic-E  Current GPS data (but not used in Kennwood TM-D700)
+            //case 0x1c:// Mic-E  Current GPS data (Rev. 0 beta units only)
+            //case 0x1d:// Mic-E  Old GPS data (Rev. 0 beta units only)
+                done = decode_Mic_E(call,path,message,port,third_party);
+                ok_igate_rf = done;
+                break;
+
+            case '_':   // Positionless weather data                [APRS Reference, chapter 12]
+                done = data_add(APRS_WX2,call,path,message,port,origin,third_party, station_is_mine, 0);
+                ok_igate_rf = done;
+                break;
+
+            case '#':   // Peet Bros U-II Weather Station (km/h)    [APRS Reference, chapter 12]
+                if (is_xnum_or_dash(message,13))
+                    done = data_add(APRS_WX4,call,path,message,port,origin,third_party, station_is_mine, 0);
+                ok_igate_rf = done;
+                break;
+
+            case '*':   // Peet Bros U-II Weather Station (mph)
+                if (is_xnum_or_dash(message,13))
+                    done = data_add(APRS_WX6,call,path,message,port,origin,third_party, station_is_mine, 0);
+                ok_igate_rf = done;
+                break;
+
+            case '$':   // Raw GPS data or Ultimeter 2000
+                if (strncmp("ULTW",message,4) == 0 && is_xnum_or_dash(message+4,44))
+                    done = data_add(APRS_WX5,call,path,message+4,port,origin,third_party, station_is_mine, 0);
+                else if (strncmp("GPGGA",message,5) == 0)
+                    done = data_add(GPS_GGA,call,path,message,port,origin,third_party, station_is_mine, 0);
+                else if (strncmp("GPRMC",message,5) == 0)
+                    done = data_add(GPS_RMC,call,path,message,port,origin,third_party, station_is_mine, 0);
+                else if (strncmp("GPGLL",message,5) == 0)
+                    done = data_add(GPS_GLL,call,path,message,port,origin,third_party, station_is_mine, 0);
+                else {
+                        // handle VTG and WPT too  (APRS Ref p.25)
+                }
+                ok_igate_rf = done;
+                break;
+
+            case ':':   // Message
+
+                // Do message logging if that feature is enabled.
+                done = decode_message(call,path,message,port,third_party);
+
+                // there could be messages I should not retransmit to internet... ??? Queries to me...
+                break;
+
+            case '>':   // Status                                   [APRS Reference, chapter 16]
+                done = data_add(APRS_STATUS,call,path,message,port,origin,third_party, station_is_mine, 0);
+                ok_igate_rf = done;
+                break;
+
+            case '?':   // Query
+                done = process_query(call,path,message,port,third_party);
+                ignore = 1;     // don't treat undecoded packets as status text
+                break;
+
+            case 'T':   // Telemetry data                           [APRS Reference, chapter 13]
+                // We treat these as status packets currently.
+                ok_igate_rf = 1;
+                done = data_add(APRS_STATUS,call,path,message,port,origin,third_party, station_is_mine, 0);
+                break;
+            case '{':   // User-defined APRS packet format     //}
+                // We treat these as status packets currently.
+                ok_igate_rf = 1;
+                break;
+            case '<':   // Station capabilities                     [APRS Reference, chapter 15]
+                //
+                // We could tweak the Incoming Data dialog to add
+                // filter togglebuttons.  One such toggle could be
+                // "Station Capabilities".  We'd then have a usable
+                // dialog for displaying things like ?IGATE?
+                // responses.  In this case we wouldn't have to do
+                // anything special with the packet for decoding,
+                // just let it hit the default block below for
+                // putting them into the status field of the record.
+                // One downside is that we'd only be able to catch
+                // new station capability records in that dialog.
+                // The only way to look at past capability records
+                // would be the Station Info dialog for each
+                // station.
+                //
+                //fprintf(stderr,"%10s:  %s\n", call, message);
+
+                // Don't set "done" as we want these to appear in
+                // the status text for the record.
+                break;
+
+            case '%':   // Agrelo DFJr / MicroFinder Radio Direction Finding
+
+                // Here is where we'd add a call to an RDF decode
+                // function so that we could display vectors on the
+                // map for each RDF position.
+
+//
+// Agrelo format:  "%XXX/Q<cr>"
+//
+// "XXX" is relative bearing to the signal (000-359).  Careful here:
+// At least one unit reports in magnetic instead of relative
+// degrees.  "000" means no direction info available, 360 means true
+// north.
+//
+// "Q" is bearing quality (0-9).  0 = unsuitable.  9 = manually
+// entered.  1-8 = varying quality with 8 being the best.
+//
+// I've also seen these formats, which may not be Agrelo compatible:
+//
+//      "%136.0/9"
+//      "%136.0/8/158.0" (That last number is magnetic bearing)
+//
+// These sentences may be sent MULTIPLE times per second, like 20 or
+// more!  If we decide to average readings, we'll need to dump our
+// averages and start over if our course changes.
+//
+
+                // Check for Agrelo format:
+                if (    strlen(message) >= 5
+                        && is_num_chr(message[0])   // "%136/9"
+                        && is_num_chr(message[1])
+                        && is_num_chr(message[2])
+                        && message[3] == '/'
+                        && is_num_chr(message[4]) ) {
+
+                    rdf_type = 1;
+
+                    fprintf(stderr,
+                        "Type 1 RDF packet from call: %s\tBearing: %c%c%c\tQuality: %c\n",
+                        call,
+                        message[0],
+                        message[1],
+                        message[2],
+                        message[4]);
+                }
+
+                // Check for extended formats (not
+                // Agrelo-compatible):
+                else if (strlen(message) >= 13
+                        && is_num_chr(message[0])   // "%136.0/8/158.0"
+                        && is_num_chr(message[1])
+                        && is_num_chr(message[2])
+                        && message[3] == '.'
+                        && is_num_chr(message[4])
+                        && message[5] == '/'
+                        && is_num_chr(message[6])
+                        && message[7] == '/'
+                        && is_num_chr(message[8])
+                        && is_num_chr(message[9])
+                        && is_num_chr(message[10])
+                        && message[11] == '.'
+                        && is_num_chr(message[12]) ) {
+
+                    rdf_type = 3;
+
+                    fprintf(stderr,
+                        "Type 3 RDF packet from call: %s\tBearing: %c%c%c%c%c\tQuality: %c\tMag Bearing: %c%c%c%c%c\n",
+                        call,
+                        message[0],
+                        message[1],
+                        message[2],
+                        message[3],
+                        message[4],
+                        message[6],
+                        message[8],
+                        message[9],
+                        message[10],
+                        message[11],
+                        message[12]);
+                }
+
+                // Check for extended formats (not
+                // Agrelo-compatible):
+                else if (strlen(message) >= 7
+                        && is_num_chr(message[0])   // "%136.0/9"
+                        && is_num_chr(message[1])
+                        && is_num_chr(message[2])
+                        && message[3] == '.'
+                        && is_num_chr(message[4])
+                        && message[5] == '/'
+                        && is_num_chr(message[6]) ) {
+
+                    rdf_type = 2;
+
+                    fprintf(stderr,
+                        "Type 2 RDF packet from call: %s\tBearing: %c%c%c%c%c\tQuality: %c\n",
+                        call,
+                        message[0],
+                        message[1],
+                        message[2],
+                        message[3],
+                        message[4],
+                        message[6]);
+                }
+
+                // Don't set "done" as we want these to appear in
+                // the status text for the record until we get the
+                // full decoding for this type of packet coded up.
+                break;
+            case '~':   // UI-format messages, not relevant for APRS ("Do not use" in Reference)
+            case ',':   // Invalid data or test packets             [APRS Reference, chapter 19]
+            case '&':   // Reserved -- Map Feature
+                ignore = 1;     // Don't treat undecoded packets as status text
+                break;
+        }
+
+        
+        // Add most remaining data to the station record as status
+        // info
+        //
+        if (!done && !ignore) {         // Other Packets        [APRS Reference, chapter 19]
+            done = data_add(OTHER_DATA,call,path,message-1,port,origin,third_party, station_is_mine, 0);
+            ok_igate_net = 0;           // don't put data on internet       ????
+        }
+
+        if (!done) {                    // data that we do ignore...
+            //fprintf(stderr,"decode_info_field: not decoding info: Call:%s ID:%c Msg:|%s|\n",call,data_id,message);
+            ok_igate_net = 0;           // don't put data on internet
+        }
+    }
+    
+    if (third_party)
+        ok_igate_net = 0;   // don't put third party traffic on internet
+
+
+    if (station_is_mine)
+        ok_igate_net = 0;   // don't put my data on internet     ???
+    
+    
+    // TODO - Add TX to IGate support
+/*
+    if (ok_igate_net) {
+
+        if ( (from == DATA_VIA_TNC) // Came in via a TNC
+                && (strlen(orig_message) > 0) ) { // Not empty
+
+            // Here's where we inject our own callsign like this:
+            // "WE7U-15,I" in order to provide injection ID for our
+            // igate.
+            snprintf(line,
+                sizeof(line),
+                "%s>%s,%s,I:%s",
+                (strlen(origin)) ? origin : call,
+                path,
+                _aprs_mycall,
+                orig_message);
+
+            output_igate_net(line, port, third_party);
+        }
+    }
+*/
+}
+
+
+
+/*
+ *  Check for a valid object name
+ */
+int valid_object(char *name) {
+    int len, i;
+
+    // max 9 printable ASCII characters, case sensitive   [APRS
+    // Reference]
+    len = (int)strlen(name);
+    if (len > 9 || len == 0)
+        return(0);                      // wrong size
+
+    for (i=0;i<len;i++)
+        if (!isprint((int)name[i]))
+            return(0);                  // not printable
+
+    return(1);
+}
+
+
+
+
+
+
+/*
+ *  Extract object or item data from information field before processing
+ *
+ *  Returns 1 if valid object found, else returns 0.
+ *
+ */
+int extract_object(char *call, char **info, char *origin) 
+{
+    int ok, i;
+
+    // Object and Item Reports     [APRS Reference, chapter 11]
+    ok = 0;
+    // todo: add station originator to database
+    if ((*info)[0] == ';') {                    // object
+        // fixed 9 character object name with any printable ASCII character
+        if (strlen((*info)) > 1+9) {
+            substr(call,(*info)+1,9);           // extract object name
+            (*info) = (*info) + 10;
+            // Remove leading spaces ? They look bad, but are allowed by the APRS Reference ???
+            (void)remove_trailing_spaces(call);
+            if (valid_object(call)) {
+                // info length is at least 1
+                ok = 1;
+            }
+        }
+    }
+    else if ((*info)[0] == ')') {             // item
+        // 3 - 9 character item name with any printable ASCII character
+        if (strlen((*info)) > 1+3) {
+            for (i = 1; i <= 9; i++) {
+                if ((*info)[i] == '!' || (*info)[i] == '_') {
+                    call[i-1] = '\0';
+                    break;
+                }
+                call[i-1] = (*info)[i];
+            }
+            call[9] = '\0';  // In case we never saw '!' || '_'
+            (*info) = &(*info)[i];
+            // Remove leading spaces ? They look bad, but are allowed by the APRS Reference ???
+            //(void)remove_trailing_spaces(call);   // This statement messed up our searching!!! Don't use it!
+            if (valid_object(call)) {
+                // info length is at least 1
+                ok = 1;
+            }
+        }
+    }
+    else 
+    {
+        fprintf(stderr,"Not an object, nor an item!!! call=%s, info=%s, origin=%s.\n",
+               call, *info, origin);
+    }
+    return(ok);
+}
+
+
+/*
+ *  Decode AX.25 line
+ *  \r and \n should already be stripped from end of line
+ *  line should not be NULL
+ *
+ * If dbadd is set, add to database.  Otherwise, just return true/false
+ * to indicate whether input is valid AX25 line.
+ */
+//
+// Note that the length of "line" can be up to MAX_DEVICE_BUFFER,
+// which is currently set to 4096.
+//
+gint decode_ax25_line(gchar *line, TAprsPort port) {
+    gchar *call_sign;
+    gchar *path0;
+    gchar path[100+1];           // new one, we may add an '*'
+    gchar *info;
+    gchar info_copy[MAX_LINE_SIZE+1];
+    gchar call[MAX_CALLSIGN+1];
+    gchar origin[MAX_CALLSIGN+1];
+    gint ok;
+    gint third_party;
+//    gchar backup[MAX_LINE_SIZE+1];
+//    gchar tmp_line[MAX_LINE_SIZE+1];
+//    gchar tmp_path[100+1];
+//    gchar *ViaCalls[10];
+
+    if (line == NULL) 
+    {
+        return(FALSE);
+    }
+
+    if ( strlen(line) > MAX_LINE_SIZE ) 
+    { 
+        // Overly long message, throw it away.  We're done.
+        return(FALSE);
+    }
+
+    if (line[strlen(line)-1] == '\n')           // better: look at other places,
+                                                // so that we don't get it here...
+        line[strlen(line)-1] = '\0';            // Wipe out '\n', to be sure
+    if (line[strlen(line)-1] == '\r')
+        line[strlen(line)-1] = '\0';            // Wipe out '\r'
+
+    call_sign   = NULL;
+    path0       = NULL;
+    info        = NULL;
+    origin[0]   = '\0';
+    call[0]     = '\0';
+    path[0]     = '\0';
+    third_party = 0;
+
+    // CALL>PATH:APRS-INFO-FIELD                // split line into components
+    //     ^    ^
+    ok = 0;
+    call_sign = (gchar*)strtok(line,">");               // extract call from AX.25 line
+    if (call_sign != NULL) {
+        path0 = (gchar*)strtok(NULL,":");               // extract path from AX.25 line
+        if (path0 != NULL) {
+            info = (gchar*)strtok(NULL,"");             // rest is info_field
+            if (info != NULL) {
+                if ((info - path0) < 100) {     // check if path could be copied
+                    ok = 1;                     // we have found all three components
+                }
+            }
+        }
+    }
+
+    if (ok) 
+    {
+        snprintf(path, sizeof(path), "%s", path0);
+
+        snprintf(info_copy, sizeof(info_copy), "%s", info);
+
+        ok = valid_path(path);                  // check the path and convert it to TAPR format
+        // Note that valid_path() also removes igate injection identifiers
+    }
+
+    if (ok) 
+    {
+        extract_TNC_text(info);                 // extract leading text from TNC X-1J4
+        if (strlen(info) > 256)                 // first check if information field conforms to APRS specs
+            ok = 0;                             // drop packets too long
+    }
+
+    if (ok) 
+    {                                                   // check callsign
+        (void)remove_trailing_asterisk(call_sign);              // is an asterisk valid here ???
+
+        if (valid_inet_name(call_sign,info,origin,sizeof(origin))) 
+        { // accept some of the names used in internet
+            snprintf(call, sizeof(call), "%s", call_sign);
+        }
+        else if (valid_call(call_sign)) 
+        {                     // accept real AX.25 calls
+            snprintf(call, sizeof(call), "%s", call_sign);
+        }
+        else {
+            ok = 0;
+        }
+    }
+
+    if (ok && info[0] == '}') 
+    {
+       // look for third-party traffic
+        ok = extract_third_party(call,path,sizeof(path),&info,origin,sizeof(origin)); // extract third-party data
+        third_party = 1;
+    }
+
+    if (ok && (info[0] == ';' || info[0] == ')')) 
+    {             
+       // look for objects or items
+        snprintf(origin, sizeof(origin), "%s", call);
+
+        ok = extract_object(call,&info,origin);                 // extract object data
+    }
+
+    if (ok) 
+    {
+        // decode APRS information field, always called with valid call and path
+        // info is a string with 0 - 256 bytes
+        // fprintf(stderr,"dec: %s (%s) %s\n",call,origin,info);
+  
+       decode_info_field(call,
+            path,
+            info,
+            origin,
+            port,
+            third_party,
+            info_copy);
+    }
+
+
+    return(ok);
+}
+
+
+
+
+
+///////////////////
+double convert_lon_l2d(long lon) 
+{
+    double dResult = 0.00;
+    
+    int ewpn;
+    float deg, min; //, sec;
+    int ideg; //, imin;
+    long temp;
+
+    deg = (float)(lon - 64800000l) / 360000.0;
+
+    // Switch to integer arithmetic to avoid floating-point rounding
+    // errors.
+    temp = (long)(deg * 100000);
+
+    
+    ewpn = 1;
+    if (temp <= 0) 
+    {
+        ewpn = -1;
+        temp = labs(temp);
+    }
+
+    ideg = (int)temp / 100000;
+    min = (temp % 100000) * 60.0 / 100000.0;
+
+
+    dResult = ewpn*(ideg+min/60.0);
+
+    return dResult;
+}
+
+
+
+
+// convert latitude from long to double 
+// Input is in Xastir coordinate system
+
+double convert_lat_l2d(long lat) 
+{
+    double dResult = 0.00;
+
+    int nspn;
+    float deg, min;
+    int ideg;
+    long temp;
+
+    deg = (float)(lat - 32400000l) / 360000.0;
+    // Switch to integer arithmetic to avoid floating-point
+    // rounding errors.
+    temp = (long)(deg * 100000);
+
+    nspn = -1;
+    if (temp <= 0) {
+        nspn = 1;
+        temp = labs(temp);
+    }   
+
+    ideg = (int)temp / 100000;
+    min = (temp % 100000) * 60.0 / 100000.0;
+
+    dResult = nspn*(ideg+min/60.0);
+
+    return dResult;
+}
+
+
+
+/***********************************************************/
+/* returns the hour (00..23), localtime                    */
+/***********************************************************/
+int get_hours(void) {
+    struct tm *time_now;
+    time_t secs_now;
+    char shour[5];
+
+    secs_now=sec_now();
+    time_now = localtime(&secs_now);
+    (void)strftime(shour,4,"%H",time_now);
+    return(atoi(shour));
+}
+
+
+
+
+/***********************************************************/
+/* returns the minute (00..59), localtime                  */
+/***********************************************************/
+int get_minutes(void) {
+    struct tm *time_now;
+    time_t secs_now;
+    char sminute[5];
+
+    secs_now=sec_now();
+    time_now = localtime(&secs_now);
+    (void)strftime(sminute,4,"%M",time_now);
+    return(atoi(sminute));
+}
+
+
+
+
+/**************************************************************/
+/* compute_rain_hour - rolling average for the last 59.x      */
+/* minutes of rain.  I/O numbers are in hundredths of an inch.*/
+/* Output is in "rain_minute_total", a global variable.       */
+/**************************************************************/
+void compute_rain_hour(float rain_total) {
+    int current, j;
+    float lowest;
+
+
+    // Deposit the _starting_ rain_total for each minute into a separate bucket.
+    // Subtract lowest rain_total in queue from current rain_total to get total
+    // for the last hour.
+
+
+    current = get_minutes(); // Fetch the current minute value.  Use this as an index
+                        // into our minute "buckets" where we store the data.
+
+
+    rain_minute[ (current + 1) % 60 ] = 0.0;   // Zero out the next bucket (probably have data in
+                                                // there from the previous hour).
+
+
+    if (rain_minute[current] == 0.0)           // If no rain_total stored yet in this minute's bucket
+        rain_minute[current] = rain_total;      // Write into current bucket
+
+
+    // Find the lowest non-zero value for rain_total.  The max value is "rain_total".
+    lowest = rain_total;                    // Set to maximum to get things going
+    for (j = 0; j < 60; j++) {
+        if ( (rain_minute[j] > 0.0) && (rain_minute[j] < lowest) ) { // Found a lower non-zero value?
+            lowest = rain_minute[j];        // Snag it
+        }
+    }
+    // Found it, subtract the two to get total for the last hour
+    rain_minute_total = rain_total - lowest;
+}
+
+
+
+
+/***********************************************************/
+/* compute_rain - compute rain totals from the total rain  */
+/* so far.  rain_total (in hundredths of an inch) keeps on */
+/* incrementing.                                           */
+/***********************************************************/
+void compute_rain(float rain_total) {
+    int current, i;
+    float lower;
+
+
+    // Skip the routine if input is outlandish (Negative value, zero, or 512 inches!).
+    // We seem to get occasional 0.0 packets from wx200d.  This makes them go away.
+    if ( (rain_total <= 0.0) || (rain_total > 51200.0) )
+        return;
+
+    compute_rain_hour(rain_total);
+
+    current = get_hours();
+
+    // Set rain_base:  The first rain_total for each hour.
+    if (rain_base[current] == 0.0) {       // If we don't have a start value yet for this hour,
+        rain_base[current] = rain_total;    // save it away.
+        rain_check = 0;                     // Set up rain_check so we'll do the following
+                                            // "else" clause a few times at start of each hour.
+    }
+    else {  // rain_base has been set, is it wrong?  We recheck three times at start of hour.
+        if (rain_check < 3) {
+            rain_check++;
+            // Is rain less than base?  It shouldn't be.
+            if (rain_total < rain_base[current])
+                rain_base[current] = rain_total;
+
+            // Difference greater than 10 inches since last reading?  It shouldn't be.
+            if (fabs(rain_total - rain_base[current]) > 1000.0) // Remember:  Hundredths of an inch
+                rain_base[current] = rain_total;
+        }
+    }
+    rain_base[ (current + 1) % 24 ] = 0.0;    // Clear next hour's index.
+
+
+    // Compute total rain in last 24 hours:  Really we'll compute the total rain
+    // in the last 23 hours plus the portion of an hour we've completed (Sum up
+    // all 24 of the hour totals).  This isn't the perfect way to do it, but to
+    // really do it right we'd need finer increments of time (to get closer to
+    // the goal of 24 hours of rain).
+    lower = rain_total;
+    for ( i = 0; i < 24; i++ ) {    // Find the lowest non-zero rain_base value in last 24 hours
+        if ( (rain_base[i] > 0.0) && (rain_base[i] < lower) ) {
+            lower = rain_base[i];
+        }
+    }
+    rain_24 = rain_total - lower;    // Hundredths of an inch
+
+
+    // Compute rain since midnight.  Note that this uses whatever local time was set
+    // on the machine.  It might not be local midnight if your box is set to GMT.
+    lower = rain_total;
+    for ( i = 0; i <= current; i++ ) {  // Find the lowest non-zero rain_base value since midnight
+        if ( (rain_base[i] > 0.0) && (rain_base[i] < lower) ) {
+            lower = rain_base[i];
+        }
+    }
+    rain_00 = rain_total - lower;    // Hundredths of an inch
+
+    // It is the responsibility of the calling program to save
+    // the new totals in the data structure for our station.
+    // We don't return anything except in global variables.
+
+}
+
+
+
+
+/**************************************************************/
+/* compute_gust - compute max wind gust during last 5 minutes */
+/*                                                            */
+/**************************************************************/
+float compute_gust(float wx_speed, float last_speed, time_t *last_speed_time) {
+    float computed_gust;
+    int current, j;
+
+
+    // Deposit max gust for each minute into a different bucket.
+    // Check all buckets for max gust within the last five minutes
+    // (Really 4 minutes plus whatever portion of a minute we've completed).
+
+    current = get_minutes(); // Fetch the current minute value.  We use this as an index
+                        // into our minute "buckets" where we store the data.
+
+    // If we haven't started collecting yet, set up to do so
+    if (gust_read_ptr == gust_write_ptr) {  // We haven't started yet
+        gust_write_ptr = current;           // Set to write into current bucket
+        gust_last_write = current;
+
+        gust_read_ptr = current - 1;        // Set read pointer back one, modulus 60
+        if (gust_read_ptr < 0)
+            gust_read_ptr = 59;
+
+        gust[gust_write_ptr] = 0.0;         // Zero the max gust
+        gust[gust_read_ptr] = 0.0;          // for both buckets.
+
+//WE7U: Debug
+//gust[gust_write_ptr] = 45.9;
+    }
+
+    // Check whether we've advanced at least one minute yet
+    if (current != gust_write_ptr) {        // We've advanced to a different minute
+        gust_write_ptr = current;           // Start writing into a new bucket.
+        gust[gust_write_ptr] = 0.0;         // Zero the new bucket
+
+        // Check how many bins of real data we have currently.  Note that this '5' is
+        // correct, as we just advanced "current" to the next minute.  We're just pulling
+        // along the read_ptr behind us if we have 5 bins worth of data by now.
+        if ( ((gust_read_ptr + 5) % 60) == gust_write_ptr)  // We have 5 bins of real data
+            gust_read_ptr = (gust_read_ptr + 1) % 60;       // So advance the read pointer,
+
+        // Check for really bad pointers, perhaps the weather station got
+        // unplugged for a while or it's just REALLY slow at sending us data?
+        // We're checking to see if gust_last_write happened in the previous
+        // minute.  If not, we skipped a minute or more somewhere.
+        if ( ((gust_last_write + 1) % 60) != current ) {
+            // We lost some time somewhere: Reset the pointers, older gust data is
+            // lost.  Start over collecting new gust data.
+
+            gust_read_ptr = current - 1;    // Set read pointer back one, modulus 60
+            if (gust_read_ptr < 0)
+                gust_read_ptr = 59;
+
+            gust[gust_read_ptr] = 0.0;
+        }
+        gust_last_write = current;
+    }
+
+    // Is current wind speed higher than the current minute bucket?
+    if (wx_speed > gust[gust_write_ptr])
+        gust[gust_write_ptr] = wx_speed;    // Save it in the bucket
+
+    // Read the last (up to) five buckets and find the max gust
+    computed_gust=gust[gust_write_ptr];
+    j = gust_read_ptr;
+    while (j != ((gust_write_ptr + 1) % 60) ) {
+        if ( computed_gust < gust[j] )
+            computed_gust = gust[j];
+        j = (j + 1) % 60;
+    }
+
+    *last_speed_time = sec_now();
+    return(computed_gust);
+}
+
+
+
+
+
+//*********************************************************************
+// Decode Peet Brothers Ultimeter 2000 weather data (Data logging mode)
+//
+// This function is called from db.c:data_add() only.  Used for
+// decoding incoming packets, not for our own weather station data.
+//
+// The Ultimeter 2000 can be in any of three modes, Data Logging Mode,
+// Packet Mode, or Complete Record Mode.  This routine handles only
+// the Data Logging Mode.
+//*********************************************************************
+
+void decode_U2000_L (int from, unsigned char *data, AprsWeatherRow *weather) {
+    time_t last_speed_time;
+    float last_speed;
+    float computed_gust;
+    char temp_data1[10];
+    char *temp_conv;
+    char format;
+
+    last_speed = 0.0;
+    last_speed_time = 0;
+    computed_gust = 0.0;
+    format = 0;
+
+    weather->wx_type = WX_TYPE;
+    xastir_snprintf(weather->wx_station,
+        sizeof(weather->wx_station),
+        "U2k");
+
+    /* get last gust speed */
+    if (strlen(weather->wx_gust) > 0 && !from) {
+        /* get last speed */
+        last_speed = (float)atof(weather->wx_gust);
+        last_speed_time = weather->wx_speed_sec_time;
+    }
+
+    // 006B 00 58
+    // 00A4 00 46 01FF 380E 2755 02C1 03E8 ---- 0052 04D7    0001 007BM
+    // ^       ^  ^    ^    ^         ^                      ^
+    // 0       6  8    12   16        24                     40
+    /* wind speed */
+    if (data[0] != '-') { // '-' signifies invalid data
+        substr(temp_data1,(char *)data,4);
+        xastir_snprintf(weather->wx_speed,
+            sizeof(weather->wx_speed),
+            "%03d",
+            (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
+        if (from) {
+            weather->wx_speed_sec_time = sec_now();
+        } else {
+            /* local station */
+            computed_gust = compute_gust((float)atof(weather->wx_speed),
+                                        last_speed,
+                                        &last_speed_time);
+            weather->wx_speed_sec_time = sec_now();
+            xastir_snprintf(weather->wx_gust,
+                sizeof(weather->wx_gust),
+                "%03d",
+                (int)(0.5 + computed_gust)); // Cheater's way of rounding
+        }
+    } else {
+        if (!from)
+            weather->wx_speed[0] = 0;
+    }
+
+    /* wind direction */
+    //
+    // Note that the first two digits here may be 00, or may be FF
+    // if a direction calibration has been entered.  We should zero
+    // them.
+    //
+    if (data[6] != '-') { // '-' signifies invalid data
+        substr(temp_data1,(char *)(data+6),2);
+        temp_data1[0] = '0';
+        temp_data1[1] = '0';
+        xastir_snprintf(weather->wx_course,
+            sizeof(weather->wx_course),
+            "%03d",
+            (int)(((float)strtol(temp_data1,&temp_conv,16)/256.0)*360.0));
+    } else {
+        xastir_snprintf(weather->wx_course,
+            sizeof(weather->wx_course),
+            "000");
+        if (!from)
+            weather->wx_course[0]=0;
+    }
+
+    /* outdoor temp */
+    if (data[8] != '-') { // '-' signifies invalid data
+        int temp4;
+
+        substr(temp_data1,(char *)(data+8),4);
+        temp4 = (int)strtol(temp_data1,&temp_conv,16);
+        if (temp_data1[0] > '7') {  // Negative value, convert
+            temp4 = (temp4 & (temp4-0x7FFF)) - 0x8000;
+        }
+
+        xastir_snprintf(weather->wx_temp,
+            sizeof(weather->wx_temp),
+            "%03d",
+            (int)((float)((temp4<<16)/65536)/10.0));
+
+    }
+    else {
+        if (!from)
+            weather->wx_temp[0]=0;
+    }
+
+    /* rain total long term */
+    if (data[12] != '-') { // '-' signifies invalid data
+        substr(temp_data1,(char *)(data+12),4);
+        xastir_snprintf(weather->wx_rain_total,
+            sizeof(weather->wx_rain_total),
+            "%0.2f",
+            (float)strtol(temp_data1,&temp_conv,16)/100.0);
+        if (!from) {
+            /* local station */
+            compute_rain((float)atof(weather->wx_rain_total));
+            /*last hour rain */
+            xastir_snprintf(weather->wx_rain,
+                sizeof(weather->wx_rain),
+                "%0.2f",
+                rain_minute_total);
+            /*last 24 hour rain */
+            xastir_snprintf(weather->wx_prec_24,
+                sizeof(weather->wx_prec_24),
+                "%0.2f",
+                rain_24);
+            /* rain since midnight */
+            xastir_snprintf(weather->wx_prec_00,
+                sizeof(weather->wx_prec_00),
+                "%0.2f",
+                rain_00);
+        }
+    } else {
+        if (!from)
+            weather->wx_rain_total[0]=0;
+    }
+
+    /* baro */
+    if (data[16] != '-') { // '-' signifies invalid data
+        substr(temp_data1,(char *)(data+16),4);
+        xastir_snprintf(weather->wx_baro,
+            sizeof(weather->wx_baro),
+            "%0.1f",
+            (float)strtol(temp_data1,&temp_conv,16)/10.0);
+    } else {
+        if (!from)
+            weather->wx_baro[0]=0;
+    }
+    
+
+    /* outdoor humidity */
+    if (data[24] != '-') { // '-' signifies invalid data
+        substr(temp_data1,(char *)(data+24),4);
+        xastir_snprintf(weather->wx_hum,
+            sizeof(weather->wx_hum),
+            "%03d",
+            (int)((float)strtol(temp_data1,&temp_conv,16)/10.0));
+    } else {
+        if (!from)
+            weather->wx_hum[0]=0;
+    }
+
+    /* todays rain total */
+    if (data[40] != '-') { // '-' signifies invalid data
+        if (from) {
+            substr(temp_data1,(char *)(data+40),4);
+            xastir_snprintf(weather->wx_prec_00,
+                sizeof(weather->wx_prec_00),
+                "%0.2f",
+                (float)strtol(temp_data1,&temp_conv,16)/100.0);
+        }
+    } else {
+        if (!from)
+            weather->wx_prec_00[0] = 0;
+    }
+}
+
+
+
+
+
+//********************************************************************
+// Decode Peet Brothers Ultimeter 2000 weather data (Packet mode)
+//
+// This function is called from db.c:data_add() only.  Used for
+// decoding incoming packets, not for our own weather station data.
+//
+// The Ultimeter 2000 can be in any of three modes, Data Logging Mode,
+// Packet Mode, or Complete Record Mode.  This routine handles only
+// the Packet Mode.
+//********************************************************************
+void decode_U2000_P(int from, unsigned char *data, AprsWeatherRow *weather) {
+    time_t last_speed_time;
+    float last_speed;
+    float computed_gust;
+    char temp_data1[10];
+    char *temp_conv;
+    int len;
+
+    last_speed      = 0.0;
+    last_speed_time = 0;
+    computed_gust   = 0.0;
+    len = (int)strlen((char *)data);
+
+    weather->wx_type = WX_TYPE;
+    xastir_snprintf(weather->wx_station,
+        sizeof(weather->wx_station),
+        "U2k");
+
+    /* get last gust speed */
+    if (strlen(weather->wx_gust) > 0 && !from) {
+        /* get last speed */
+        last_speed = (float)atof(weather->wx_gust);
+        last_speed_time = weather->wx_speed_sec_time;
+    }
+
+    // $ULTW   0031 00 37 02CE  0069 ---- 0000 86A0 0001 ---- 011901CC   0000 0005
+    //         ^       ^  ^     ^    ^                   ^               ^    ^
+    //         0       6  8     12   16                  32              44   48
+
+    /* wind speed peak over last 5 min */
+    if (data[0] != '-') { // '-' signifies invalid data
+        substr(temp_data1,(char *)data,4);
+        if (from) {
+            xastir_snprintf(weather->wx_gust,
+                sizeof(weather->wx_gust),
+                "%03d",
+                (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
+            /* this may be the only wind data */
+            xastir_snprintf(weather->wx_speed,
+                sizeof(weather->wx_speed),
+                "%03d",
+                (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
+        } else {
+            /* local station and may be the only wind data */
+            if (len < 51) {
+                xastir_snprintf(weather->wx_speed,
+                    sizeof(weather->wx_speed),
+                    "%03d",
+                    (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
+                computed_gust = compute_gust((float)atof(weather->wx_speed),
+                                        last_speed,
+                                        &last_speed_time);
+                weather->wx_speed_sec_time = sec_now();
+                xastir_snprintf(weather->wx_gust,
+                    sizeof(weather->wx_gust),
+                    "%03d",
+                    (int)(0.5 + computed_gust));
+            }
+        }
+    } else {
+        if (!from)
+            weather->wx_gust[0] = 0;
+    }
+
+    /* wind direction */
+    //
+    // Note that the first two digits here may be 00, or may be FF
+    // if a direction calibration has been entered.  We should zero
+    // them.
+    //
+    if (data[6] != '-') { // '-' signifies invalid data
+        substr(temp_data1,(char *)(data+6),2);
+        temp_data1[0] = '0';
+        temp_data1[1] = '0';
+        xastir_snprintf(weather->wx_course,
+            sizeof(weather->wx_course),
+            "%03d",
+            (int)(((float)strtol(temp_data1,&temp_conv,16)/256.0)*360.0));
+    } else {
+        xastir_snprintf(weather->wx_course,
+            sizeof(weather->wx_course),
+            "000");
+        if (!from)
+            weather->wx_course[0] = 0;
+    }
+
+    /* outdoor temp */
+    if (data[8] != '-') { // '-' signifies invalid data
+        int temp4;
+
+        substr(temp_data1,(char *)(data+8),4);
+        temp4 = (int)strtol(temp_data1,&temp_conv,16);
+
+        if (temp_data1[0] > '7') {  // Negative value, convert
+            temp4 = (temp4 & (temp4-0x7FFF)) - 0x8000;
+        }
+
+        xastir_snprintf(weather->wx_temp,
+            sizeof(weather->wx_temp),
+            "%03d",
+            (int)((float)((temp4<<16)/65536)/10.0));
+    }
+    else {
+        if (!from)
+            weather->wx_temp[0] = 0;
+    }
+    /* todays rain total (on some units) */
+    if ((data[44]) != '-') { // '-' signifies invalid data
+        if (from) {
+            substr(temp_data1,(char *)(data+44),4);
+            xastir_snprintf(weather->wx_prec_00,
+                sizeof(weather->wx_prec_00),
+                "%0.2f",
+                (float)strtol(temp_data1,&temp_conv,16)/100.0);
+        }
+    } else {
+        if (!from)
+            weather->wx_prec_00[0] = 0;
+    }
+
+    /* rain total long term */
+    if (data[12] != '-') { // '-' signifies invalid data
+        substr(temp_data1,(char *)(data+12),4);
+        xastir_snprintf(weather->wx_rain_total,
+            sizeof(weather->wx_rain_total),
+            "%0.2f",
+            (float)strtol(temp_data1,&temp_conv,16)/100.0);
+        if (!from) {
+            /* local station */
+            compute_rain((float)atof(weather->wx_rain_total));
+            /*last hour rain */
+            snprintf(weather->wx_rain,
+                sizeof(weather->wx_rain),
+                "%0.2f",
+                rain_minute_total);
+            /*last 24 hour rain */
+            snprintf(weather->wx_prec_24,
+                sizeof(weather->wx_prec_24),
+                "%0.2f",
+                rain_24);
+            /* rain since midnight */
+            snprintf(weather->wx_prec_00,
+                sizeof(weather->wx_prec_00),
+                "%0.2f",
+                rain_00);
+        }
+    } else {
+        if (!from)
+            weather->wx_rain_total[0] = 0;
+    }
+
+    /* baro */
+    if (data[16] != '-') { // '-' signifies invalid data
+        substr(temp_data1,(char *)(data+16),4);
+        xastir_snprintf(weather->wx_baro,
+            sizeof(weather->wx_baro),
+            "%0.1f",
+            (float)strtol(temp_data1,&temp_conv,16)/10.0);
+    } else {
+        if (!from)
+            weather->wx_baro[0] = 0;
+    }
+
+    /* outdoor humidity */
+    if (data[32] != '-') { // '-' signifies invalid data
+        substr(temp_data1,(char *)(data+32),4);
+        xastir_snprintf(weather->wx_hum,
+            sizeof(weather->wx_hum),
+            "%03d",
+            (int)((float)strtol(temp_data1,&temp_conv,16)/10.0));
+    } else {
+        if (!from)
+            weather->wx_hum[0] = 0;
+    }
+
+    /* 1 min wind speed avg */
+    if (len > 48 && (data[48]) != '-') { // '-' signifies invalid data
+        substr(temp_data1,(char *)(data+48),4);
+        xastir_snprintf(weather->wx_speed,
+            sizeof(weather->wx_speed),
+            "%03d",
+            (int)(0.5 + ((float)strtol(temp_data1,&temp_conv,16)/10.0)*0.62137));
+        if (from) {
+            weather->wx_speed_sec_time = sec_now();
+        } else {
+            /* local station */
+            computed_gust = compute_gust((float)atof(weather->wx_speed),
+                                        last_speed,
+                                        &last_speed_time);
+            weather->wx_speed_sec_time = sec_now();
+            xastir_snprintf(weather->wx_gust,
+                sizeof(weather->wx_gust),
+                "%03d",
+                (int)(0.5 + computed_gust));
+        }
+    } else {
+        if (!from) {
+            if (len > 48)
+                weather->wx_speed[0] = 0;
+        }
+    }
+}
+
+
+
+
+
+//*****************************************************************
+// Decode Peet Brothers Ultimeter-II weather data
+//
+// This function is called from db.c:data_add() only.  Used for
+// decoding incoming packets, not for our own weather station data.
+//*****************************************************************
+void decode_Peet_Bros(int from, unsigned char *data, AprsWeatherRow *weather, int type) {
+    time_t last_speed_time;
+    float last_speed;
+    float computed_gust;
+    char temp_data1[10];
+    char *temp_conv;
+
+    last_speed    = 0.0;
+    computed_gust = 0.0;
+    last_speed_time = 0;
+
+    weather->wx_type = WX_TYPE;
+    xastir_snprintf(weather->wx_station,
+        sizeof(weather->wx_station),
+        "UII");
+
+    // '*' = MPH
+    // '#' = km/h
+    //
+    // #  5 0B 75 0082 0082
+    // *  7 00 76 0000 0000
+    //    ^ ^  ^  ^
+    //            rain [1/100 inch ?]
+    //         outdoor temp
+    //      wind speed [mph / km/h]
+    //    wind dir
+
+    /* wind direction */
+    //
+    // 0x00 is N
+    // 0x04 is E
+    // 0x08 is S
+    // 0x0C is W
+    //
+    substr(temp_data1,(char *)data,1);
+    snprintf(weather->wx_course,
+        sizeof(weather->wx_course),
+        "%03d",
+        (int)(((float)strtol(temp_data1,&temp_conv,16)/16.0)*360.0));
+
+    /* get last gust speed */
+    if (strlen(weather->wx_gust) > 0 && !from) {
+        /* get last speed */
+        last_speed = (float)atof(weather->wx_gust);
+        last_speed_time = weather->wx_speed_sec_time;
+    }
+
+    /* wind speed */
+    substr(temp_data1,(char *)(data+1),2);
+    if (type == APRS_WX4) {     // '#'  speed in km/h, convert to mph
+        snprintf(weather->wx_speed,
+            sizeof(weather->wx_speed),
+            "%03d",
+            (int)(0.5 + (float)(strtol(temp_data1,&temp_conv,16)*0.62137)));
+    } else { // type == APRS_WX6,  '*'  speed in mph
+        xastir_snprintf(weather->wx_speed,
+            sizeof(weather->wx_speed),
+            "%03d",
+            (int)(0.5 + (float)strtol(temp_data1,&temp_conv,16)));
+    }
+
+    if (from) {
+        weather->wx_speed_sec_time = sec_now();
+    } else {
+        /* local station */
+        computed_gust = compute_gust((float)atof(weather->wx_speed),
+                                last_speed,
+                                &last_speed_time);
+        weather->wx_speed_sec_time = sec_now();
+        xastir_snprintf(weather->wx_gust,
+            sizeof(weather->wx_gust),
+            "%03d",
+            (int)(0.5 + computed_gust));
+    }
+
+    /* outdoor temp */
+    if (data[3] != '-') { // '-' signifies invalid data
+        int temp4;
+
+        substr(temp_data1,(char *)(data+3),2);
+        temp4 = (int)strtol(temp_data1,&temp_conv,16);
+
+        if (temp_data1[0] > '7') {  // Negative value, convert
+            temp4 = (temp4 & (temp4-0x7FFF)) - 0x8000;
+        }
+
+        xastir_snprintf(weather->wx_temp,
+            sizeof(weather->wx_temp),
+            "%03d",
+            temp4-56);
+    } else {
+        if (!from)
+            weather->wx_temp[0] = 0;
+    }
+
+    // Rain divided by 100 for readings in hundredth of an inch
+    if (data[5] != '-') { // '-' signifies invalid data
+        substr(temp_data1,(char *)(data+5),4);
+        xastir_snprintf(weather->wx_rain_total,
+            sizeof(weather->wx_rain_total),
+            "%0.2f",
+            (float)strtol(temp_data1,&temp_conv,16)/100.0);
+        if (!from) {
+            /* local station */
+            compute_rain((float)atof(weather->wx_rain_total));
+            /*last hour rain */
+            xastir_snprintf(weather->wx_rain,
+                sizeof(weather->wx_rain),
+                "%0.2f",
+                rain_minute_total);
+            /*last 24 hour rain */
+            xastir_snprintf(weather->wx_prec_24,
+                sizeof(weather->wx_prec_24),
+                "%0.2f",
+                rain_24);
+            /* rain since midnight */
+            xastir_snprintf(weather->wx_prec_00,
+                sizeof(weather->wx_prec_00),
+                "%0.2f",
+                rain_00);
+        }
+    } else {
+        if (!from)
+            weather->wx_rain_total[0] = 0;
+    }
+}
+
+
+#endif //INCLUDE_APRS
diff --git a/src/aprs_decode.h b/src/aprs_decode.h
new file mode 100644 (file)
index 0000000..b871141
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Maemo Mapper is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Maemo Mapper.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * 
+ * Parts of this code have been ported from Xastir by Rob Williams (Aug 2008):
+ * 
+ *  * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000  Frank Giannandrea
+ * Copyright (C) 2000-2007  The Xastir Group
+ * 
+ */
+
+#ifdef HAVE_CONFIG_H
+#    include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#ifndef MAEMO_MAPPER_APRS_DECODE_H
+#define MAEMO_MAPPER_APRS_DECODE_H
+
+#define xastir_snprintf snprintf
+
+#include "types.h"
+
+
+
+void substr(char *dest, char *src, int size);
+gint decode_ax25_line(gchar *line, TAprsPort port);
+
+
+int extract_position(AprsDataRow *p_station, char **info, int type);
+time_t sec_now(void);
+void insert_name(AprsDataRow *p_new, AprsDataRow *p_name);
+double calc_distance_haversine_radian(double lat1, double lon1, double lat2, double lon2);
+void init_station_data(void);
+void init_station(AprsDataRow *p_station);
+char *get_tactical_from_hash(char *callsign);
+
+double convert_lat_l2d(long lat);
+double convert_lon_l2d(long lon);
+
+#endif
+
+#endif //INCLUDE_APRS
+
diff --git a/src/aprs_display.c b/src/aprs_display.c
new file mode 100644 (file)
index 0000000..b5c1818
--- /dev/null
@@ -0,0 +1,1106 @@
+/*
+ * Created by Rob Williams - 10 Aug 2008
+ * 
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Maemo Mapper is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Maemo Mapper.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ */
+
+#ifdef HAVE_CONFIG_H
+#    include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#include "aprs_display.h"
+#include "aprs_message.h"
+#include "types.h"
+#include "aprs.h"
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <dbus/dbus-glib.h>
+#include <bt-dbus.h>
+#include <gconf/gconf-client.h>
+
+#ifndef LEGACY
+#    include <hildon/hildon-help.h>
+#    include <hildon/hildon-note.h>
+#    include <hildon/hildon-color-button.h>
+#    include <hildon/hildon-file-chooser-dialog.h>
+#    include <hildon/hildon-number-editor.h>
+#    include <hildon/hildon-banner.h>
+#else
+#    include <osso-helplib.h>
+#    include <hildon-widgets/hildon-note.h>
+#    include <hildon-widgets/hildon-color-button.h>
+#    include <hildon-widgets/hildon-file-chooser-dialog.h>
+#    include <hildon-widgets/hildon-number-editor.h>
+#    include <hildon-widgets/hildon-banner.h>
+#    include <hildon-widgets/hildon-input-mode-hint.h>
+#endif
+
+#include "types.h"
+#include "data.h"
+#include "defines.h"
+
+#include "gps.h"
+#include "display.h"
+#include "gdk-pixbuf-rotate.h"
+#include "maps.h"
+#include "marshal.h"
+#include "poi.h"
+#include "settings.h"
+#include "util.h"
+
+extern AprsDataRow *n_first;
+
+typedef struct _AprsStationSelectInfo AprsStationSelectInfo;
+struct _AprsStationSelectInfo
+{
+    GtkWidget *dialog;
+    GtkWidget *tree_view;
+    gint      column_index;
+    gchar     *call_sign;
+};
+
+static AprsStationSelectInfo selected_station;
+
+
+
+double convert_lat_l2d(long lat);
+double convert_lon_l2d(long lon);
+static gboolean panto_station(GtkWidget *widget, AprsStationSelectInfo *aprs_station_sel);
+
+void convert_temp_f_to_c(gchar * f, gchar ** c)
+{
+       *c = g_strdup("        ");
+       
+       gdouble df = 0.0;
+       gdouble dc = 0.0;
+       
+       // Convert fahrenheit to fahrenheit (double)
+       df = g_ascii_strtod ( f, (gchar*)(f + strlen(f)));
+       
+       // Convert ff to fc
+       dc = 5*((df - 32.0)/9);
+       
+       // Convert fc to celsius
+       snprintf(*c, 8, "%0.1f°C", dc);
+}
+
+void setup_aprs_basic_wx_display_page(GtkWidget *notebook, AprsDataRow *p_station)
+{
+    GtkWidget *table;
+    GtkWidget *label;
+    
+
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+        table = gtk_table_new(4/*rows*/, 4/*columns*/, FALSE/*All cells same size*/),
+        label = gtk_label_new(_("WX")));
+
+    /* Last update */    
+    gchar last_update_time[26];
+    
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new("Updated:"),
+            0, 1, 0, 1, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    
+    
+    if(p_station->weather_data && p_station->weather_data->wx_sec_time)
+    {
+        strftime(last_update_time, 25, "%x %X", localtime(&p_station->weather_data->wx_sec_time));
+    }
+    else
+    {
+       snprintf(last_update_time, 25, " ");
+    }
+    
+    gtk_table_attach(GTK_TABLE(table),
+               label = gtk_label_new( last_update_time ),
+            1, 4, 0, 1, GTK_EXPAND |GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+        
+    
+    /* Temperature */
+    gchar * temp = NULL;
+    
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new("Temp:"),
+            0, 1, 1, 2, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    
+    if(p_station->weather_data && p_station->weather_data->wx_temp)
+    {
+       convert_temp_f_to_c(p_station->weather_data->wx_temp, &temp);
+    }
+    else
+    {
+       temp = g_strdup("");
+    }
+    
+    gtk_table_attach(GTK_TABLE(table),
+               label = gtk_label_new( temp ),
+            1, 2, 1, 2, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+    
+    g_free(temp);
+    
+    /////////////////
+
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new( (p_station->weather_data->wx_storm ? "SEVERE STORM" : "")  ),
+            2, 4, 1, 2, GTK_EXPAND |GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.5f, 0.5f);
+
+
+    /////
+    gchar course[7];
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new( "Wind course:"  ),
+            0, 1, 2, 3, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+    
+    snprintf(course, 6, "%0.f°", 
+               g_ascii_strtod (p_station->weather_data->wx_course, p_station->weather_data->wx_course + strlen(p_station->weather_data->wx_course))
+               );
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new( course ),
+            1, 2, 2, 3, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+    
+//    g_free(course);
+    
+    /////
+    gchar speed[15];
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new( "Wind speed:"  ),
+            2, 3, 2, 3, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    snprintf(speed, 14, "%0.f->%0.f MPH",
+               g_ascii_strtod (p_station->weather_data->wx_speed, p_station->weather_data->wx_speed + strlen(p_station->weather_data->wx_speed)),
+               g_ascii_strtod (p_station->weather_data->wx_gust, p_station->weather_data->wx_gust + strlen(p_station->weather_data->wx_gust))
+               );
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new( speed ),
+            3, 4, 2, 3, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+    
+  
+
+    
+    /////
+    gchar rain_ph[17];
+    gchar rain_total[17];
+    
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new( "Rain fall:"  ),
+            0, 1, 3, 4, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    if(p_station->weather_data->wx_rain)
+    {
+           snprintf(rain_ph, 16, "%0.f \"/hr",
+                       g_ascii_strtod (p_station->weather_data->wx_rain, p_station->weather_data->wx_rain + strlen(p_station->weather_data->wx_rain))
+                       );
+    }
+    else
+    {
+       snprintf(rain_ph, 1, " ");
+    }
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new( rain_ph ),
+            1, 2, 3, 4, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+    
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new( "Total:"  ),
+            2, 3, 3, 4, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    if(p_station->weather_data->wx_rain_total)
+    {
+           snprintf(rain_total, 16, "%0.f \"",
+                       g_ascii_strtod (p_station->weather_data->wx_rain_total, p_station->weather_data->wx_rain_total + strlen(p_station->weather_data->wx_rain_total))
+                       );
+    }
+    else
+    {
+       snprintf(rain_total, 1, " ");
+    }
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new( rain_total ),
+            3, 4, 3, 4, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+    
+    
+    
+    /*
+
+    char    wx_hurricane_radius[4];  //nautical miles       3
+    char    wx_trop_storm_radius[4]; //nautical miles       3
+    char    wx_whole_gale_radius[4]; // nautical miles      3
+    char    wx_snow[6];         // in inches/24h            3
+    char    wx_prec_24[10];     // in hundredths inch/day   3
+    char    wx_prec_00[10];     // in hundredths inch       3
+    char    wx_hum[5];          // in %                     3
+    char    wx_baro[10];        // in hPa                   6
+    char    wx_fuel_temp[5];    // in Â°F                    3
+    char    wx_fuel_moisture[5];// in %                     2
+    char    wx_type;
+    char    wx_station[MAX_WXSTATION];
+*/
+
+}
+
+void setup_aprs_moving_display_page(GtkWidget *notebook, AprsDataRow *p_station)
+{
+    GtkWidget *table;
+    GtkWidget *label;
+
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+        table = gtk_table_new(4/*rows*/, 4/*columns*/, FALSE/*All cells same size*/),
+        label = gtk_label_new(_("Moving")));
+
+
+    ////////////
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new("Speed:"),
+            0, 1, 0, 1, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+  
+    gchar speed[15];
+    
+//    snprintf(speed, sizeof(speed), "%.01f %s", atof(p_station->speed) * UNITS_CONVERT[_units], 
+//             UNITS_ENUM_TEXT[_units]);
+    
+    if(_units == UNITS_NM)
+        snprintf(speed, sizeof(speed), "%.01f nmph", atof(p_station->speed));
+    else if(_units == UNITS_KM)
+       snprintf(speed, sizeof(speed), "%.01f kph", atof(p_station->speed)*1.852);
+    else if(_units == UNITS_MI)
+        snprintf(speed, sizeof(speed), "%.01f mph", atof(p_station->speed)*1.1508);
+
+    
+    gtk_table_attach(GTK_TABLE(table),
+               label = gtk_label_new( speed ),
+            1, 2, 0, 1, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+    
+    
+
+    ////////////
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new("Course:"),
+            0, 1, 1, 2, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    
+    gtk_table_attach(GTK_TABLE(table),
+               label = gtk_label_new( p_station->course ),
+            1, 2, 1, 2, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+    
+    ////////////
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new("Alt (m):"),
+            0, 1, 2, 3, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    
+    gtk_table_attach(GTK_TABLE(table),
+               label = gtk_label_new( p_station->altitude ),
+            1, 2, 2, 3, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+    ////////////
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new("Bearing:"),
+            0, 1, 3, 4, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    
+    gtk_table_attach(GTK_TABLE(table),
+               label = gtk_label_new( p_station->bearing ),
+            1, 2, 3, 4, GTK_FILL, 0, 2, 4);
+    
+    /*
+    GdkPixmap *pixmap=gdk_pixmap_new(table->window,100,100,16);
+    gdk_drawable_set_colormap (pixmap,gdk_colormap_get_system ());
+
+
+    GtkWidget *image = NULL;
+    GdkColor color;
+    GdkGC * gc;
+    gc = gdk_gc_new(pixmap);
+    color.red = 0;
+    color.green = 0;
+    color.blue = 0;
+
+    gdk_gc_set_foreground(gc, &color);
+    
+    color.red = 255;
+    color.green = 255;
+    color.blue = 255;
+    gdk_gc_set_background(gc, &color);
+
+    
+    gdk_gc_set_line_attributes(gc, _draw_width, GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND);
+            
+       gdk_draw_arc (
+                       pixmap, 
+                       gc,
+                       FALSE,
+                       2, 2,
+                       96, 96,
+                       0, 360*64
+                       );
+       
+       gdouble heading = deg2rad(atof(p_station->course));
+       gint y = (gint)(48.0 * cosf(heading) );
+       gint x = (gint)(48.0 * sinf(heading) );
+       
+       gdk_draw_line (
+                       pixmap, 
+                       gc,
+                       50, 50,
+                       50+x, 50-y);
+         
+    gtk_table_attach(GTK_TABLE(table),
+               image = gtk_image_new_from_pixmap(pixmap, NULL), 
+               2, 3, 0, 4, GTK_SHRINK, GTK_SHRINK, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(image), 1.0f, 0.5f);
+     */ 
+    
+//    g_object_unref(image);
+//    gdk_pixmap_unref(pixmap);
+}
+
+
+void setup_aprs_station_stats_page(GtkWidget *notebook, AprsDataRow *p_station)
+{
+    GtkWidget *table;
+    GtkWidget *label;
+    gchar distance[15];
+    gchar lat[15], lon[15];
+    gchar course_deg[9];
+
+    
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+        table = gtk_table_new(4/*rows*/, 5/*columns*/, FALSE/*All cells same size*/),
+        label = gtk_label_new(_("Location")));
+
+    ////////////
+    
+    course_deg[0] = '\0';
+    distance[0] = '\0';
+    lat[0] = '\0';
+    lon[0] = '\0';
+    
+
+       if(p_station->coord_lat != 0 || p_station->coord_lon != 0)
+       {
+               gdouble d_lat = convert_lat_l2d(p_station->coord_lat);
+               gdouble d_lon = convert_lon_l2d(p_station->coord_lon);
+
+               format_lat_lon(d_lat, d_lon, lat, lon);
+           
+               gfloat dist = (float)calculate_distance(_gps.lat, _gps.lon, d_lat, d_lon);
+               
+               
+               snprintf(distance, sizeof(distance), "%.01f %s", dist * UNITS_CONVERT[_units], UNITS_ENUM_TEXT[_units]);
+
+               snprintf(course_deg,  sizeof(course_deg),
+                               "%.01f°",
+                               calculate_bearing(_gps.lat, _gps.lon, d_lat, d_lon));
+       }
+
+    /* Last heard */    
+    gchar last_update_time[26];
+    
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new("Last Heard:"),
+            0, 1, 4, 5, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    
+    
+    if(p_station->sec_heard)
+    {
+        strftime(last_update_time, 25, "%x %X", localtime(&p_station->sec_heard));
+    }
+    else
+    {
+       snprintf(last_update_time, 25, " ");
+    }
+    
+    gtk_table_attach(GTK_TABLE(table),
+               label = gtk_label_new( last_update_time ),
+            1, 4, 4, 5, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+        
+
+    
+       ////////////
+       gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new("Location:"),
+            0, 1, 1, 2, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    gtk_table_attach(GTK_TABLE(table),
+               label = gtk_label_new( lat ),
+            1, 3, 1, 2, GTK_EXPAND |GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+    gtk_table_attach(GTK_TABLE(table),
+               label = gtk_label_new( lon ),
+            3, 5, 1, 2, GTK_EXPAND |GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+    ////////////
+    
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new(""),
+            0, 5, 2, 3, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    
+    ////////////
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new("Distance:"),
+            0, 1, 3, 4, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    gtk_table_attach(GTK_TABLE(table),
+               label = gtk_label_new( distance ),
+            1, 2, 3, 4, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+    //
+    
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new("Bearing:"),
+            2, 4, 3, 4, GTK_EXPAND |GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    gtk_table_attach(GTK_TABLE(table),
+               label = gtk_label_new( course_deg ),
+            4, 5, 3, 4, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+    ////////////
+    
+}
+
+
+void setup_aprs_basic_display_page(GtkWidget *notebook, AprsDataRow *p_station)
+{
+    GtkWidget *table;
+    GtkWidget *label;
+
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+        table = gtk_table_new(5/*rows*/, 4/*columns*/, FALSE/*All cells same size*/),
+        label = gtk_label_new(_("Basic")));
+
+    /* Callsign. */
+    // Label
+    
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new("Callsign:"),
+            0, 1, 0, 1, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    
+    
+    gtk_table_attach(GTK_TABLE(table),
+
+               label = gtk_label_new( p_station->call_sign ),
+            1, 2, 0, 1, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+
+    
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new("Packets:"),
+            2, 3, 0, 1, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    
+    gchar packets[5];
+    snprintf(packets, 4, "%u", p_station->num_packets);
+    
+    gtk_table_attach(GTK_TABLE(table),
+               label = gtk_label_new( packets ),
+            3, 4, 0, 1, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 0.f, 0.5f);
+    
+    gtk_table_attach(GTK_TABLE(table),
+               label  = gtk_label_new("Comment:"),
+            0, 1, 1, 3, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label ), 1.f, 0.5f);
+    
+    gchar * comment = NULL;
+    
+    
+    if(p_station->comment_data && p_station->comment_data->text_ptr)
+    {
+       comment = g_strdup(p_station->comment_data->text_ptr);
+    }
+    else
+    {
+       comment = g_strdup("");
+    }
+    
+    gtk_table_attach(GTK_TABLE(table),
+               label  = gtk_label_new(comment),
+            1, 4, 1, 3, GTK_EXPAND |GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label ), 0.f, 0.5f);
+    gtk_label_set_width_chars(label, 30);
+    
+    
+    
+    //// 
+
+    gtk_table_attach(GTK_TABLE(table),
+               label  = gtk_label_new("Status:"),
+            0, 1, 3, 4, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label ), 1.f, 0.5f);
+    
+    gchar * status = NULL;
+    
+    
+    if(p_station->status_data && p_station->status_data->text_ptr)
+    {
+       status = g_strdup(p_station->status_data->text_ptr);
+    }
+    else
+    {
+       status = g_strdup("");
+    }
+    
+    gtk_table_attach(GTK_TABLE(table),
+               label  = gtk_label_new(status),
+            1, 4, 3, 4, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label ), 0.f, 0.5f);
+    
+    
+    //// 
+
+    gtk_table_attach(GTK_TABLE(table),
+               label  = gtk_label_new("Path:"),
+            0, 1, 4, 5, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label ), 1.f, 0.5f);
+    
+    gchar * path = NULL;
+    
+    if(p_station->node_path_ptr)
+    {
+       path = g_strdup(p_station->node_path_ptr);
+    }
+    else
+    {
+       path = g_strdup("");
+    }
+    
+    gtk_table_attach(GTK_TABLE(table),
+               label  = gtk_label_new(path),
+            1, 4, 4, 5, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label ), 0.f, 0.5f);
+        
+    
+
+    
+}
+
+
+
+void ShowAprsStationPopup(AprsDataRow *p_station)
+{
+       GtkWidget *dialog = NULL;
+       GtkWidget *notebook = NULL;
+       GtkWidget *btn_panto = NULL;
+
+       dialog = gtk_dialog_new_with_buttons(_("Station Details"),
+                GTK_WINDOW(_window), GTK_DIALOG_MODAL,
+                GTK_STOCK_CLOSE, GTK_RESPONSE_ACCEPT,
+//                "Send Message...", GTK_RESPONSE_ACCEPT,
+                NULL);
+       
+    
+    gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area),
+               btn_panto = gtk_button_new_with_mnemonic(_("C_entre Map...")));
+            
+    
+    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
+            notebook = gtk_notebook_new(), TRUE, TRUE, 0);
+       
+
+    selected_station.dialog = NULL;
+    selected_station.tree_view = NULL;
+    selected_station.column_index = 0;
+    selected_station.call_sign = p_station->call_sign;
+    
+    g_signal_connect(G_OBJECT(btn_panto), "clicked",
+               G_CALLBACK(panto_station), &selected_station);
+    
+    
+       setup_aprs_basic_display_page(notebook, p_station);
+       
+       
+       if(p_station->weather_data )
+               setup_aprs_basic_wx_display_page(notebook, p_station);
+
+       
+       
+       if( ( p_station->flag & ST_MOVING) == ST_MOVING){
+               setup_aprs_moving_display_page(notebook, p_station);
+       }
+       
+       
+       
+       setup_aprs_station_stats_page(notebook, p_station);
+       
+    gtk_widget_show_all(dialog);
+    gtk_dialog_run(GTK_DIALOG(dialog));  
+    gtk_widget_hide(dialog);
+    
+
+}
+
+
+
+
+
+void list_stations()
+{
+    static GtkWidget *dialog = NULL;
+    static GtkWidget *list = NULL;
+    static GtkWidget *sw = NULL;
+    static GtkWidget *btn_panto = NULL;
+    static GtkTreeViewColumn *column = NULL;
+    static GtkCellRenderer *renderer = NULL;
+    GtkListStore *store = NULL;
+    GtkTreeIter iter;
+    gint station_count = 0;
+
+    gint num_cats = 0;
+
+    printf("%s()\n", __PRETTY_FUNCTION__);
+    
+    typedef enum
+    {
+        STATION_CALLSIGN,
+        STATION_DISTANCE,
+        STATION_BEARING,
+        STATION_COMMENT,
+        STATION_DISTANCE_NUM,
+        STATION_NUM_COLUMNS
+    } StationList;
+    
+    /* Initialize store. */
+    store = gtk_list_store_new(STATION_NUM_COLUMNS,
+               G_TYPE_STRING,
+               G_TYPE_STRING,
+               G_TYPE_STRING,
+               G_TYPE_STRING,
+               G_TYPE_DOUBLE);/* Category Label */
+    AprsDataRow *p_station = n_first;
+
+    while ( (p_station) != NULL) 
+    {
+       station_count++;
+       
+       gchar * comment = NULL;
+       gchar * callsign = g_strdup(p_station->call_sign);
+       gchar course_deg[8];
+       gchar * formatted_distance = NULL;
+       gdouble distance = 0;
+       
+       course_deg[0] = '\0';
+        
+       
+       if(p_station->coord_lat != 0 && p_station->coord_lon != 0)
+       {
+               distance = distance_from_my_station(callsign, course_deg, sizeof(course_deg));
+               
+               if(_units == UNITS_KM)
+                       formatted_distance = g_strdup_printf("%.01f km", distance);
+               else if(_units == UNITS_MI)
+                       formatted_distance = g_strdup_printf("%.01f miles", distance);
+               else if(_units == UNITS_NM)
+                       formatted_distance = g_strdup_printf("%.01f nm", distance); 
+       }
+       else
+       {
+               formatted_distance = g_strdup_printf("");
+       }
+       
+       if(p_station->comment_data) comment = g_strdup(p_station->comment_data->text_ptr);
+       else comment = g_strdup("");
+       
+  
+        gtk_list_store_append(store, &iter);
+        gtk_list_store_set(store, &iter,
+                       STATION_CALLSIGN, callsign,
+                       STATION_DISTANCE, formatted_distance,
+                       STATION_BEARING, course_deg,
+                       STATION_COMMENT, comment,
+                       STATION_DISTANCE_NUM, distance,
+                -1);
+        num_cats++;
+        
+        g_free(comment);
+        g_free(callsign);
+        g_free(formatted_distance);
+        
+        (p_station) = (p_station)->n_next;  // Next element in list
+    } // End of while loop
+
+    gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), STATION_DISTANCE_NUM, GTK_SORT_ASCENDING);
+    
+
+
+    if(dialog == NULL)
+    {
+        dialog = gtk_dialog_new_with_buttons(_("Stations"),
+                GTK_WINDOW(_window), GTK_DIALOG_MODAL,
+                "Details", GTK_RESPONSE_ACCEPT,
+                GTK_STOCK_CLOSE, GTK_RESPONSE_REJECT,
+                NULL);
+
+
+        gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area),
+                       btn_panto = gtk_button_new_with_mnemonic(_("C_entre Map...")));
+                
+        
+        
+        gtk_window_set_default_size(GTK_WINDOW(dialog), 500, 300);
+
+        sw = gtk_scrolled_window_new (NULL, NULL);
+        
+        gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+                GTK_SHADOW_ETCHED_IN);
+        gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+                GTK_POLICY_NEVER,
+                GTK_POLICY_AUTOMATIC);
+        gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
+                sw, TRUE, TRUE, 0);
+
+        list = gtk_tree_view_new();
+        gtk_container_add(GTK_CONTAINER(sw), list);
+
+        gtk_tree_selection_set_mode(
+                gtk_tree_view_get_selection(GTK_TREE_VIEW(list)),
+                GTK_SELECTION_SINGLE);
+        gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), TRUE);
+
+        
+        //////
+        renderer = gtk_cell_renderer_text_new();
+        column = gtk_tree_view_column_new_with_attributes(
+                _("Callsign"), renderer, "text", STATION_CALLSIGN, NULL);
+        gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
+
+        ///////
+        renderer = gtk_cell_renderer_text_new();
+        column = gtk_tree_view_column_new_with_attributes(
+                _("Distance"), renderer, "text", STATION_DISTANCE, NULL);
+        gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
+
+        /////////
+        renderer = gtk_cell_renderer_text_new();
+        column = gtk_tree_view_column_new_with_attributes(
+                _("Bearing"), renderer, "text", STATION_BEARING, NULL);
+        gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
+        
+
+        /////////
+        renderer = gtk_cell_renderer_text_new();
+        column = gtk_tree_view_column_new_with_attributes(
+                _("Comment"), renderer, "text", STATION_COMMENT, NULL);
+        gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
+        
+        
+
+        selected_station.dialog = dialog;
+        selected_station.tree_view = list;
+        selected_station.call_sign = NULL;
+        selected_station.column_index = STATION_CALLSIGN;
+        
+        g_signal_connect(G_OBJECT(btn_panto), "clicked",
+                       G_CALLBACK(panto_station), &selected_station);
+                
+    }
+
+    gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));
+    g_object_unref(G_OBJECT(store));
+
+    
+    //
+    gchar *title = g_strdup_printf("Stations (Total: %u)", station_count);
+    gtk_window_set_title(GTK_WINDOW(dialog), title);
+    g_free(title);
+    
+    gtk_widget_show_all(dialog);
+
+    while(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog)))
+    {
+
+       if(gtk_tree_selection_get_selected(
+                gtk_tree_view_get_selection(GTK_TREE_VIEW(list)),
+                NULL, &iter))
+        {
+               // Find the callsign
+               p_station = n_first;
+               while(p_station != NULL)
+               {
+                       gchar * callsign = NULL;
+                   gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
+                       STATION_CALLSIGN, &(callsign),
+                       -1);
+
+                   if(strcmp(p_station->call_sign,callsign) == 0)
+                       {
+                               ShowAprsStationPopup(p_station);
+                               break;
+                               
+                       }
+
+                       
+                   p_station = p_station->n_next;
+               }
+
+        }
+
+
+
+    }
+
+    gtk_widget_hide(dialog);
+
+    vprintf("%s(): return %d\n", __PRETTY_FUNCTION__, selected);
+}
+
+
+
+
+static gboolean
+send_message(GtkWidget *widget)
+{
+       fprintf(stderr, "Send message...");
+       return FALSE;
+}
+
+static gboolean
+panto_station(GtkWidget *widget, AprsStationSelectInfo *aprs_station_selected)
+{
+    GtkTreeModel *store;
+    GtkTreeIter iter;
+    GtkTreeSelection *selection;
+       gchar * callsign = NULL;
+    AprsDataRow *p_station = n_first;
+    
+
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    
+       if(aprs_station_selected->call_sign != NULL)
+               callsign = aprs_station_selected->call_sign;
+       else
+       {
+               store = gtk_tree_view_get_model(GTK_TREE_VIEW(aprs_station_selected->tree_view));
+           selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(aprs_station_selected->tree_view));
+           
+           if(gtk_tree_selection_get_selected(selection, &store, &iter))
+           {
+
+            gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
+                aprs_station_selected->column_index, &(callsign),
+                -1);
+
+           }
+       }
+       
+       // Now findout the location of callsign
+       
+       p_station = n_first;
+       while(p_station != NULL)
+       {
+        if(strcmp(p_station->call_sign,callsign) == 0)
+               {
+               if(p_station->coord_lat == 0 && p_station->coord_lon == 0)
+               {
+                       // Invalid position
+               }
+               else
+               {       
+                       gdouble d_lat = convert_lat_l2d(p_station->coord_lat);
+                       gdouble d_lon = convert_lon_l2d(p_station->coord_lon);
+                       Point unit;
+       
+                       
+                   latlon2unit(d_lat, d_lon, unit.unitx, unit.unity);
+       
+                   if(_center_mode > 0)
+                       gtk_check_menu_item_set_active(
+                               GTK_CHECK_MENU_ITEM(_menu_view_ac_none_item), TRUE);
+       
+                   map_center_unit(unit);
+
+               }
+       
+               
+                       break;
+                       
+               }
+
+        p_station = p_station->n_next;
+       }
+
+       
+               
+    vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+void list_messages()
+{
+       static GtkWidget *dialog = NULL;
+//     static GtkWidget *btn_send = NULL;
+    static GtkWidget *list = NULL;
+    static GtkWidget *sw = NULL;
+    static GtkTreeViewColumn *column = NULL;
+    static GtkCellRenderer *renderer = NULL;
+    GtkListStore *store = NULL;
+       GtkTreeIter iter;
+
+
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    typedef enum
+    {
+        MSG_FROM,
+        MSG_TO,
+        MSG_TEXT,
+        MSG_TIMESTAMP,
+        MSG_NUM_COLUMNS
+    } MessageList;
+    
+    /* Initialize store. */
+    store = gtk_list_store_new(MSG_NUM_COLUMNS,
+               G_TYPE_STRING,
+               G_TYPE_STRING,
+               G_TYPE_STRING,
+               G_TYPE_DOUBLE);
+
+    
+    // Loop through each message
+    
+    gint  i = 0;
+    for (i = 0; i < msg_index_end; i++) 
+    {
+       gtk_list_store_append(store, &iter);
+        gtk_list_store_set(store, &iter,
+                       MSG_FROM, msg_data[msg_index[i]].from_call_sign,
+                       MSG_TO,   msg_data[msg_index[i]].call_sign,
+                       MSG_TEXT, msg_data[msg_index[i]].message_line,
+                       MSG_TIMESTAMP, (gdouble)msg_data[msg_index[i]].sec_heard,
+                -1);
+        
+    }
+    
+    gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), MSG_TIMESTAMP, GTK_SORT_ASCENDING);
+        
+    if(dialog == NULL)
+    {
+        dialog = gtk_dialog_new_with_buttons(_("Messages"),
+                GTK_WINDOW(_window), GTK_DIALOG_MODAL,
+                GTK_STOCK_CLOSE, GTK_RESPONSE_REJECT,
+                NULL);
+        
+        gtk_window_set_default_size(GTK_WINDOW(dialog), 550, 300);
+
+//        gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area),
+//                     btn_send = gtk_button_new_with_label(_("Send...")));
+        
+//        g_signal_connect(G_OBJECT(btn_send), "clicked",
+//                        G_CALLBACK(send_message), dialog);
+        
+               sw = gtk_scrolled_window_new (NULL, NULL);
+               
+               gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+                       GTK_SHADOW_ETCHED_IN);
+               gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+                       GTK_POLICY_NEVER,
+                       GTK_POLICY_AUTOMATIC);
+               gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
+                       sw, TRUE, TRUE, 0);
+               
+       
+               list = gtk_tree_view_new();
+               gtk_container_add(GTK_CONTAINER(sw), list);
+       
+               gtk_tree_selection_set_mode(
+                       gtk_tree_view_get_selection(GTK_TREE_VIEW(list)),
+                       GTK_SELECTION_SINGLE);
+               gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), TRUE);
+
+        
+
+               renderer = gtk_cell_renderer_text_new();
+               column = gtk_tree_view_column_new_with_attributes(
+                       _("From"), renderer, "text", MSG_FROM, NULL);
+               gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
+
+               renderer = gtk_cell_renderer_text_new();
+               column = gtk_tree_view_column_new_with_attributes(
+                       _("To"), renderer, "text", MSG_TO, NULL);
+               gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
+
+               renderer = gtk_cell_renderer_text_new();
+               column = gtk_tree_view_column_new_with_attributes(
+                       _("Message"), renderer, "text", MSG_TEXT, NULL);
+               gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);
+               //////
+           }
+
+
+       gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));
+           g_object_unref(G_OBJECT(store));
+       
+    
+           gtk_widget_show_all(dialog);
+
+           while(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog)))
+           {
+           }
+
+           gtk_widget_hide(dialog);
+
+           vprintf("%s(): return %d\n", __PRETTY_FUNCTION__, selected);
+       
+}
+
+#endif //INCLUDE_APRS
\ No newline at end of file
diff --git a/src/aprs_display.h b/src/aprs_display.h
new file mode 100644 (file)
index 0000000..e7b3d3e
--- /dev/null
@@ -0,0 +1,41 @@
+
+/*
+ * Created by Rob Williams - 10 Aug 2008
+ * 
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Maemo Mapper is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Maemo Mapper.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#    include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#ifndef MAEMO_MAPPER_APRS_DISPLAY_H
+#define MAEMO_MAPPER_APRS_DISPLAY_H
+
+
+#include "types.h"
+
+void ShowAprsStationPopup(AprsDataRow *p_station);
+void list_stations();
+void list_messages();
+
+#endif
+
+#endif //INCLUDE_APRS
diff --git a/src/aprs_kiss.c b/src/aprs_kiss.c
new file mode 100644 (file)
index 0000000..62238a5
--- /dev/null
@@ -0,0 +1,1534 @@
+/*
+ * 
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Maemo Mapper is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Maemo Mapper.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * 
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ * 
+ *  * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000  Frank Giannandrea
+ * Copyright (C) 2000-2007  The Xastir Group
+ * 
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#    include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#include "aprs_kiss.h"
+#include "aprs.h"
+#include "defines.h"
+#include "data.h"
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <pthread.h>
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <signal.h>
+#include <termios.h>
+#include <pwd.h>
+#include <termios.h>
+#include <setjmp.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+
+#include <netinet/in.h>     // Moved ahead of inet.h as reports of some *BSD's not
+                            // including this as they should.
+
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <time.h>
+
+#define DBUS_API_SUBJECT_TO_CHANGE
+#include <dbus/dbus-glib.h>
+
+
+#define DISABLE_SETUID_PRIVILEGE do { \
+seteuid(getuid()); \
+setegid(getgid()); \
+} while(0)
+#define ENABLE_SETUID_PRIVILEGE do { \
+seteuid(euid); \
+setegid(egid); \
+} while(0)
+
+
+
+
+#define MAX_INPUT_QUEUE 1000
+gint decode_ax25_line(gchar *line, TAprsPort port);
+
+typedef struct
+{
+    pthread_mutex_t lock;
+    pthread_t threadID;
+} xastir_mutex;
+
+xastir_mutex connect_lock;              // Protects port_data[].thread_status and port_data[].connect_status
+
+// Read/write pointers for the circular input queue
+/*
+static int incoming_read_ptr = 0;
+static int incoming_write_ptr = 0;
+static int queue_depth = 0;
+static int push_count = 0;
+static int pop_count = 0;
+*/
+uid_t euid;
+gid_t egid;
+
+
+
+iface port_data;     // shared port data
+
+
+//WE7U2
+// We feed a raw 7-byte string into this routine.  It decodes the
+// callsign-SSID and tells us whether there are more callsigns after
+// this.  If the "asterisk" input parameter is nonzero it'll add an
+// asterisk to the callsign if it has been digipeated.  This
+// function is called by the decode_ax25_header() function.
+//
+// Inputs:  string          Raw input string
+//          asterisk        1 = add "digipeated" asterisk
+//
+// Outputs: callsign        Processed string
+//          returned int    1=more callsigns follow, 0=end of address field
+//
+gint decode_ax25_address(gchar *string, gchar *callsign, gint asterisk) {
+    gint i,j;
+    gchar ssid;
+    gchar t;
+    gint more = 0;
+    gint digipeated = 0;
+
+    // Shift each of the six callsign characters right one bit to
+    // convert to ASCII.  We also get rid of the extra spaces here.
+    j = 0;
+    for (i = 0; i < 6; i++) {
+        t = ((unsigned char)string[i] >> 1) & 0x7f;
+        if (t != ' ') {
+            callsign[j++] = t;
+        }
+    }
+
+    // Snag out the SSID byte to play with.  We need more than just
+    // the 4 SSID bits out of it.
+    ssid = (unsigned char)string[6];
+
+    // Check the digipeat bit
+    if ( (ssid & 0x80) && asterisk)
+        digipeated++;   // Has been digipeated
+
+    // Check whether it is the end of the address field
+    if ( !(ssid & 0x01) )
+        more++; // More callsigns to come after this one
+
+    // Snag the four SSID bits
+    ssid = (ssid >> 1) & 0x0f;
+
+    // Construct the SSID number and add it to the end of the
+    // callsign if non-zero.  If it's zero we don't add it.
+    if (ssid) {
+        callsign[j++] = '-';
+        if (ssid > 9) {
+            callsign[j++] = '1';
+        }
+        ssid = ssid % 10;
+        callsign[j++] = '0' + ssid;
+    }
+
+    // Add an asterisk if the packet has been digipeated through
+    // this callsign
+    if (digipeated)
+        callsign[j++] = '*';
+
+    // Terminate the string
+    callsign[j] = '\0';
+
+    return(more);
+}
+
+
+// Function which receives raw AX.25 packets from a KISS interface and
+// converts them to a printable TAPR-2 (more or less) style string.
+// We receive the packet with a KISS Frame End character at the
+// beginning and a "\0" character at the end.  We can end up with
+// multiple asterisks, one for each callsign that the packet was
+// digipeated through.  A few other TNC's put out this same sort of
+// format.
+//
+// Note about KISS & CRC's:  The TNC checks the CRC.  If bad, it
+// drops the packet.  If good, it sends it to the computer WITHOUT
+// the CRC bytes.  There's no way at the computer end to check
+// whether the packet was corrupted over the serial channel between
+// the TNC and the computer.  Upon sending a KISS packet to the TNC,
+// the TNC itself adds the CRC bytes back on before sending it over
+// the air.  In Xastir we can just assume that we're getting
+// error-free packets from the TNC, ignoring possible corruption
+// over the serial line.
+//
+// Some versions of KISS can encode the radio channel (for
+// multi-port TNC's) in the command byte.  How do we know we're
+// running those versions of KISS though?  Here are the KISS
+// variants that I've been able to discover to date:
+//
+// KISS               No CRC, one radio port
+//
+// SMACK              16-bit CRC, multiport TNC's
+//
+// KISS-CRC
+//
+// 6-PACK
+//
+// KISS Multi-drop (Kantronics) 8-bit XOR Checksum, multiport TNC's (AGWPE compatible)
+// BPQKISS (Multi-drop)         8-bit XOR Checksum, multiport TNC's
+// XKISS (Kantronics)           8-bit XOR Checksum, multiport TNC's
+//
+// JKISS              (AGWPE and BPQ32 compatible)
+//
+// MKISS              Linux driver which supports KISS/BPQ and
+//                    hardware handshaking?  Also Paccomm command to
+//                    immediately enter KISS mode.
+//
+// FlexKISS           -,
+// FlexCRC            -|-- These are all the same!
+// RMNC-KISS          -|
+// CRC-RMNC           -'
+//
+//
+// It appears that none of the above protocols implement any form of
+// hardware flow control.
+// 
+// 
+// Compare this function with interface.c:process_ax25_packet() to
+// see if we're missing anything important.
+//
+//
+// Inputs:  data_string         Raw string (must be MAX_LINE_SIZE or bigger)
+//          length              Length of raw string (may get changed here)
+//
+// Outputs: int                 0 if it is a bad packet,
+//                              1 if it is good
+//          data_string         Processed string
+//
+gint decode_ax25_header(
+               unsigned char *data_string, 
+               gint *length) {
+    gchar temp[20];
+    gchar result[MAX_LINE_SIZE+100];
+    gchar dest[15];
+    gint i, ptr;
+    gchar callsign[15];
+    gchar more;
+    gchar num_digis = 0;
+
+
+    // Do we have a string at all?
+    if (data_string == NULL)
+        return(0);
+
+    // Drop the packet if it is too long.  Note that for KISS packets
+    // we can't use strlen() as there can be 0x00 bytes in the
+    // data itself.
+    if (*length > 1024) {
+        data_string[0] = '\0';
+        *length = 0;
+        return(0);
+    }
+
+    // Start with an empty string for the result
+    result[0] = '\0';
+
+    ptr = 0;
+
+    // Process the destination address
+    for (i = 0; i < 7; i++)
+        temp[i] = data_string[ptr++];
+    temp[7] = '\0';
+    more = decode_ax25_address(temp, callsign, 0); // No asterisk
+    snprintf(dest,sizeof(dest),"%s",callsign);
+
+    // Process the source address
+    for (i = 0; i < 7; i++)
+        temp[i] = data_string[ptr++];
+    temp[7] = '\0';
+    more = decode_ax25_address(temp, callsign, 0); // No asterisk
+
+    // Store the two callsigns we have into "result" in the correct
+    // order
+    snprintf(result,sizeof(result),"%s>%s",callsign,dest);
+
+    // Process the digipeater addresses (if any)
+    num_digis = 0;
+    while (more && num_digis < 8) {
+        for (i = 0; i < 7; i++)
+            temp[i] = data_string[ptr++];
+        temp[7] = '\0';
+
+        more = decode_ax25_address(temp, callsign, 1); // Add asterisk
+        strncat(result,
+            ",",
+            sizeof(result) - strlen(result));
+        
+        strncat(result,
+            callsign,
+            sizeof(result) - strlen(result));
+        num_digis++;
+    }
+
+    strncat(result,
+        ":",
+        sizeof(result) - strlen(result));
+
+
+    // Check the Control and PID bytes and toss packets that are
+    // AX.25 connect/disconnect or information packets.  We only
+    // want to process UI packets in Xastir.
+
+
+    // Control byte should be 0x03 (UI Frame).  Strip the poll-bit
+    // from the PID byte before doing the comparison.
+    if ( (data_string[ptr++] & (~0x10)) != 0x03) {
+        return(0);
+    }
+
+
+    // PID byte should be 0xf0 (normal AX.25 text)
+    if (data_string[ptr++] != 0xf0)
+        return(0);
+
+
+// WE7U:  We get multiple concatenated KISS packets sometimes.  Look
+// for that here and flag when it happens (so we know about it and
+// can fix it someplace earlier in the process).  Correct the
+// current packet so we don't get the extra garbage tacked onto the
+// end.
+    for (i = ptr; i < *length; i++) {
+        if (data_string[i] == KISS_FEND) {
+            fprintf(stderr,"***Found concatenated KISS packets:***\n");
+            data_string[i] = '\0';    // Truncate the string
+            break;
+        }
+    }
+
+    // Add the Info field to the decoded header info
+    strncat(result,
+        (char *)(&data_string[ptr]),
+        sizeof(result) - strlen(result));
+
+    // Copy the result onto the top of the input data.  Note that
+    // the length can sometimes be longer than the input string, so
+    // we can't just use the "length" variable here or we'll
+    // truncate our string.  Make sure the data_string variable is
+    // MAX_LINE_SIZE or bigger.
+    //
+    snprintf((char *)data_string,
+        MAX_LINE_SIZE,
+        "%s",
+        result);
+
+    // Write out the new length
+    *length = strlen(result); 
+
+//fprintf(stderr,"%s\n",data_string);
+
+    return(1);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+// Added by KB6MER for KAM XL(SERIAL_TNC_AUX_GPS) support
+// buf is a null terminated string
+// returns buf as a null terminated string after cleaning.
+// Currently:
+//    removes leading 'cmd:' prompts from TNC if needed
+// Can be used to add any additional data cleaning functions desired.
+// Currently only called for SERIAL_TNC_AUX_GPS, but could be added
+// to other device routines to improve packet decode on other devices.
+//
+// Note that the length of "buf" can be up to MAX_DEVICE_BUFFER,
+// which is currently set to 4096.
+//
+void tnc_data_clean(gchar *buf) {
+
+    while (!strncmp(buf,"cmd:",4)) {
+        int ii;
+
+        // We're _shortening_ the string here, so we don't need to
+        // know the length of the buffer unless it has no '\0'
+        // terminator to begin with!  In that one case we could run
+        // off the end of the string and get a segfault or cause
+        // other problems.
+        for (ii = 0; ; ii++) {
+            buf[ii] = buf[ii+4];
+            if (buf[ii] == '\0')
+                break;
+        }
+    }
+}
+
+
+
+static gboolean aprs_parse_tty_packet(gchar *packet)
+{
+    decode_ax25_line(packet, APRS_PORT_TTY);
+
+    g_free(packet);
+
+    return FALSE;
+}
+
+
+static gboolean kiss_parse_packet(unsigned char *data_string, gint data_length)
+{
+       
+       
+       //fprintf(stderr, "Parse: %s\n", data_string);
+       
+       gint devicetype = port_data.device_type;
+       
+       
+       switch(devicetype)
+       {
+    case DEVICE_SERIAL_KISS_TNC:
+    case DEVICE_SERIAL_MKISS_TNC:
+           if ( !decode_ax25_header( data_string,
+                   &data_length ) ) {
+               // Had a problem decoding it.  Drop
+               // it on the floor.
+               break;
+           }
+           else {
+               // Good decode.  Drop through to the
+               // next block to log and decode the
+               // packet.
+           }
+       
+       case DEVICE_SERIAL_TNC:
+           tnc_data_clean((char *)data_string);
+
+       case DEVICE_AX25_TNC:   
+
+
+
+               
+//fprintf(stderr, "Decoded kiss: %s\n", data_string);
+      g_idle_add((GSourceFunc)aprs_parse_tty_packet, data_string);
+//             decode_ax25_line(data_string, "T", 0);
+               break;
+               
+       default:
+               break;
+       }
+
+    
+    
+       return FALSE; 
+}
+
+// Add one record to the circular queue.  Returns 1 if queue is
+// full, 0 if successful.
+//
+/*
+int push_incoming_data(unsigned char *data_string, int length) {
+       
+    int next_write_ptr = (incoming_write_ptr + 1) % MAX_INPUT_QUEUE;
+
+    // Check whether queue is full
+    if (incoming_read_ptr == next_write_ptr) {
+        // Yep, it's full!
+        return(1);
+    }
+
+    // Advance the write pointer
+    incoming_write_ptr = next_write_ptr;
+
+    incoming_data_queue[incoming_write_ptr].length = length;
+
+//    incoming_data_queue[incoming_write_ptr].port = port;
+
+    snprintf((char *)incoming_data_queue[incoming_write_ptr].data,
+        (length < MAX_LINE_SIZE) ? length : MAX_LINE_SIZE,
+        "%s",
+        data_string);
+
+    queue_depth++;
+    push_count++;
+
+    return(0);
+}
+*/
+
+
+
+
+
+
+
+
+
+//***********************************************************
+// channel_data()
+//
+// Takes data read in from a port and adds it to the
+// incoming_data_queue.  If queue is full, waits for queue to have
+// space before continuing.
+//
+// port #                                                    
+// string is the string of data
+// length is the length of the string.  If 0 then use strlen()
+// on the string itself to determine the length.
+//
+// Note that decode_ax25_header() and perhaps other routines may
+// increase the length of the string while processing.  We need to
+// send a COPY of our input string off to the decoding routines for
+// this reason, and the size of the buffer must be MAX_LINE_SIZE
+// for this reason also.
+//***********************************************************
+void channel_data(unsigned char *string, int length) {
+    int max;
+//    struct timeval tmv;
+    // Some messiness necessary because we're using xastir_mutex's
+    // instead of pthread_mutex_t's.
+    int process_it = 0;
+
+
+    //fprintf(stderr,"channel_data: %x %d\n",string[0],length);
+
+    
+    max = 0;
+
+    if (string == NULL)
+    {
+        return;
+    }
+
+    if (string[0] == '\0')
+    {
+        return;
+    }
+
+    if (length == 0) {
+        // Compute length of string including terminator
+        length = strlen((const char *)string) + 1;
+    }
+
+    // Check for excessively long packets.  These might be TCP/IP
+    // packets or concatenated APRS packets.  In any case it's some
+    // kind of garbage that we don't want to try to parse.
+
+    // Note that for binary data (WX stations and KISS packets), the
+    // strlen() function may not work correctly.
+    if (length > MAX_LINE_SIZE) {   // Too long!
+//     fprintf(stderr, "Too long");
+        string[0] = '\0';   // Truncate it to zero length
+        return;
+    }
+
+
+    // This protects channel_data from being run by more than one
+    // thread at the same time.
+
+    if (length > 0) {
+
+
+        // Install the cleanup routine for the case where this
+        // thread gets killed while the mutex is locked.  The
+        // cleanup routine initiates an unlock before the thread
+        // dies.  We must be in deferred cancellation mode for the
+        // thread to have this work properly.  We must first get the
+        // pthread_mutex_t address.
+
+
+
+        // If it's any of three types of GPS ports and is a GPRMC or
+        // GPGGA string, just stick it in one of two global
+        // variables for holding such strings.  UpdateTime() can
+        // come along and process/clear-out those strings at the
+        // gps_time interval.
+        //
+        process_it++;
+
+        // Remove the cleanup routine for the case where this thread
+        // gets killed while the mutex is locked.  The cleanup
+        // routine initiates an unlock before the thread dies.  We
+        // must be in deferred cancellation mode for the thread to
+        // have this work properly.
+  //      pthread_cleanup_pop(0);
+
+//fprintf(stderr,"Channel data on Port [%s]\n",(char *)string);
+
+        if (process_it) {
+
+            // Wait for empty space in queue
+//fprintf(stderr,"\n== %s", string);
+               
+               
+/*
+            while (push_incoming_data(string, length) && max < 5400) {
+                sched_yield();  // Yield to other threads
+                tmv.tv_sec = 0;
+                tmv.tv_usec = 2;  // 2 usec
+                (void)select(0,NULL,NULL,NULL,&tmv);
+                max++;
+            }
+*/
+               
+               kiss_parse_packet(g_strdup(string), length);
+            //g_idle_add((GSourceFunc)kiss_parse_packet, g_strdup(string));
+
+            
+        }
+//        else
+//        {
+//             fprintf(stderr,"Channel data on Port [%s]\n",(char *)string);
+//        }
+    }
+
+}
+
+
+
+
+//****************************************************************
+// get device name only (the portion at the end of the full path)
+// device_name current full name of device
+//****************************************************************
+
+char *get_device_name_only(char *device_name) {
+    int i,len,done;
+
+    if (device_name == NULL)
+        return(NULL);
+
+    done = 0;
+    len = (int)strlen(device_name);
+    for(i = len; i > 0 && !done; i--){
+        if(device_name[i] == '/'){
+            device_name += (i+1);
+            done = 1;
+        }
+    }
+    return(device_name);
+}
+
+
+int filethere(char *fn) {
+    FILE *f;
+    int ret;
+
+    ret =0;
+    f=fopen(fn,"r");
+    if (f != NULL) {
+        ret=1;
+        (void)fclose(f);
+    }
+    return(ret);
+}
+
+
+
+/*
+ * Close the serial port
+ * */
+gint serial_detach() {
+//    char fn[600];
+    int ok;
+    ok = -1;
+
+    if (port_data.active == DEVICE_IN_USE && port_data.status == DEVICE_UP) 
+    {
+        // Close port first
+        (void)tcsetattr(port_data.channel, TCSANOW, &port_data.t_old);
+        if (close(port_data.channel) == 0) 
+        {
+            port_data.status = DEVICE_DOWN;
+            usleep(200);
+            port_data.active = DEVICE_NOT_IN_USE;
+            ok = 1;
+
+        }
+        else 
+        {
+            fprintf(stderr,"Could not close port %s\n",port_data.device_name);
+
+            port_data.status = DEVICE_DOWN;
+            usleep(200);
+            port_data.active = DEVICE_NOT_IN_USE;
+
+        }
+
+        //update_interface_list();
+  /*      
+        // Delete lockfile
+        snprintf(fn, sizeof(fn), "/var/lock/LCK..%s", get_device_name_only(port_data.device_name));
+
+        ENABLE_SETUID_PRIVILEGE;
+        (void)unlink(fn);
+        DISABLE_SETUID_PRIVILEGE;
+*/
+    }
+
+    return(ok);
+}
+
+
+typedef struct {
+  char *adapter;  /* do not free this, it is freed somewhere else */
+  char *bonding;  /* allocated from heap, you must free this */
+} bonding_t;
+
+gboolean open_bluetooth_tty_connection(gchar *bda, gchar **aprs_bt_port)
+{
+       gint i, st, num_bondings = 0, num_rfcomms = 0, num_posdev = 0;
+       //gint j, k, num_classes = 0, bonding_cnt = 0,  
+       GError *error = NULL;
+       DBusGConnection *bus = NULL;
+       DBusGProxy *proxy = NULL;
+       //char **str_iter = NULL;
+       //char **tmp_bondings = 0, **tmp_classes = 0;
+       //char **adapters = 0;
+       gchar **rfcomms = 0;
+       bonding_t *bondings = 0; /* points to array of bonding_t */
+       bonding_t *posdev = 0; /* bondings with positioning bit on */
+       gchar *tmp;
+       //gchar *onoff;
+       const gchar const *spp="SPP";
+       //gint timeout;
+//     gchar *gpsd_prog;
+//     gchar *gpsd_ctrl_sock;
+  
+       /* Use the dbus interface to get the BT information */
+       
+
+#if (__GNUC__ > 2) && ((__GNUC__ > 3) || (__GNUC_MINOR__ > 2))
+#define ERRSTR(fmt, args...)                                     \
+  if (error_buf && error_buf_max_len>0) {                        \
+    set_error_msg(error_buf, error_buf_max_len, fmt, args);      \
+  } else {                                                       \
+    PDEBUG(fmt, args);                                           \
+  }
+#else
+#define ERRSTR(fmt, args...)                                     \
+  if (error_buf && error_buf_max_len>0) {                        \
+    set_error_msg(error_buf, error_buf_max_len, fmt, ##args);    \
+  } else {                                                       \
+    PDEBUG(fmt, ##args);                                         \
+  }
+#endif  
+
+
+       error = NULL;
+       bus = dbus_g_bus_get(DBUS_BUS_SYSTEM, &error);
+
+       if (error) 
+       {
+           st = -1;
+           errno = ECONNREFUSED; /* close enough :) */
+           //ERRSTR("%s", error->message);
+           //PDEBUG("Cannot get reply message [%s]\n", error->message);
+           goto OUT;
+    }
+
+       /* We need BT information only if the caller does not specify
+        * the BT address. If address is defined, it is assumed that
+        * it is already bonded and we just create RFCOMM connection
+        * to it.
+        */
+       if (!bda) 
+       {
+               // May want to support auto detect for serial ports?
+       } else {  /* if (!bda) */
+           /* Caller supplied BT address so use it */
+           num_posdev = 1;
+           posdev = calloc(1, sizeof(bonding_t *));
+           if (!posdev) 
+           {
+             st = -1;
+             errno = ENOMEM;
+             goto OUT;
+           }
+           posdev[0].bonding = strdup(bda);
+
+           /* Adapter information is not needed */
+           posdev[0].adapter = "<not avail>";
+       }
+       
+       /* For each bondend BT GPS device, try to create rfcomm */
+       for (i=0; i<num_posdev; i++) 
+       {
+               /* Note that bluez does not provide this interface (its defined but not
+            * yet implemented) so we use btcond for creating rfcomm device(s)
+            */
+
+           proxy = dbus_g_proxy_new_for_name(bus,
+               BTCOND_DBUS, BTCOND_PATH, BTCOND_INTERFACE);
+
+           error = NULL;
+           tmp = NULL;
+           if(!dbus_g_proxy_call(proxy, BTCOND_CONNECT, &error,
+                 G_TYPE_STRING, posdev[i].bonding,
+                 G_TYPE_STRING, spp,
+              G_TYPE_BOOLEAN, TRUE,
+                 G_TYPE_INVALID,
+                 G_TYPE_STRING, &tmp,
+                 G_TYPE_INVALID)
+               || error || !tmp || !*tmp) 
+           {
+               /*PDEBUG("dbus_g_proxy_call returned an error: (error=(%d,%s), tmp=%s\n",
+                 error ? error->code : -1,
+                 error ? error->message : "<null>",
+                 tmp ? tmp : "<null>");
+*/
+               /* No error if already connected */
+               if (error && !strstr(error->message,
+                   "com.nokia.btcond.error.connected")) 
+               {
+                       ERROR:
+                               fprintf(stderr, "Cannot send msg (service=%s, object=%s, interface=%s, "
+                                               "method=%s) [%s]\n",
+                                               BTCOND_DBUS,
+                                               BTCOND_PATH,
+                                               BTCOND_INTERFACE,
+                                               BTCOND_CONNECT,
+                                               error->message ? error->message : "<no error msg>");
+/*                             ERRSTR("Cannot send msg (service=%s, object=%s, interface=%s, "
+                                               "method=%s) [%s]\n",
+                                               BTCOND_DBUS,
+                                               BTCOND_PATH,
+                                               BTCOND_INTERFACE,
+                                               BTCOND_CONNECT,
+                                               error->message ? error->message : "<no error msg>");
+*/
+                               continue;
+               } 
+               else if(!tmp || !*tmp) 
+               {
+                       /* hack: rfcommX device name is at the end of error message */
+                       char *last_space = strstr(error->message, " rfcomm");
+                       if (!last_space) 
+                       {
+                               goto ERROR;
+                       }
+
+                       g_free(tmp);
+                       tmp = g_strdup_printf("/dev/%s", last_space+1);
+               }
+           }
+           g_object_unref(proxy);
+
+           if (tmp && tmp[0]) 
+           {
+               rfcomms = (char **)realloc(rfcomms, (num_rfcomms+1)*sizeof(char *));
+               if (!rfcomms) 
+               {
+                       st = -1;
+                       errno = ENOMEM;
+                       goto OUT;
+               }
+
+               rfcomms[num_rfcomms] = tmp;
+               num_rfcomms++;
+
+               fprintf(stderr, "BT addr=%s, RFCOMM %s now exists (adapter=%s)\n",
+                     posdev[i].bonding, tmp, posdev[i].adapter);
+
+               tmp = NULL;
+           }
+           else 
+           {
+               g_free(tmp);
+           }
+       }
+
+       if (num_rfcomms==0) 
+       {
+           /* serial device creation failed */
+           fprintf(stderr, "No rfcomm created\n");
+           st = -1;
+           errno = EINVAL;
+
+       } 
+       else 
+       {
+           /* Add null at the end */
+           rfcomms = (char **)realloc(rfcomms, (num_rfcomms+1)*sizeof(char *));
+           if (!rfcomms) 
+           {
+               st = -1;
+               errno = ENOMEM;
+
+           } 
+           else 
+           {
+               rfcomms[num_rfcomms] = NULL;
+
+               /* Just start the beast (to be done if everything is ok) */
+               st = 0;
+                               
+               *aprs_bt_port = g_strdup_printf("%s",rfcomms[0]);
+
+           }
+       }
+
+OUT:
+/*     if (adapters) 
+       {
+           g_strfreev(adapters);
+       }
+*/
+       if (posdev) 
+       {
+           for (i=0; i<num_posdev; i++) 
+           {
+               if (posdev[i].bonding) 
+               {
+                       free(posdev[i].bonding);
+                       memset(&posdev[i], 0, sizeof(bonding_t)); /* just in case */
+               }
+           }
+           free(posdev);
+           posdev = 0;
+       }
+
+       if (bondings) 
+       {
+           for (i=0; i<num_bondings; i++) 
+           {
+               if (bondings[i].bonding) 
+               {
+                       free(bondings[i].bonding);
+                       memset(&bondings[i], 0, sizeof(bonding_t)); /* just in case */
+               }
+           }
+           free(bondings);
+           bondings = 0;
+       }
+
+       if (rfcomms) 
+       {
+               for (i=0; i<num_rfcomms; i++) 
+               {
+                       if (rfcomms[i]) 
+                       {
+                               free(rfcomms[i]);
+                               rfcomms[i]=0;
+                       }
+          }
+          free(rfcomms);
+          rfcomms = 0;
+       }
+
+       if (bus) 
+       {
+               dbus_g_connection_unref(bus);
+       }
+
+       return st>-1;
+}
+
+
+
+
+
+
+
+//***********************************************************
+// Serial port INIT
+//***********************************************************
+int serial_init () {
+//    FILE *lock;
+    int speed;
+//    pid_t mypid = 0;
+//    int myintpid;
+//    char fn[600];
+//    uid_t user_id;
+//    struct passwd *user_info;
+//    char temp[100];
+//    char temp1[100];
+//    pid_t status;
+
+//    status = -9999;
+
+    
+    euid = geteuid();
+    egid = getegid();
+
+
+    // clear port_channel
+    port_data.channel = -1;
+
+    // clear port active
+    port_data.active = DEVICE_NOT_IN_USE;
+
+    // clear port status
+    port_data.status = DEVICE_DOWN;
+
+    // Show the latest status in the interface control dialog
+//    update_interface_list();
+
+    
+    
+    //gw-obex.h
+    if(_aprs_tnc_method == TNC_CONNECTION_BT)
+    {
+       // Bluetooth connection
+       gchar * aprs_bt_port = NULL;
+       
+       if(!open_bluetooth_tty_connection(_aprs_tnc_bt_mac, &aprs_bt_port))
+       {
+               fprintf(stderr, "Failed to connect to BT device\n");
+               // Failed to connect
+               return -1;
+       }
+       
+       
+       snprintf(port_data.device_name, MAX_DEVICE_NAME, aprs_bt_port);
+       g_free(aprs_bt_port);
+       
+       fprintf(stderr, "BT Port: %s\n", port_data.device_name);
+    }
+    else
+    {
+       snprintf(port_data.device_name, MAX_DEVICE_NAME, _aprs_tty_port);
+    }
+    
+    
+    port_data.device_type = DEVICE_SERIAL_KISS_TNC;
+    port_data.sp = B9600; 
+    
+
+    // check for lock file
+    
+    // Try to open the serial port now
+    ENABLE_SETUID_PRIVILEGE;
+    port_data.channel = open(port_data.device_name, O_RDWR|O_NOCTTY);
+    DISABLE_SETUID_PRIVILEGE;
+    if (port_data.channel == -1){
+
+        fprintf(stderr,"Could not open channel on port!\n");
+
+        return (-1);
+    }
+
+    // Attempt to create the lock file
+/*    snprintf(fn, sizeof(fn), "/var/lock/LCK..%s", get_device_name_only(port_data.device_name));
+
+    ENABLE_SETUID_PRIVILEGE;
+    lock = fopen(fn,"w");
+    DISABLE_SETUID_PRIVILEGE;
+    if (lock != NULL) {
+        // get my process id for lock file
+        mypid = getpid();
+
+        // get user info
+        user_id = getuid();
+        user_info = getpwuid(user_id);
+        snprintf(temp,
+            sizeof(temp),
+            "%s",
+            user_info->pw_name);
+
+        fprintf(lock,"%9d %s %s",(int)mypid,"xastir",temp);
+        (void)fclose(lock);
+        // We've successfully created our own lock file
+    }
+    else {
+        // lock failed
+        fprintf(stderr,"Warning:  Failed opening LCK file!  Continuing on...\n");
+
+    }
+*/
+    
+    // get port attributes for new and old
+    if (tcgetattr(port_data.channel, &port_data.t) != 0) {
+        fprintf(stderr,"Could not get t port attributes for port!\n");
+
+        // Here we should close the port and remove the lock.
+        serial_detach();
+
+        return (-1);
+    }
+
+    if (tcgetattr(port_data.channel, &port_data.t_old) != 0) {
+
+        fprintf(stderr,"Could not get t_old port attributes for port!\n");
+
+        // Here we should close the port and remove the lock.
+        serial_detach();
+
+        return (-1);
+    }
+
+    // set time outs
+    port_data.t.c_cc[VMIN] = (cc_t)1;
+    port_data.t.c_cc[VTIME] = (cc_t)2;
+
+    // set port flags
+    port_data.t.c_iflag &= ~(BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
+    port_data.t.c_iflag = (tcflag_t)(IGNBRK | IGNPAR);
+
+    port_data.t.c_oflag = (0);
+    port_data.t.c_lflag = (0);
+
+#ifdef    CBAUD
+    speed = (int)(port_data.t.c_cflag & CBAUD);
+#else   // CBAUD
+    speed = 0;
+#endif  // CBAUD
+    
+    port_data.t.c_cflag = (tcflag_t)(HUPCL|CLOCAL|CREAD);
+    port_data.t.c_cflag &= ~PARENB;
+    switch (port_data.style){
+        case(0):
+            // No parity (8N1)
+            port_data.t.c_cflag &= ~CSTOPB;
+            port_data.t.c_cflag &= ~CSIZE;
+            port_data.t.c_cflag |= CS8;
+            break;
+
+        case(1):
+            // Even parity (7E1)
+            port_data.t.c_cflag &= ~PARODD;
+            port_data.t.c_cflag &= ~CSTOPB;
+            port_data.t.c_cflag &= ~CSIZE;
+            port_data.t.c_cflag |= CS7;
+            break;
+
+        case(2):
+            // Odd parity (7O1):
+            port_data.t.c_cflag |= PARODD;
+            port_data.t.c_cflag &= ~CSTOPB;
+            port_data.t.c_cflag &= ~CSIZE;
+            port_data.t.c_cflag |= CS7;
+            break;
+
+        default:
+            break;
+    }
+
+    port_data.t.c_cflag |= speed;
+    // set input and out put speed
+    if (cfsetispeed(&port_data.t, port_data.sp) == -1) 
+    {
+       fprintf(stderr,"Could not set port input speed for port!\n");
+
+        // Here we should close the port and remove the lock.
+        serial_detach();
+
+        return (-1);
+    }
+
+    if (cfsetospeed(&port_data.t, port_data.sp) == -1) {
+
+        fprintf(stderr,"Could not set port output speed for port!\n");
+
+        // Here we should close the port and remove the lock.
+        serial_detach();
+
+        return (-1);
+    }
+
+    if (tcflush(port_data.channel, TCIFLUSH) == -1) {
+
+        fprintf(stderr,"Could not flush data for port!\n");
+
+        // Here we should close the port and remove the lock.
+        serial_detach();
+
+        return (-1);
+    }
+
+    if (tcsetattr(port_data.channel,TCSANOW, &port_data.t) == -1) 
+    {
+        fprintf(stderr,"Could not set port attributes for port!\n");
+
+        // Here we should close the port and remove the lock.
+        serial_detach();
+
+        return (-1);
+    }
+
+    // clear port active
+    port_data.active = DEVICE_IN_USE;
+
+    // clear port status
+    port_data.status = DEVICE_UP;
+
+    // Show the latest status in the interface control dialog
+//    update_interface_list();
+
+    // Ensure we are in KISS mode
+    if(port_data.device_type == DEVICE_SERIAL_KISS_TNC)
+    {
+       // Send KISS init string
+       gchar * cmd = g_strdup("\nINT KISS\nRESTART\n");
+       port_write_string(cmd, strlen(cmd), APRS_PORT_TTY);
+    }
+    
+    // return good condition
+    return (1);
+}
+
+
+//***********************************************************
+// port_read()
+//
+//
+// This function becomes the long-running thread that snags
+// characters from an interface and passes them off to the
+// decoding routines.  One copy of this is run for each read
+// thread for each interface.
+//***********************************************************
+
+void port_read() {
+    unsigned char cin, last;
+//    unsigned char buffer[MAX_DEVICE_BUFFER];    // Only used for AX.25 packets
+    gint i;
+    struct timeval tmv;
+    fd_set rd;
+//    gint group;
+//    gint max;
+    /*
+    * Some local variables used for checking AX.25 data - PE1DNN
+    *
+    * "from"     is used to look up where the data comes from
+    * "from_len" is used to keep the size of sockaddr structure
+    * "dev"      is used to keep the name of the interface that
+    *            belongs to our port/device_name
+    */
+//    struct sockaddr from;
+//    socklen_t from_len;
+
+ //   group = 0;
+//    max = MAX_DEVICE_BUFFER - 1;
+    cin = (unsigned char)0;
+    last = (unsigned char)0;
+    
+    
+
+    // We stay in this read loop until the port is shut down
+    while(port_data.active == DEVICE_IN_USE){
+
+        if (port_data.status == DEVICE_UP){
+
+            port_data.read_in_pos = 0;
+            port_data.scan = 1;
+
+            while (port_data.scan
+                    && (port_data.read_in_pos < (MAX_DEVICE_BUFFER - 1) )
+                    && (port_data.status == DEVICE_UP) ) {
+
+                int skip = 0;
+
+//                pthread_testcancel();   // Check for thread termination request
+                // Handle all EXCEPT AX25_TNC interfaces here
+                // Get one character
+                port_data.scan = (int)read(port_data.channel,&cin,1);
+//fprintf(stderr,"tty in:%02x ",cin);
+
+
+                // Below is code for ALL types of interfaces
+                if (port_data.scan > 0 && port_data.status == DEVICE_UP ) {
+
+                    if (port_data.device_type != DEVICE_AX25_TNC)
+                        port_data.bytes_input += port_data.scan;      // Add character to read buffer
+
+
+
+                    // Handle all EXCEPT AX25_TNC interfaces here
+                    if (port_data.device_type != DEVICE_AX25_TNC){
+
+
+                        // Do special KISS packet processing here.
+                        // We save the last character in
+                        // port_data.channel2, as it is
+                        // otherwise only used for AX.25 ports.
+
+                        if ( (port_data.device_type == DEVICE_SERIAL_KISS_TNC)
+                                || (port_data.device_type == DEVICE_SERIAL_MKISS_TNC) ) {
+
+
+                            if (port_data.channel2 == KISS_FESC) { // Frame Escape char
+                                if (cin == KISS_TFEND) { // Transposed Frame End char
+
+                                    // Save this char for next time
+                                    // around
+                                       port_data.channel2 = cin;
+
+                                    cin = KISS_FEND;
+                                }
+                                else if (cin == KISS_TFESC) { // Transposed Frame Escape char
+
+                                    // Save this char for next time
+                                    // around
+                                       port_data.channel2 = cin;
+
+                                    cin = KISS_FESC;
+                                }
+                                else {
+                                       port_data.channel2 = cin;
+                                }
+                            }
+                            else if (port_data.channel2 == KISS_FEND) { // Frame End char
+                                // Frame start or frame end.  Drop
+                                // the next character which should
+                                // either be another frame end or a
+                                // type byte.
+
+// Note this "type" byte is where it specifies which KISS interface
+// the packet came from.  We may want to use this later for
+// multi-drop KISS or other types of KISS protocols.
+
+                                // Save this char for next time
+                                // around
+                               port_data.channel2 = cin;
+
+                                skip++;
+                            }
+                            else if (cin == KISS_FESC) { // Frame Escape char
+                               port_data.channel2 = cin;
+                                skip++;
+                            }
+                            else {
+                               port_data.channel2 = cin;
+                            }
+                        }   // End of first special KISS processing
+
+
+                        // We shouldn't see any AX.25 flag
+                        // characters on a KISS interface because
+                        // they are stripped out by the KISS code.
+                        // What we should see though are KISS_FEND
+                        // characters at the beginning of each
+                        // packet.  These characters are where we
+                        // should break the data apart in order to
+                        // send strings to the decode routines.  It
+                        // may be just fine to still break it on \r
+                        // or \n chars, as the KISS_FEND should
+                        // appear immediately afterwards in
+                        // properly formed packets.
+
+
+                        if ( (!skip)
+                                && (cin == (unsigned char)'\r'
+                                    || cin == (unsigned char)'\n'
+                                    || port_data.read_in_pos >= (MAX_DEVICE_BUFFER - 1)
+                                    || ( (cin == KISS_FEND) && (port_data.device_type == DEVICE_SERIAL_KISS_TNC) )
+                                    || ( (cin == KISS_FEND) && (port_data.device_type == DEVICE_SERIAL_MKISS_TNC) ) )
+                               && port_data.data_type == 0) {     // If end-of-line
+
+// End serial/net type data send it to the decoder Put a terminating
+// zero at the end of the read-in data
+
+                               port_data.device_read_buffer[port_data.read_in_pos] = (char)0;
+
+                            if (port_data.status == DEVICE_UP && port_data.read_in_pos > 0) {
+                                int length;
+
+                                // Compute length of string in
+                                // circular queue
+
+                                //fprintf(stderr,"%d\t%d\n",port_data.read_in_pos,port_data.read_out_pos);
+
+                                // KISS TNC sends binary data
+                                if ( (port_data.device_type == DEVICE_SERIAL_KISS_TNC)
+                                        || (port_data.device_type == DEVICE_SERIAL_MKISS_TNC) ) {
+                                    length = port_data.read_in_pos - port_data.read_out_pos;
+                                    if (length < 0)
+                                        length = (length + MAX_DEVICE_BUFFER) % MAX_DEVICE_BUFFER;
+
+                                    length++;
+                                }
+                                else {  // ASCII data
+                                    length = 0;
+                                }
+
+                                channel_data(
+                                    (unsigned char *)port_data.device_read_buffer,
+                                    length);   // Length of string
+                            }
+
+                            for (i = 0; i <= port_data.read_in_pos; i++)
+                                port_data.device_read_buffer[i] = (char)0;
+
+                            port_data.read_in_pos = 0;
+                        }
+                        else if (!skip) {
+
+                            // Check for binary WX station data
+                            if (cin == '\0')    // OWW WX daemon sends 0x00's!
+                                cin = '\n';
+
+                            if (port_data.read_in_pos < (MAX_DEVICE_BUFFER - 1) ) {
+                                port_data.device_read_buffer[port_data.read_in_pos] = (char)cin;
+                                port_data.read_in_pos++;
+                                port_data.device_read_buffer[port_data.read_in_pos] = (char)0;
+                            }
+                            else {
+                                port_data.read_in_pos = 0;
+                            }
+                        }
+
+                    }   // End of non-AX.25 interface code block
+
+
+                }
+                else if (port_data.status == DEVICE_UP) {    /* error or close on read */
+                    port_data.errors++;
+                    if (port_data.scan == 0) {
+                        // Should not get this unless the device is down.  NOT TRUE!
+                        // We seem to also be able to get here if we're closing/restarting
+                        // another interface.  For that reason I commented out the below
+                        // statement so that this interface won't go down.  The inactivity
+                        // timer solves that issue now anyway.  --we7u.
+                        port_data.status = DEVICE_ERROR;
+
+                        // If the below statement is enabled, it causes an immediate reconnect
+                        // after one time-period of inactivity, currently 7.5 minutes, as set in
+                        // main.c:UpdateTime().  This means the symbol will never change from green
+                        // to red on the status bar, so the operator might not know about a
+                        // connection that is being constantly reconnected.  By leaving it commented
+                        // out we get one time period of red, and then it will reconnect at the 2nd
+                        // time period.  This means we can reconnect within 15 minutes if a line
+                        // goes dead.
+                        //
+                        port_data.reconnects = -1;     // Causes an immediate reconnect
+                        // Show the latest status in the interface control dialog
+//                        update_interface_list();
+
+                    } else {
+                        if (port_data.scan == -1) {
+                            /* Should only get this if an real error occurs */
+                            port_data.status = DEVICE_ERROR;
+
+                            // If the below statement is enabled, it causes an immediate reconnect
+                            // after one time-period of inactivity, currently 7.5 minutes, as set in
+                            // main.c:UpdateTime().  This means the symbol will never change from green
+                            // to red on the status bar, so the operator might not know about a
+                            // connection that is being constantly reconnected.  By leaving it commented
+                            // out we get one time period of red, and then it will reconnect at the 2nd
+                            // time period.  This means we can reconnect within 15 minutes if a line
+                            // goes dead.
+                            //
+                            port_data.reconnects = -1;     // Causes an immediate reconnect
+                            // Show the latest status in the
+                            // interface control dialog
+//                            update_interface_list();
+
+                        }
+                    }
+                }
+                
+                
+                /*
+                
+                // Send any packets queued
+                       // try to get lock, otherwise try next time
+                       if(g_mutex_trylock (_write_buffer[APRS_PORT_TTY].write_lock))
+                       {
+                       // Store the current end pointer as it may change
+                               
+                    while (_write_buffer[APRS_PORT_TTY].write_in_pos != _write_buffer[APRS_PORT_TTY].write_out_pos) {
+
+                       port_write_string(
+                               _write_buffer[APRS_PORT_TTY].device_write_buffer[_write_buffer[APRS_PORT_TTY].write_out_pos],
+                               APRS_PORT_TTY);
+
+                       _write_buffer[APRS_PORT_TTY].write_out_pos++;
+                        if (_write_buffer[APRS_PORT_TTY].write_out_pos >= MAX_DEVICE_BUFFER)
+                               _write_buffer[APRS_PORT_TTY].write_out_pos = 0;
+
+                    }
+
+                                       
+                   g_mutex_unlock(_write_buffer[APRS_PORT_TTY].write_lock);
+                       }            
+*/
+            }
+        }
+        if (port_data.active == DEVICE_IN_USE)  {
+
+            // We need to delay here so that the thread doesn't use
+            // high amounts of CPU doing nothing.
+
+// This select that waits on data and a timeout, so that if data
+// doesn't come in within a certain period of time, we wake up to
+// check whether the socket has gone down.  Else, we go back into
+// the select to wait for more data or a timeout.  FreeBSD has a
+// problem if this is less than 1ms.  Linux works ok down to 100us.
+// We don't need it anywhere near that short though.  We just need
+// to check whether the main thread has requested the interface be
+// closed, and so need to have this short enough to have reasonable
+// response time to the user.
+
+//sched_yield();  // Yield to other threads
+
+            // Set up the select to block until data ready or 100ms
+            // timeout, whichever occurs first.
+            FD_ZERO(&rd);
+            FD_SET(port_data.channel, &rd);
+            tmv.tv_sec = 0;
+            tmv.tv_usec = 100000;    // 100 ms
+            (void)select(0,&rd,NULL,NULL,&tmv);
+        }
+    }
+
+}
+
+#endif //INCLUDE_APRS
\ No newline at end of file
diff --git a/src/aprs_kiss.h b/src/aprs_kiss.h
new file mode 100644 (file)
index 0000000..0efd9f2
--- /dev/null
@@ -0,0 +1,19 @@
+
+#ifdef HAVE_CONFIG_H
+#    include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#ifndef MAEMO_MAPPER_APRS_KISS_H
+#define MAEMO_MAPPER_APRS_KISS_H
+
+
+
+
+
+
+#endif
+
+
+#endif //INCLUDE_APRS
diff --git a/src/aprs_message.c b/src/aprs_message.c
new file mode 100644 (file)
index 0000000..1975a4d
--- /dev/null
@@ -0,0 +1,2312 @@
+/*
+ * 
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Maemo Mapper is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Maemo Mapper.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * 
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ * 
+ *  * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000  Frank Giannandrea
+ * Copyright (C) 2000-2007  The Xastir Group
+ * 
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#    include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#include "aprs_message.h"
+#include "data.h"
+#include "aprs.h"
+
+
+/////////////////////////////////////////// Messages ///////////////////////////////////////////
+
+
+long *msg_index;
+long msg_index_end;
+static long msg_index_max;
+
+int log_wx_alert_data = 0;
+
+Message *msg_data; // Array containing all messages,
+                          // including ones we've transmitted (via
+                          // loopback in the code)
+
+time_t last_message_update = 0;
+ack_record *ack_list_head = NULL;  // Head of linked list storing most recent ack's
+int satellite_ack_mode;
+
+int  new_message_data;
+time_t last_message_remove;     // last time we did a check for message removing
+
+// How often update_messages() will run, in seconds.
+// This is necessary because routines like UpdateTime()
+// call update_messages() VERY OFTEN.
+//
+// Actually, we just changed the code around so that we only call
+// update_messages() with the force option, and only when we receive a
+// message.  message_update_delay is no longer used, and we don't call
+// update_messages() from UpdateTime() anymore.
+static int message_update_delay = 300;
+
+
+
+
+char *remove_trailing_spaces(char *data);
+void update_messages(int force);
+
+
+
+
+void clear_acked_message(char *from, char *to, char *seq) {
+       // TODO - replace stub
+}
+
+int check_popup_window(char *from_call_sign, int group) {
+       // TODO - replace stub
+       return 1;
+}
+
+void bulletin_data_add(char *call_sign, char *from_call, char *data, char *seq, char type, TAprsPort port)
+{
+       // TODO - replace stub
+}
+
+int look_for_open_group_data(char *to) {
+       // TODO - replace stub
+       return 0;
+}
+
+void get_send_message_path(char *callsign, char *path, int path_size)
+{
+       // TODO - replace stub
+}
+
+void transmit_message_data_delayed(char *to, char *message,
+                                   char *path, time_t when) {
+       // TODO - replace stub
+}
+
+int process_directed_query(char *call,char *path,char *message,TAprsPort port) {
+       // TODO - replace stub
+       return 0;
+}
+
+void transmit_message_data(char *to, char *message, char *path)
+{
+       // TODO - replace stub
+}
+
+void popup_message(char *banner, char *message)
+{
+       
+}
+
+// Saves latest ack in a linked list.  We need this value in order
+// to use Reply/Ack protocol when sending out messages.
+void store_most_recent_ack(char *callsign, char *ack) {
+    ack_record *p;
+    int done = 0;
+    char call[MAX_CALLSIGN+1];
+    char new_ack[5+1];
+
+    snprintf(call,
+        sizeof(call),
+        "%s",
+        callsign);
+    remove_trailing_spaces(call);
+
+    // Get a copy of "ack".  We might need to change it.
+    snprintf(new_ack,
+        sizeof(new_ack),
+        "%s",
+        ack);
+
+    // If it's more than 2 characters long, we can't use it for
+    // Reply/Ack protocol as there's only space enough for two.
+    // In this case we need to make sure that we blank out any
+    // former ack that was 1 or 2 characters, so that communications
+    // doesn't stop.
+    if ( strlen(new_ack) > 2 ) {
+        // It's too long, blank it out so that gets saved as "",
+        // which will overwrite any previously saved ack's that were
+        // short enough to use.
+        new_ack[0] = '\0';
+    }
+
+    // Search for matching callsign through linked list
+    p = ack_list_head;
+    while ( !done && (p != NULL) ) {
+        if (strcasecmp(call,p->callsign) == 0) {
+            done++;
+        }
+        else {
+            p = p->next;
+        }
+    }
+
+    if (done) { // Found it.  Update the ack field.
+        //fprintf(stderr,"Found callsign %s on recent ack list, Old:%s, New:%s\n",call,p->ack,new_ack);
+        snprintf(p->ack,sizeof(p->ack),"%s",new_ack);
+    }
+    else {  // Not found.  Add a new record to the beginning of the
+            // list.
+        //fprintf(stderr,"New callsign %s, adding to list.  Ack: %s\n",call,new_ack);
+        p = (ack_record *)malloc(sizeof(ack_record));
+        CHECKMALLOC(p);
+
+        snprintf(p->callsign,sizeof(p->callsign),"%s",call);
+        snprintf(p->ack,sizeof(p->ack),"%s",new_ack);
+        p->next = ack_list_head;
+        ack_list_head = p;
+    }
+}
+
+
+
+
+
+// Gets latest ack by callsign
+char *get_most_recent_ack(char *callsign) {
+    ack_record *p;
+    int done = 0;
+    char call[MAX_CALLSIGN+1];
+
+    snprintf(call,
+        sizeof(call),
+        "%s",
+        callsign);
+    remove_trailing_spaces(call);
+
+    // Search for matching callsign through linked list
+    p = ack_list_head;
+    while ( !done && (p != NULL) ) {
+        if (strcasecmp(call,p->callsign) == 0) {
+            done++;
+        }
+        else {
+            p = p->next;
+        }
+    }
+
+    if (done) { // Found it.  Return pointer to ack string.
+        //fprintf(stderr,"Found callsign %s on linked list, returning ack: %s\n",call,p->ack);
+        return(&p->ack[0]);
+    }
+    else {
+        //fprintf(stderr,"Callsign %s not found\n",call);
+        return(NULL);
+    }
+}
+
+
+
+
+
+void init_message_data(void) {  // called at start of main
+
+    new_message_data = 0;
+    last_message_remove = sec_now();
+}
+
+
+
+
+
+
+void msg_clear_data(Message *clear) {
+    int size;
+    int i;
+    unsigned char *data_ptr;
+
+    data_ptr = (unsigned char *)clear;
+    size=sizeof(Message);
+    for(i=0;i<size;i++)
+        *data_ptr++ = 0;
+}
+
+
+
+#ifdef MSG_DEBUG
+
+void msg_copy_data(Message *to, Message *from) {
+    int size;
+    int i;
+    unsigned char *data_ptr;
+    unsigned char *data_ptr_from;
+
+    data_ptr = (unsigned char *)to;
+    data_ptr_from = (unsigned char *)from;
+    size=sizeof(Message);
+    for(i=0;i<size;i++)
+        *data_ptr++ = *data_ptr_from++;
+}
+#endif /* MSG_DEBUG */
+
+
+
+
+
+// Returns 1 if it's time to update the messages again
+int message_update_time (void) {
+    if ( sec_now() > (last_message_update + message_update_delay) )
+        return(1);
+    else
+        return(0);
+}
+
+
+
+
+
+int msg_comp_active(const void *a, const void *b) {
+    char temp_a[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+2];
+    char temp_b[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+2];
+
+    snprintf(temp_a, sizeof(temp_a), "%c%s%s%s",
+            ((Message*)a)->active, ((Message*)a)->call_sign,
+            ((Message*)a)->from_call_sign,
+            ((Message*)a)->seq);
+    snprintf(temp_b, sizeof(temp_b), "%c%s%s%s",
+            ((Message*)b)->active, ((Message*)b)->call_sign,
+            ((Message*)b)->from_call_sign,
+            ((Message*)b)->seq);
+
+    return(strcmp(temp_a, temp_b));
+}
+
+
+
+
+
+int msg_comp_data(const void *a, const void *b) {
+    char temp_a[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+1];
+    char temp_b[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+1];
+
+    snprintf(temp_a, sizeof(temp_a), "%s%s%s",
+            msg_data[*(long*)a].call_sign, msg_data[*(long *)a].from_call_sign,
+            msg_data[*(long *)a].seq);
+    snprintf(temp_b, sizeof(temp_b), "%s%s%s", msg_data[*(long*)b].call_sign,
+            msg_data[*(long *)b].from_call_sign, msg_data[*(long *)b].seq);
+
+    return(strcmp(temp_a, temp_b));
+}
+
+
+
+
+
+void msg_input_database(Message *m_fill) {
+    void *m_ptr;
+    long i;
+    
+//    fprintf(stderr, "DEBUG: Message: %s  %s\n", m_fill->call_sign, m_fill->message_line);
+    
+    if (msg_index_end == msg_index_max) {
+        for (i = 0; i < msg_index_end; i++) {
+
+            // Check for a record that is marked RECORD_NOTACTIVE.
+            // If found, use that record instead of malloc'ing a new
+            // one.
+            if (msg_data[msg_index[i]].active == RECORD_NOTACTIVE) {
+
+                // Found an unused record.  Fill it in.
+                memcpy(&msg_data[msg_index[i]], m_fill, sizeof(Message));
+
+// Sort msg_data
+                qsort(msg_data, (size_t)msg_index_end, sizeof(Message), msg_comp_active);
+
+                for (i = 0; i < msg_index_end; i++) {
+                    msg_index[i] = i;
+                    if (msg_data[i].active == RECORD_NOTACTIVE) {
+                        msg_index_end = i;
+                        break;
+                    }
+                }
+
+// Sort msg_index
+                qsort(msg_index, (size_t)msg_index_end, sizeof(long *), msg_comp_data);
+
+                // All done with this message.
+                return;
+            }
+        }
+
+        // Didn't find free message record.  Fetch some more space.
+        // Get more msg_data space.
+        m_ptr = realloc(msg_data, (msg_index_max+MSG_INCREMENT)*sizeof(Message));
+        if (m_ptr) {
+            msg_data = m_ptr;
+
+            // Get more msg_index space
+            m_ptr = realloc(msg_index, (msg_index_max+MSG_INCREMENT)*sizeof(Message *));
+            if (m_ptr) {
+                msg_index = m_ptr;
+                msg_index_max += MSG_INCREMENT;
+
+//fprintf(stderr, "Max Message Array: %ld\n", msg_index_max);
+
+            }
+            else {
+               // TODO
+                //XtWarning("Unable to allocate more space for message index.\n");
+            }
+        }
+        else {
+               // TODO 
+            //XtWarning("Unable to allocate more space for message database.\n");
+        }
+    }
+    if (msg_index_end < msg_index_max) {
+        msg_index[msg_index_end] = msg_index_end;
+
+        // Copy message data into new message record.
+        memcpy(&msg_data[msg_index_end++], m_fill, sizeof(Message));
+
+// Sort msg_index
+        qsort(msg_index, (size_t)msg_index_end, sizeof(long *), msg_comp_data);
+    }
+}
+
+
+
+
+
+// Does a binary search through a sorted message database looking
+// for a string match.
+//
+// If two or more messages match, this routine _should_ return the
+// message with the latest timestamp.  This will ensure that earlier
+// messages don't get mistaken for current messages, for the case
+// where the remote station did a restart and is using the same
+// sequence numbers over again.
+//
+long msg_find_data(Message *m_fill) {
+    long record_start, record_mid, record_end, return_record, done;
+    char tempfile[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+1];
+    char tempfill[MAX_CALLSIGN+MAX_CALLSIGN+MAX_MESSAGE_ORDER+1];
+
+
+    snprintf(tempfill, sizeof(tempfill), "%s%s%s",
+            m_fill->call_sign,
+            m_fill->from_call_sign,
+            m_fill->seq);
+
+    return_record = -1L;
+    if (msg_index && msg_index_end >= 1) {
+        /* more than one record */
+         record_start=0L;
+         record_end = (msg_index_end - 1);
+         record_mid=(record_end-record_start)/2;
+
+         done=0;
+         while (!done) {
+
+            /* get data for record start */
+            snprintf(tempfile, sizeof(tempfile), "%s%s%s",
+                    msg_data[msg_index[record_start]].call_sign,
+                    msg_data[msg_index[record_start]].from_call_sign,
+                    msg_data[msg_index[record_start]].seq);
+
+            if (strcmp(tempfill, tempfile) < 0) {
+                /* filename comes before */
+                /*fprintf(stderr,"Before No data found!!\n");*/
+                done=1;
+                break;
+            }
+            else { /* get data for record end */
+
+                snprintf(tempfile, sizeof(tempfile), "%s%s%s",
+                        msg_data[msg_index[record_end]].call_sign,
+                        msg_data[msg_index[record_end]].from_call_sign,
+                        msg_data[msg_index[record_end]].seq);
+
+                if (strcmp(tempfill,tempfile)>=0) { /* at end or beyond */
+                    if (strcmp(tempfill, tempfile) == 0) {
+                        return_record = record_end;
+//fprintf(stderr,"record %ld",return_record);
+                    }
+
+                    done=1;
+                    break;
+                }
+                else if ((record_mid == record_start) || (record_mid == record_end)) {
+                    /* no mid for compare check to see if in the middle */
+                    done=1;
+                    snprintf(tempfile, sizeof(tempfile), "%s%s%s",
+                            msg_data[msg_index[record_mid]].call_sign,
+                            msg_data[msg_index[record_mid]].from_call_sign,
+                            msg_data[msg_index[record_mid]].seq);
+                    if (strcmp(tempfill,tempfile)==0) {
+                        return_record = record_mid;
+//fprintf(stderr,"record: %ld",return_record);
+                    }
+                }
+            }
+            if (!done) { /* get data for record mid */
+                snprintf(tempfile, sizeof(tempfile), "%s%s%s",
+                        msg_data[msg_index[record_mid]].call_sign,
+                        msg_data[msg_index[record_mid]].from_call_sign,
+                        msg_data[msg_index[record_mid]].seq);
+
+                if (strcmp(tempfill, tempfile) == 0) {
+                    return_record = record_mid;
+//fprintf(stderr,"record %ld",return_record);
+                    done = 1;
+                    break;
+                }
+
+                if(strcmp(tempfill, tempfile)<0)
+                    record_end = record_mid;
+                else
+                    record_start = record_mid;
+
+                record_mid = record_start+(record_end-record_start)/2;
+            }
+        }
+    }
+    return(return_record);
+}
+
+
+
+
+
+void msg_replace_data(Message *m_fill, long record_num) {
+    memcpy(&msg_data[msg_index[record_num]], m_fill, sizeof(Message));
+}
+
+
+
+
+
+void msg_get_data(Message *m_fill, long record_num) {
+    memcpy(m_fill, &msg_data[msg_index[record_num]], sizeof(Message));
+}
+
+
+
+
+
+void msg_update_ack_stamp(long record_num) {
+
+    //fprintf(stderr,"Attempting to update ack stamp: %ld\n",record_num);
+    if ( (record_num >= 0) && (record_num < msg_index_end) ) {
+        msg_data[msg_index[record_num]].last_ack_sent = sec_now();
+        //fprintf(stderr,"Ack stamp: %ld\n",msg_data[msg_index[record_num]].last_ack_sent);
+    }
+    //fprintf(stderr,"\n\n\n*** Record: %ld ***\n\n\n",record_num);
+}
+
+
+
+
+
+// Called when we receive an ACK.  Sets the "acked" field in a
+// Message which gets rid of the highlighting in the Send Message
+// dialog for that message line.  This lets us know which messages
+// have been acked and which have not.  If timeout is non-zero, then
+// set acked to 2:  We use this in update_messages() to flag that
+// "*TIMEOUT*" should prefix the string.  If cancelled is non-zero,
+// set acked to 3:  We use this in update_messages() to flag that
+// "*CANCELLED*" should prefix the string.
+//
+void msg_record_ack(char *to_call_sign,
+                    char *my_call,
+                    char *seq,
+                    int timeout,
+                    int cancel) {
+    Message m_fill;
+    long record;
+    int do_update = 0;
+
+
+    // Find the corresponding message in msg_data[i], set the
+    // "acked" field to one.
+
+    substr(m_fill.call_sign, to_call_sign, MAX_CALLSIGN);
+    (void)remove_trailing_asterisk(m_fill.call_sign);
+
+    substr(m_fill.from_call_sign, my_call, MAX_CALLSIGN);
+    (void)remove_trailing_asterisk(m_fill.from_call_sign);
+
+    substr(m_fill.seq, seq, MAX_MESSAGE_ORDER);
+    (void)remove_trailing_spaces(m_fill.seq);
+    (void)remove_leading_spaces(m_fill.seq);
+
+    // Look for a message with the same to_call_sign, my_call,
+    // and seq number
+    record = msg_find_data(&m_fill);
+
+    if (record == -1L) { // No match yet, try another tactic.
+        if (seq[2] == '}' && strlen(seq) == 3) {
+
+            // Try it again without the trailing '}' character
+            m_fill.from_call_sign[2] = '\0';
+
+            // Look for a message with the same to_call_sign,
+            // my_call, and seq number (minus the trailing '}')
+            record = msg_find_data(&m_fill);
+        }
+    }
+
+    if(record != -1L) {     // Found a match!
+        // Only cause an update if this is the first ack.  This
+        // reduces dialog "flashing" a great deal
+        if ( msg_data[msg_index[record]].acked == 0 ) {
+
+            // Check for my callsign (including SSID).  If found,
+            // update any open message dialogs
+            if (is_my_call(msg_data[msg_index[record]].from_call_sign, 1) ) {
+
+                //fprintf(stderr,"From: %s\tTo: %s\n",
+                //    msg_data[msg_index[record]].from_call_sign,
+                //    msg_data[msg_index[record]].call_sign);
+
+                do_update++;
+            }
+        }
+        else {  // This message has already been acked.
+        }
+
+        if (cancel)
+            msg_data[msg_index[record]].acked = (char)3;
+        else if (timeout)
+            msg_data[msg_index[record]].acked = (char)2;
+        else
+            msg_data[msg_index[record]].acked = (char)1;
+
+        // Set the interval to zero so that we don't display it
+        // anymore in the dialog.  Same for tries.
+        msg_data[msg_index[record]].interval = 0;
+        msg_data[msg_index[record]].tries = 0;
+
+
+    }
+
+
+    if (do_update) {
+
+        update_messages(1); // Force an update
+
+        // Call check_popup_messages() here in order to pop up any
+        // closed Send Message dialogs.  For first ack's or
+        // CANCELLED messages it is less important, but for TIMEOUT
+        // messages it is very important.
+        //
+// TODO
+//        (void)check_popup_window(m_fill.call_sign, 2);  // Calls update_messages()
+    }
+}
+
+
+
+
+
+// Called when we receive a REJ packet (reject).  Sets the "acked"
+// field in a Message to 4 to indicate that the message has been
+// rejected by the remote station.  This gets rid of the
+// highlighting in the Send Message dialog for that message line.
+// This lets us know which messages have been rejected and which
+// have not.  We use this in update_messages() to flag that
+// "*REJECTED*" should prefix the string.
+//
+// The most common source of REJ packets would be from sending to a
+// D700A who's buffers are full, so that it can't take another
+// message.
+//
+void msg_record_rej(char *to_call_sign,
+                    char *my_call,
+                    char *seq) {
+    Message m_fill;
+    long record;
+    int do_update = 0;
+
+    // Find the corresponding message in msg_data[i], set the
+    // "acked" field to four.
+
+    substr(m_fill.call_sign, to_call_sign, MAX_CALLSIGN);
+    (void)remove_trailing_asterisk(m_fill.call_sign);
+
+    substr(m_fill.from_call_sign, my_call, MAX_CALLSIGN);
+    (void)remove_trailing_asterisk(m_fill.from_call_sign);
+
+    substr(m_fill.seq, seq, MAX_MESSAGE_ORDER);
+    (void)remove_trailing_spaces(m_fill.seq);
+    (void)remove_leading_spaces(m_fill.seq);
+
+    // Look for a message with the same to_call_sign, my_call,
+    // and seq number
+    record = msg_find_data(&m_fill);
+
+    if (record == -1L) { // No match yet, try another tactic.
+        if (seq[2] == '}' && strlen(seq) == 3) {
+
+            // Try it again without the trailing '}' character
+            m_fill.from_call_sign[2] = '\0';
+
+            // Look for a message with the same to_call_sign,
+            // my_call, and seq number (minus the trailing '}')
+            record = msg_find_data(&m_fill);
+        }
+    }
+
+    if(record != -1L) {     // Found a match!
+        // Only cause an update if this is the first rej.  This
+        // reduces dialog "flashing" a great deal
+        if ( msg_data[msg_index[record]].acked == 0 ) {
+
+            // Check for my callsign (including SSID).  If found,
+            // update any open message dialogs
+            if (is_my_call(msg_data[msg_index[record]].from_call_sign, 1) ) {
+
+                //fprintf(stderr,"From: %s\tTo: %s\n",
+                //    msg_data[msg_index[record]].from_call_sign,
+                //    msg_data[msg_index[record]].call_sign);
+
+                do_update++;
+            }
+        }
+        else {  // This message has already been acked.
+        }
+
+        // Actually record the REJ here
+        msg_data[msg_index[record]].acked = (char)4;
+
+        // Set the interval to zero so that we don't display it
+        // anymore in the dialog.  Same for tries.
+        msg_data[msg_index[record]].interval = 0;
+        msg_data[msg_index[record]].tries = 0;
+
+    }
+
+
+    if (do_update) {
+
+        update_messages(1); // Force an update
+
+        // Call check_popup_messages() here in order to pop up any
+        // closed Send Message dialogs.  For first ack's or
+        // CANCELLED messages it is less important, but for TIMEOUT
+        // messages it is very important.
+        //
+// TODO
+//        (void)check_popup_window(m_fill.call_sign, 2);  // Calls update_messages()
+    }
+}
+
+
+
+
+
+// Called from check_and_transmit_messages().  Updates the interval
+// field in our message record for the message currently being
+// transmitted.  We'll use this in the Send Message dialog to
+// display the current message interval.
+//
+void msg_record_interval_tries(char *to_call_sign,
+                    char *my_call,
+                    char *seq,
+                    time_t interval,
+                    int tries) {
+    Message m_fill;
+    long record;
+
+
+    // Find the corresponding message in msg_data[i]
+
+    substr(m_fill.call_sign, to_call_sign, MAX_CALLSIGN);
+    (void)remove_trailing_asterisk(m_fill.call_sign);
+
+    substr(m_fill.from_call_sign, my_call, MAX_CALLSIGN);
+    (void)remove_trailing_asterisk(m_fill.from_call_sign);
+
+    substr(m_fill.seq, seq, MAX_MESSAGE_ORDER);
+    (void)remove_trailing_spaces(m_fill.seq);
+    (void)remove_leading_spaces(m_fill.seq);
+
+    // Look for a message with the same to_call_sign, my_call,
+    // and seq number
+    record = msg_find_data(&m_fill);
+    if(record != -1L) {     // Found a match!
+
+        msg_data[msg_index[record]].interval = interval;
+        msg_data[msg_index[record]].tries = tries;
+    }
+
+    update_messages(1); // Force an update
+}
+
+
+
+
+
+// Returns: time_t for last_ack_sent
+//          -1 if the message doesn't pass our tests
+//           0 if it is a new message.
+//
+// Also returns the record number found if not passed a NULL pointer
+// in record_out or -1L if it's a new record.
+//
+time_t msg_data_add(char *call_sign, char *from_call, char *data,
+        char *seq, char type, TAprsPort port, long *record_out) {
+    Message m_fill;
+    long record;
+    char time_data[MAX_TIME];
+    int do_msg_update = 0;
+    time_t last_ack_sent;
+    int distance = -1;
+    char temp[10];
+    int group_message = 0;
+
+
+//fprintf(stderr,"from:%s, to:%s, seq:%s\n", from_call, call_sign, seq);
+
+    // Set the default output condition.  We'll change this later if
+    // we need to.
+    if (record_out != NULL)
+        *record_out = -1l;
+
+    // Check for some reasonable string in call_sign parameter
+    if (call_sign == NULL || strlen(call_sign) == 0) {
+
+        return((time_t)-1l);
+    }
+//else
+//fprintf(stderr,"msg_data_add():call_sign: %s\n", call_sign);
+    if ( (data != NULL) && (strlen(data) > MAX_MESSAGE_LENGTH) ) {
+
+        return((time_t)-1l);
+    }
+
+    substr(m_fill.call_sign, call_sign, MAX_CALLSIGN);
+    (void)remove_trailing_asterisk(m_fill.call_sign);
+
+    substr(m_fill.from_call_sign, from_call, MAX_CALLSIGN);
+    (void)remove_trailing_asterisk(m_fill.call_sign);
+
+    substr(m_fill.seq, seq, MAX_MESSAGE_ORDER);
+    (void)remove_trailing_spaces(m_fill.seq);
+    (void)remove_leading_spaces(m_fill.seq);
+
+// If the sequence number is blank, then it may have been a query,
+// directed query, or group message.  Assume it is a new message in
+// each case and add it.
+
+    if (seq[0] != '\0') {   // Normal station->station messaging or
+                            // bulletins
+        // Look for a message with the same call_sign,
+        // from_call_sign, and seq number
+        record = msg_find_data(&m_fill);
+//fprintf(stderr,"RECORD %ld  \n",record);
+//fprintf(stderr,"Normal station->station message\n");
+    }
+    else {  // Group message/query/etc.
+        record = -1L;
+        group_message++;    // Flag it as a group message
+//fprintf(stderr,"Group message/query/etc\n");
+    }
+    msg_clear_data(&m_fill);
+    if(record != -1L) { /* fill old data */
+        msg_get_data(&m_fill, record);
+        last_ack_sent = m_fill.last_ack_sent;
+        //fprintf(stderr,"Found: last_ack_sent: %ld\n",m_fill.last_ack_sent);
+
+        //fprintf(stderr,"Found a duplicate message.  Updating fields, seq %s\n",seq);
+
+        // If message is different this time, do an update to the
+        // send message window and update the sec_heard field.  The
+        // remote station must have restarted and is re-using the
+        // sequence numbers.  What a pain!
+        if (strcmp(m_fill.message_line,data) != 0) {
+            m_fill.sec_heard = sec_now();
+            last_ack_sent = (time_t)0;
+//fprintf(stderr,"Message is different this time: Setting last_ack_sent to 0\n");
+            if (type != MESSAGE_BULLETIN) { // Not a bulletin
+                do_msg_update++;
+            }
+        }
+
+        // If message is the same, but the sec_heard field is quite
+        // old (more than 8 hours), the remote station must have
+        // restarted, is re-using the sequence numbers, and just
+        // happened to send the same message with the same sequence
+        // number.  Again, what a pain!  Either that, or we
+        // connected to a spigot with a _really_ long queue!
+        if (m_fill.sec_heard < (sec_now() - (8 * 60 * 60) )) {
+            m_fill.sec_heard = sec_now();
+            last_ack_sent = (time_t)0;
+//fprintf(stderr,"Found >8hrs old: Setting last_ack_sent to 0\n");
+
+            if (type != MESSAGE_BULLETIN) { // Not a bulletin
+                do_msg_update++;
+            }
+        }
+
+        // Check for zero time
+        if (m_fill.sec_heard == (time_t)0) {
+            m_fill.sec_heard = sec_now();
+            fprintf(stderr,"Zero time on a previous message.\n");
+        }
+    }
+    else {
+        // Only do this if it's a new message.  This keeps things
+        // more in sequence by not updating the time stamps
+        // constantly on old messages that don't get ack'ed.
+        m_fill.sec_heard = sec_now();
+        last_ack_sent = (time_t)0;
+        //fprintf(stderr,"New msg: Setting last_ack_sent to 0\n");
+
+        if (type != MESSAGE_BULLETIN) { // Not a bulletin
+//fprintf(stderr,"Found new message\n");
+            do_msg_update++;    // Always do an update to the
+                                // message window for new messages
+        }
+    }
+
+    /* FROM */
+    m_fill.port =port;
+    m_fill.active=RECORD_ACTIVE;
+    m_fill.type=type;
+    if (m_fill.heard_via_tnc != VIA_TNC)
+        m_fill.heard_via_tnc = (port == APRS_PORT_TTY) ? VIA_TNC : NOT_VIA_TNC;
+
+    distance = (int)(distance_from_my_station(from_call,temp, sizeof(temp)) + 0.9999);
+    if (distance != 0) {    // Have a posit from the sending station
+        m_fill.position_known = 1;
+        //fprintf(stderr,"Position known: %s\n",from_call);
+    }
+    else {
+        //fprintf(stderr,"Position not known: %s\n",from_call);
+    }
+
+    substr(m_fill.call_sign,call_sign,MAX_CALLSIGN);
+    (void)remove_trailing_asterisk(m_fill.call_sign);
+
+    substr(m_fill.from_call_sign,from_call,MAX_CALLSIGN);
+    (void)remove_trailing_asterisk(m_fill.from_call_sign);
+
+    // Update the message field
+    substr(m_fill.message_line,data,MAX_MESSAGE_LENGTH);
+
+    substr(m_fill.seq,seq,MAX_MESSAGE_ORDER);
+    (void)remove_trailing_spaces(m_fill.seq);
+    (void)remove_leading_spaces(m_fill.seq);
+
+    // Create a timestamp from the current time
+    snprintf(m_fill.packet_time,
+        sizeof(m_fill.packet_time),
+        "%s",
+        get_time(time_data));
+
+    if(record == -1L) {     // No old record found
+        if (group_message)
+            m_fill.acked = 1;   // Group msgs/queries need no ack
+        else
+            m_fill.acked = 0;   // We can't have been acked yet
+
+        m_fill.interval = 0;
+        m_fill.tries = 0;
+
+        // We'll be sending an ack right away if this is a new
+        // message, so might as well set the time now so that we
+        // don't care about failing to set it in
+        // msg_update_ack_stamp due to the record number being -1.
+        m_fill.last_ack_sent = sec_now();
+
+        msg_input_database(&m_fill);    // Create a new entry
+        //fprintf(stderr,"No record found: Setting last_ack_sent to sec_now()00\n");
+    }
+    else {  // Old record found
+        //fprintf(stderr,"Replacing the message in the database, seq %s\n",seq);
+        msg_replace_data(&m_fill, record);  // Copy fields from m_fill to record
+    }
+
+    /* display messages */
+// TODO
+ //   if (type == MESSAGE_MESSAGE)
+//        all_messages(from,call_sign,from_call,data);
+
+    // Check for my callsign (including SSID).  If found, update any
+    // open message dialogs
+    if (       is_my_call(m_fill.from_call_sign, 1)
+            || is_my_call(m_fill.call_sign, 1) ) {
+
+        if (do_msg_update) {
+            update_messages(1); // Force an update
+        }
+    }
+
+    // Return the important variables we'll need
+    if (record_out != NULL)
+        *record_out = record;
+
+//fprintf(stderr,"\nrecord_out:%ld record %ld\n",*record_out,record);
+    return(last_ack_sent);
+
+}   // End of msg_data_add()
+
+
+
+
+
+// alert_data_add:  Function which adds NWS weather alerts to the
+// alert hash.
+//
+// This function adds alerts directly to the alert hash, bypassing
+// the message list and associated message-scan functions.
+//
+void alert_data_add(char *call_sign, char *from_call, char *data,
+        char *seq, char type, TAprsPort port) {
+    Message m_fill;
+    char time_data[MAX_TIME];
+
+
+
+/*
+    if (log_wx_alert_data && from != DATA_VIA_FILE) {
+        char temp_msg[MAX_MESSAGE_LENGTH+1];
+
+        // Attempt to reconstruct the original weather alert packet
+        // here, minus the path.
+        snprintf(temp_msg,
+            sizeof(temp_msg),
+            "%s>APRS::%-9s:%s{%s",
+            from_call,
+            call_sign,
+            data,
+            seq);
+        log_data( get_user_base_dir(LOGFILE_WX_ALERT), temp_msg);
+//        fprintf(stderr, "%s\n", temp_msg);
+    }
+*/
+
+    if ( (data != NULL) && (strlen(data) > MAX_MESSAGE_LENGTH) ) {
+        return;
+    }
+
+    substr(m_fill.call_sign, call_sign, MAX_CALLSIGN);
+    (void)remove_trailing_asterisk(m_fill.call_sign);
+
+    substr(m_fill.from_call_sign, from_call, MAX_CALLSIGN);
+    (void)remove_trailing_asterisk(m_fill.call_sign);
+
+    substr(m_fill.seq, seq, MAX_MESSAGE_ORDER);
+    (void)remove_trailing_spaces(m_fill.seq);
+    (void)remove_leading_spaces(m_fill.seq);
+
+    m_fill.sec_heard = sec_now();
+
+    /* FROM */
+    m_fill.port=port;
+    m_fill.active=RECORD_ACTIVE;
+    m_fill.type=type;
+
+    // We don't have a value filled in yet here!
+    //if (m_fill.heard_via_tnc != VIA_TNC)
+    m_fill.heard_via_tnc = (port == APRS_PORT_TTY) ? VIA_TNC : NOT_VIA_TNC;
+
+    substr(m_fill.call_sign,call_sign,MAX_CALLSIGN);
+    (void)remove_trailing_asterisk(m_fill.call_sign);
+
+    substr(m_fill.from_call_sign,from_call,MAX_CALLSIGN);
+    (void)remove_trailing_asterisk(m_fill.from_call_sign);
+
+    // Update the message field
+    substr(m_fill.message_line,data,MAX_MESSAGE_LENGTH);
+
+    substr(m_fill.seq,seq,MAX_MESSAGE_ORDER);
+    (void)remove_trailing_spaces(m_fill.seq);
+    (void)remove_leading_spaces(m_fill.seq);
+
+    // Create a timestamp from the current time
+    snprintf(m_fill.packet_time,
+        sizeof(m_fill.packet_time),
+        "%s",
+        get_time(time_data));
+
+    // Go try to add it to our alert hash.  alert_build_list() will
+    // check for duplicates before adding it.
+
+// TODO
+//    alert_build_list(&m_fill);
+
+    // This function fills in the Shapefile filename and index
+    // so that we can later draw it.
+// TODO
+//    fill_in_new_alert_entries();
+
+
+}   // End of alert_data_add()
+
+
+
+
+// What I'd like to do for the following routine:  Use
+// XmTextGetInsertionPosition() or XmTextGetCursorPosition() to
+// find the last of the text.  Could also save the position for
+// each SendMessage window.  Compare the timestamps of messages
+// found with the last update time.  If newer, then add them to
+// the end.  This should stop the incessant scrolling.
+
+// Another idea, easier method:  Create a buffer.  Snag out the
+// messages from the array and sort by time.  Put them into a
+// buffer.  Figure out the length of the text widget, and append
+// the extra length of the buffer onto the end of the text widget.
+// Once the message data is turned into a linked list, it might
+// be sorted already by time, so this window will look better
+// anyway.
+
+// Calling update_messages with force == 1 will cause an update
+// no matter what message_update_time() says.
+void update_messages(int force) {
+    // TODO
+}
+/*
+void update_messages(int force) {
+    static XmTextPosition pos;
+    char temp1[MAX_CALLSIGN+1];
+    char temp2[500];
+    char stemp[20];
+    long i;
+    int mw_p;
+    char *temp_ptr;
+
+
+    if ( message_update_time() || force) {
+
+//fprintf(stderr,"update_messages()\n");
+
+        //fprintf(stderr,"Um %d\n",(int)sec_now() );
+
+        // go through all mw_p's! 
+
+        // Perform this for each message window
+        for (mw_p=0; msg_index && mw_p < MAX_MESSAGE_WINDOWS; mw_p++) {
+            //pos=0;
+
+begin_critical_section(&send_message_dialog_lock, "db.c:update_messages" );
+
+            if (mw[mw_p].send_message_dialog!=NULL) {
+
+//fprintf(stderr,"\n");
+
+//fprintf(stderr,"found send_message_dialog\n");
+
+                // Clear the text from message window
+                XmTextReplace(mw[mw_p].send_message_text,
+                    (XmTextPosition) 0,
+                    XmTextGetLastPosition(mw[mw_p].send_message_text),
+                    "");
+
+                // Snag the callsign you're dealing with from the message dialogue
+                if (mw[mw_p].send_message_call_data != NULL) {
+                    temp_ptr = XmTextFieldGetString(mw[mw_p].send_message_call_data);
+                    snprintf(temp1,
+                        sizeof(temp1),
+                        "%s",
+                        temp_ptr);
+                    XtFree(temp_ptr);
+
+                    new_message_data--;
+                    if (new_message_data<0)
+                        new_message_data=0;
+
+                    if(strlen(temp1)>0) {   // We got a callsign from the dialog so
+                        // create a linked list of the message indexes in time-sorted order
+
+                        typedef struct _index_record {
+                            int index;
+                            time_t sec_heard;
+                            struct _index_record *next;
+                        } index_record;
+                        index_record *head = NULL;
+                        index_record *p_prev = NULL;
+                        index_record *p_next = NULL;
+
+                        // Allocate the first record (a dummy record)
+                        head = (index_record *)malloc(sizeof(index_record));
+                        CHECKMALLOC(head);
+
+                        head->index = -1;
+                        head->sec_heard = (time_t)0;
+                        head->next = NULL;
+
+                        (void)remove_trailing_spaces(temp1);
+                        (void)to_upper(temp1);
+
+                        pos = 0;
+                        // Loop through looking for messages to/from
+                        // that callsign (including SSID)
+                        for (i = 0; i < msg_index_end; i++) {
+                            if (msg_data[msg_index[i]].active == RECORD_ACTIVE
+                                    && (strcmp(temp1, msg_data[msg_index[i]].from_call_sign) == 0
+                                        || strcmp(temp1,msg_data[msg_index[i]].call_sign) == 0)
+                                    && (is_my_call(msg_data[msg_index[i]].from_call_sign, 1)
+                                        || is_my_call(msg_data[msg_index[i]].call_sign, 1)
+                                        || mw[mw_p].message_group ) ) {
+                                int done = 0;
+
+                                // Message matches our parameters so
+                                // save the relevant data about the
+                                // message in our linked list.  Compare
+                                // the sec_heard field to see whether
+                                // we're higher or lower, and insert the
+                                // record at the correct spot in the
+                                // list.  We end up with a time-sorted
+                                // list.
+                                p_prev  = head;
+                                p_next = p_prev->next;
+                                while (!done && (p_next != NULL)) {  // Loop until end of list or record inserted
+
+                                    //fprintf(stderr,"Looping, looking for insertion spot\n");
+
+                                    if (p_next->sec_heard <= msg_data[msg_index[i]].sec_heard) {
+                                        // Advance one record
+                                        p_prev = p_next;
+                                        p_next = p_prev->next;
+                                    }
+                                    else {  // We found the correct insertion spot
+                                        done++;
+                                    }
+                                }
+
+                                //fprintf(stderr,"Inserting\n");
+
+                                // Add the record in between p_prev and
+                                // p_next, even if we're at the end of
+                                // the list (in that case p_next will be
+                                // NULL.
+                                p_prev->next = (index_record *)malloc(sizeof(index_record));
+                                CHECKMALLOC(p_prev->next);
+
+                                p_prev->next->next = p_next; // Link to rest of records or NULL
+                                p_prev->next->index = i;
+                                p_prev->next->sec_heard = msg_data[msg_index[i]].sec_heard;
+// Remember to free this entire linked list before exiting the loop for
+// this message window!
+                            }
+                        }
+                        // Done processing the entire list for this
+                        // message window.
+
+                        //fprintf(stderr,"Done inserting/looping\n");
+
+                        if (head->next != NULL) {   // We have messages to display
+                            int done = 0;
+
+                            //fprintf(stderr,"We have messages to display\n");
+
+                            // Run through the linked list and dump the
+                            // info out.  It's now in time-sorted order.
+
+// Another optimization would be to keep a count of records added, then
+// later when we were dumping it out to the window, only dump the last
+// XX records out.
+
+                            p_prev = head->next;    // Skip the first dummy record
+                            p_next = p_prev->next;
+                            while (!done && (p_prev != NULL)) {  // Loop until end of list
+                                int j = p_prev->index;  // Snag the index out of the record
+                                char prefix[50];
+                                char interval_str[50];
+                                int offset = 22;    // Offset for highlighting
+
+
+                                //fprintf(stderr,"\nLooping through, reading messages\n");
+//fprintf(stderr,"acked: %d\n",msg_data[msg_index[j]].acked);
+                                // Message matches so snag the important pieces into a string
+                                snprintf(stemp, sizeof(stemp),
+                                    "%c%c/%c%c %c%c:%c%c",
+                                    msg_data[msg_index[j]].packet_time[0],
+                                    msg_data[msg_index[j]].packet_time[1],
+                                    msg_data[msg_index[j]].packet_time[2],
+                                    msg_data[msg_index[j]].packet_time[3],
+                                    msg_data[msg_index[j]].packet_time[8],
+                                    msg_data[msg_index[j]].packet_time[9],
+                                    msg_data[msg_index[j]].packet_time[10],
+                                    msg_data[msg_index[j]].packet_time[11]
+                                );
+
+// Somewhere in here we appear to be losing the first message.  It
+// doesn't get written to the window later in the QSO.  Same for
+// closing the window and re-opening it, putting the same callsign
+// in and pressing "New Call" button.  First message is missing.
+
+                                // Label the message line with who sent it.
+                                // If acked = 2 a timeout has occurred
+                                // If acked = 3 a cancel has occurred
+                                if (msg_data[msg_index[j]].acked == 2) {
+                                    snprintf(prefix,
+                                        sizeof(prefix),
+                                        "%s ",
+                                        langcode("WPUPMSB016") ); // "*TIMEOUT*"
+                                }
+                                else if (msg_data[msg_index[j]].acked == 3) {
+                                    snprintf(prefix,
+                                        sizeof(prefix),
+                                        "%s ",
+                                        langcode("WPUPMSB017") ); // "*CANCELLED*"
+                                }
+                                else if (msg_data[msg_index[j]].acked == 4) {
+                                    snprintf(prefix,
+                                        sizeof(prefix),
+                                        "%s ",
+                                        langcode("WPUPMSB018") ); // "*REJECTED*"
+                                }
+                                else prefix[0] = '\0';
+
+                                if (msg_data[msg_index[j]].interval) {
+                                    snprintf(interval_str,
+                                        sizeof(interval_str),
+                                        ">%d/%lds",
+                                        msg_data[msg_index[j]].tries + 1,
+                                        (long)msg_data[msg_index[j]].interval);
+
+                                    // Don't highlight the interval
+                                    // value
+                                    offset = offset + strlen(interval_str);
+                                }
+                                else {
+                                    interval_str[0] = '\0';
+                                }
+
+                                snprintf(temp2, sizeof(temp2),
+                                    "%s %-9s%s>%s%s\n",
+                                    // Debug code.  Trying to find sorting error
+                                    //"%ld  %s  %-9s>%s\n",
+                                    //msg_data[msg_index[j]].sec_heard,
+                                    stemp,
+                                    msg_data[msg_index[j]].from_call_sign,
+                                    interval_str,
+                                    prefix,
+                                    msg_data[msg_index[j]].message_line);
+
+//fprintf(stderr,"message: %s\n", msg_data[msg_index[j]].message_line);
+//fprintf(stderr,"update_messages: %s|%s", temp1, temp2);
+                                // Replace the text from pos to pos+strlen(temp2) by the string "temp2"
+                                if (mw[mw_p].send_message_text != NULL) {
+
+                                    // Insert the text at the end
+//                                    XmTextReplace(mw[mw_p].send_message_text,
+//                                            pos,
+//                                            pos+strlen(temp2),
+//                                            temp2);
+
+                                    XmTextInsert(mw[mw_p].send_message_text,
+                                            pos,
+                                            temp2);
+                                    // Set highlighting based on the
+                                    // "acked" field.  Callsign
+                                    // match here includes SSID.
+//fprintf(stderr,"acked: %d\t",msg_data[msg_index[j]].acked);
+                                    if ( (msg_data[msg_index[j]].acked == 0)    // Not acked yet
+                                            && ( is_my_call(msg_data[msg_index[j]].from_call_sign, 1)) ) {
+//fprintf(stderr,"Setting underline\t");
+                                        XmTextSetHighlight(mw[mw_p].send_message_text,
+                                            pos+offset,
+                                            pos+strlen(temp2),
+                                            //XmHIGHLIGHT_SECONDARY_SELECTED); // Underlining
+                                            XmHIGHLIGHT_SELECTED);         // Reverse Video
+                                    }
+                                    else {  // Message was acked, get rid of highlighting
+//fprintf(stderr,"Setting normal\t");
+                                        XmTextSetHighlight(mw[mw_p].send_message_text,
+                                            pos+offset,
+                                            pos+strlen(temp2),
+                                            XmHIGHLIGHT_NORMAL);
+                                    }
+
+//fprintf(stderr,"Text: %s\n",temp2); 
+
+                                    pos += strlen(temp2);
+
+                                }
+
+                                // Advance to the next record in the list
+                                p_prev = p_next;
+                                if (p_next != NULL)
+                                    p_next = p_prev->next;
+
+                            }   // End of while
+                        }   // End of if
+                        else {  // No messages matched, list is empty
+                        }
+
+// What does this do?  Move all of the text?
+//                        if (pos > 0) {
+//                            if (mw[mw_p].send_message_text != NULL) {
+//                                XmTextReplace(mw[mw_p].send_message_text,
+//                                        --pos,
+//                                        XmTextGetLastPosition(mw[mw_p].send_message_text),
+//                                        "");
+//                            }
+//                        }
+
+                        //fprintf(stderr,"Free'ing list\n");
+
+                        // De-allocate the linked list
+                        p_prev = head;
+                        while (p_prev != NULL) {
+
+                            //fprintf(stderr,"You're free!\n");
+
+                            p_next = p_prev->next;
+                            free(p_prev);
+                            p_prev = p_next;
+                        }
+
+                        // Show the last added message in the window
+                        XmTextShowPosition(mw[mw_p].send_message_text,
+                            pos);
+                    }
+                }
+            }
+
+end_critical_section(&send_message_dialog_lock, "db.c:update_messages" );
+
+        }
+        last_message_update = sec_now();
+
+//fprintf(stderr,"Message index end: %ld\n",msg_index_end);
+    }
+}
+*/
+
+
+
+
+void mdelete_messages_from(char *from) {
+    long i;
+
+    // Mark message records with RECORD_NOTACTIVE.  This will mark
+    // them for re-use.
+    for (i = 0; msg_index && i < msg_index_end; i++)
+        if (strcmp(msg_data[i].call_sign, _aprs_mycall) == 0 
+                       && strcmp(msg_data[i].from_call_sign, from) == 0)
+            msg_data[i].active = RECORD_NOTACTIVE;
+}
+
+
+
+
+
+void mdelete_messages_to(char *to) {
+    long i;
+
+    // Mark message records with RECORD_NOTACTIVE.  This will mark
+    // them for re-use.
+    for (i = 0; msg_index && i < msg_index_end; i++)
+        if (strcmp(msg_data[i].call_sign, to) == 0)
+            msg_data[i].active = RECORD_NOTACTIVE;
+}
+
+
+
+
+
+void mdelete_messages(char *to_from) {
+    long i;
+
+    // Mark message records with RECORD_NOTACTIVE.  This will mark
+    // them for re-use.
+    for (i = 0; msg_index && i < msg_index_end; i++)
+        if (strcmp(msg_data[i].call_sign, to_from) == 0 || strcmp(msg_data[i].from_call_sign, to_from) == 0)
+            msg_data[i].active = RECORD_NOTACTIVE;
+}
+
+
+
+
+
+void mdata_delete_type(const char msg_type, const time_t reference_time) {
+    long i;
+
+    // Mark message records with RECORD_NOTACTIVE.  This will mark
+    // them for re-use.
+    for (i = 0; msg_index && i < msg_index_end; i++)
+
+        if ((msg_type == '\0' || msg_type == msg_data[i].type)
+                && msg_data[i].active == RECORD_ACTIVE
+                && msg_data[i].sec_heard < reference_time)
+
+            msg_data[i].active = RECORD_NOTACTIVE;
+}
+
+
+
+
+
+void check_message_remove(time_t curr_sec) {       // called in timing loop
+
+    // Time to check for old messages again?  (Currently every ten
+    // minutes)
+#ifdef EXPIRE_DEBUG
+    if ( last_message_remove < (curr_sec - DEBUG_MESSAGE_REMOVE_CYCLE) ) {
+#else // EXPIRE_DEBUG
+    if ( last_message_remove < (curr_sec - MESSAGE_REMOVE_CYCLE) ) {
+#endif
+
+        // Yes it is.  Mark all messages that are older than
+        // sec_remove with the RECORD_NOTACTIVE flag.  This will
+        // mark them for re-use.
+#ifdef EXPIRE_DEBUG
+        mdata_delete_type('\0', curr_sec-DEBUG_MESSAGE_REMOVE);
+#else   // EXPIRE_DEBUG
+        mdata_delete_type('\0', curr_sec-_aprs_sec_remove);
+#endif
+
+        last_message_remove = curr_sec;
+    }
+
+    // Should we sort them at this point so that the unused ones are
+    // near the end?  It looks like the message input functions do
+    // this, so I guess we don't need to do it here.
+}
+
+
+
+
+
+void mscan_file(char msg_type, void (*function)(Message *)) {
+    long i;
+
+    for (i = 0; msg_index && i < msg_index_end; i++)
+        if ((msg_type == '\0' || msg_type == msg_data[msg_index[i]].type) &&
+                msg_data[msg_index[i]].active == RECORD_ACTIVE)
+            function(&msg_data[msg_index[i]]);
+}
+
+
+
+
+
+void mprint_record(Message *m_fill) {
+/*
+    fprintf(stderr,
+        "%-9s>%-9s %s:%5s %s:%c :%s\n",
+        m_fill->from_call_sign,
+        m_fill->call_sign,
+        langcode("WPUPMSB013"), // "seq"
+        m_fill->seq,
+        langcode("WPUPMSB014"), // "type"
+        m_fill->type,
+        m_fill->message_line);
+*/
+}
+
+
+
+
+
+void mdisplay_file(char msg_type) {
+    fprintf(stderr,"\n\n");
+    mscan_file(msg_type, mprint_record);
+    fprintf(stderr,"\tmsg_index_end %ld, msg_index_max %ld\n", msg_index_end, msg_index_max);
+}
+
+
+
+
+
+int decode_message(gchar *call,gchar *path,gchar *message,gint port,gint third_party) 
+{
+    char *temp_ptr;
+    char ipacket_message[300];
+    char message_plus_acks[MAX_MESSAGE_LENGTH + 10];
+    char from_call[MAX_CALLSIGN+1];
+    char ack[20];
+    int ok, len;
+    char addr[9+1];
+    char addr9[9+1];
+    char msg_id[5+1];
+    char orig_msg_id[5+1];
+    char ack_string[6];
+    int done;
+    int reply_ack = 0;
+    int to_my_call = 0;
+    int to_my_base_call = 0;
+    int from_my_call = 0;
+
+
+    // :xxxxxxxxx:____0-67____             message              printable, except '|', '~', '{'
+    // :BLNn     :____0-67____             general bulletin     printable, except '|', '~'
+    // :BLNnxxxxx:____0-67____           + Group Bulletin
+    // :BLNX     :____0-67____             Announcement
+    // :NWS-xxxxx:____0-67____             NWS Service Bulletin
+    // :NWS_xxxxx:____0-67____             NWS Service Bulletin
+    // :xxxxxxxxx:ackn1-5n               + ack
+    // :xxxxxxxxx:rejn1-5n               + rej
+    // :xxxxxxxxx:____0-67____{n1-5n     + message
+    // :NTS....
+    //  01234567890123456
+    // 01234567890123456    old
+    // we get message with already extracted data ID
+
+
+    if (is_my_call(call, 1) ) { // Check SSID also
+        from_my_call++;
+    }
+
+    ack_string[0] = '\0';   // Clear out the Reply/Ack result string
+
+    len = (int)strlen(message);
+    ok = (int)(len > 9 && message[9] == ':');
+
+    if (ok) {
+       
+//fprintf(stderr,"DEBUG: decode_message: from %s: %s\n", call, message);
+//return 0;
+       
+        substr(addr9,message,9); // extract addressee
+        snprintf(addr,
+            sizeof(addr),
+            "%s",
+            addr9);
+        (void)remove_trailing_spaces(addr);
+
+        if (is_my_call(addr,1)) { // Check includes SSID
+            to_my_call++;
+        }
+
+        if (is_my_call(addr,0)) { // Check ignores SSID.  We use
+                                  // this to catch messages to some
+                                  // of our other SSID's
+            to_my_base_call++;
+        }
+
+        message = message + 10; // pointer to message text
+
+        // Save the message text and the acks/reply-acks before we
+        // extract the acks below.
+        snprintf(message_plus_acks,
+            sizeof(message_plus_acks),
+            "%s",
+            message);
+
+        temp_ptr = strrchr(message,'{'); // look for message ID after
+                                         //*last* { in message.
+        msg_id[0] = '\0';
+        if (temp_ptr != NULL) {
+            substr(msg_id,temp_ptr+1,5); // extract message ID, could be non-digit
+            temp_ptr[0] = '\0';          // adjust message end (chops off message ID)
+        }
+
+        // Save the original msg_id away.
+        snprintf(orig_msg_id,
+            sizeof(orig_msg_id),
+            "%s",
+            msg_id);
+
+        // Check for Reply/Ack protocol in msg_id, which looks like
+        // this:  "{XX}BB", where XX is the sequence number for the
+        // message, and BB is the ack for the previous message from
+        // my station.  I've also seen this from APRS+: "{XX}B", so
+        // perhaps this is also possible "{X}B" or "{X}BB}".  We can
+        // also get auto-reply responses from APRS+ that just have
+        // "}X" or "}XX" at the end.  We decode those as well.
+        //
+
+        temp_ptr = strstr(msg_id,"}"); // look for Reply Ack in msg_id
+
+        if (temp_ptr != NULL) { // Found Reply/Ack protocol!
+            reply_ack++;
+
+// Put this code into the UI message area as well (if applicable).
+
+            // Separate out the extra ack so that we can deal with
+            // it properly.
+            snprintf(ack_string,
+                sizeof(ack_string),
+                "%s",
+                temp_ptr+1); // After the '}' character!
+
+            // Terminate it here so that rest of decode works
+            // properly.  We can get duplicate messages
+            // otherwise.
+            //
+// Note that we modify msg_id here.  Use orig_msg_id if we need the
+// unmodified version (full REPLY-ACK version) later.
+            //
+            temp_ptr[0] = '\0'; // adjust msg_id end
+
+        }
+        else {  // Look for Reply Ack in message without sequence
+                // number
+            temp_ptr = strstr(message,"}");
+
+            if (temp_ptr != NULL) {
+                int yy = 0;
+
+                reply_ack++;
+
+// Put this code into the UI message area as well (if applicable).
+                snprintf(ack_string,
+                    sizeof(ack_string),
+                    "%s",
+                    temp_ptr+1);    // After the '}' character!
+
+                ack_string[yy] = '\0';  // Terminate the string
+
+                // Terminate it here so that rest of decode works
+                // properly.  We can get duplicate messages
+                // otherwise.
+                temp_ptr[0] = '\0'; // adjust message end
+            } 
+        }
+
+        done = 0;
+    }
+    else {
+        done = 1;                               // fall through...
+    }
+
+    len = (int)strlen(message);
+    //--------------------------------------------------------------------------
+    if (!done && len > 3 && strncmp(message,"ack",3) == 0) {              // ACK
+
+        // Received an ACK packet.  Note that these can carry the
+        // REPLY-ACK protocol or a single ACK sequence number plus
+        // perhaps an extra '}' on the end.  They should have one of
+        // these formats:
+        //      ack1        Normal ACK
+        //      ackY        Normal ACK
+        //      ack23       Normal ACK
+        //      ackfH       Normal ACK
+        //      ack23{      REPLY-ACK Protocol
+        //      ack2Q}3d    REPLY-ACK Protocol
+
+        substr(msg_id,message+3,5);
+        // fprintf(stderr,"ACK: %s: |%s| |%s|\n",call,addr,msg_id);
+
+        if (to_my_call) { // Check SSID also
+
+            // Note:  This function handles REPLY-ACK protocol just
+            // fine, stripping off the 2nd ack if present.  It uses
+            // only the first sequence number.
+            clear_acked_message(call,addr,msg_id);  // got an ACK for me
+
+            // This one also handles REPLY-ACK protocol just fine.
+            msg_record_ack(call,addr,msg_id,0,0);   // Record the ack for this message
+        }
+        else {  // ACK is for another station
+            // Now if I have Igate on and I allow to retransmit station data
+            // check if this message is to a person I have heard on my TNC within an X
+            // time frame. If if is a station I heard and all the conditions are ok
+            // spit the ACK out on the TNC -FG
+/*
+ * TODO - Add igate support
+            if (operate_as_an_igate>1
+                    && from==DATA_VIA_NET
+                    && !from_my_call     // Check SSID also
+                    && port != -1) {    // Not from a log file
+                char short_path[100];
+
+                shorten_path(path,short_path,sizeof(short_path));
+
+                // Only send '}' and the ack_string if it's not
+                // empty, else just end the packet with the message
+                // string.  This keeps us from appending a '}' when
+                // it's not called for.
+                snprintf(ipacket_message,
+                    sizeof(ipacket_message),
+                    "}%s>%s,TCPIP,%s*::%s:%s",
+                    call,
+                    short_path,
+                    my_callsign,
+                    addr9,
+                    message_plus_acks);
+
+
+                output_igate_rf(call,
+                    addr,
+                    path,
+                    ipacket_message,
+                    port,
+                    third_party,
+                    NULL);
+
+                igate_msgs_tx++;
+            }
+*/
+        }
+        done = 1;
+    }
+    //--------------------------------------------------------------------------
+    if (!done && len > 3 && strncmp(message,"rej",3) == 0) {              // REJ
+
+        substr(msg_id,message+3,5);
+
+        if (to_my_call) {   // Check SSID also
+
+            // Note:  This function handles REPLY-ACK protocol just
+            // fine, stripping off the 2nd ack if present.  It uses
+            // only the first sequence number.
+            clear_acked_message(call,addr,msg_id);  // got an REJ for me
+
+            // This one also handles REPLY-ACK protocol just fine.
+            msg_record_rej(call,addr,msg_id);   // Record the REJ for this message
+        }
+        else {  // REJ is for another station
+            /* Now if I have Igate on and I allow to retransmit station data           */
+            /* check if this message is to a person I have heard on my TNC within an X */
+            /* time frame. If if is a station I heard and all the conditions are ok    */
+            /* spit the REJ out on the TNC                                             */
+               
+/*
+ * TODO - Add igate support
+            if (operate_as_an_igate>1
+                    && from==DATA_VIA_NET
+                    && !from_my_call    // Check SSID also
+                    && port != -1) {    // Not from a log file
+                char short_path[100];
+
+                shorten_path(path,short_path,sizeof(short_path));
+
+                // Only send '}' and the rej_string if it's not
+                // empty, else just end the packet with the message
+                // string.  This keeps us from appending a '}' when
+                // it's not called for.
+                snprintf(ipacket_message,
+                    sizeof(ipacket_message),
+                    "}%s>%s,TCPIP,%s*::%s:%s",
+                    call,
+                    short_path,
+                    my_callsign,
+                    addr9,
+                    message_plus_acks);
+
+
+                output_igate_rf(call,
+                    addr,
+                    path,
+                    ipacket_message,
+                    port,
+                    third_party,
+                    NULL);
+
+                igate_msgs_tx++;
+            }
+*/
+        }
+
+        done = 1;
+    }
+    
+    //--------------------------------------------------------------------------
+    if (!done && strncmp(addr,"BLN",3) == 0) {                       // Bulletin
+        // fprintf(stderr,"found BLN: |%s| |%s|\n",addr,message);
+        bulletin_data_add(addr,call,message,"",MESSAGE_BULLETIN,port);
+        done = 1;
+    }
+
+    //--------------------------------------------------------------------------
+    if (!done && strlen(msg_id) > 0 && to_my_call) {         // Message for me (including SSID check)
+                                                             // with msg_id (sequence number)
+        time_t last_ack_sent;
+        long record;
+
+
+// Remember to put this code into the UI message area as well (if
+// applicable).
+
+        // Check for Reply/Ack
+        if (reply_ack && strlen(ack_string) != 0) { // Have a free-ride ack to deal with
+
+            clear_acked_message(call,addr,ack_string);  // got an ACK for me
+
+            msg_record_ack(call,addr,ack_string,0,0);   // Record the ack for this message
+        }
+
+        // Save the ack 'cuz we might need it while talking to this
+        // station.  We need it to implement Reply/Ack protocol.
+
+// Note that msg_id has already been truncated by this point.
+// orig_msg_id contains the full REPLY-ACK text.
+
+//fprintf(stderr, "store_most_recent_ack()\n");
+        store_most_recent_ack(call,msg_id);
+        // fprintf(stderr,"found Msg w line to me: |%s| |%s|\n",message,msg_id);
+        last_ack_sent = msg_data_add(addr,
+                            call,
+                            message,
+                            msg_id,
+                            MESSAGE_MESSAGE,
+                            port, 
+                            &record); // id_fixed
+
+        // Here we need to know if it is a new message or an old.
+        // If we've already received it, we don't want to kick off
+        // the alerts or pop up the Send Message dialog again.  If
+        // last_ack_sent == (time_t)0, then it is a new message.
+        //
+        if (last_ack_sent == (time_t)0l && record == -1l) { // Msg we've never received before
+
+            new_message_data += 1;
+
+            // Note that the check_popup_window() function will
+            // re-create a Send Message dialog if one doesn't exist
+            // for this QSO.  Only call it for the first message
+            // line or the first ack, not for any repeats.
+            //
+            (void)check_popup_window(call, 2);  // Calls update_messages()
+
+            //update_messages(1); // Force an update
+
+
+        }
+
+        // Try to only send an ack out once per 30 seconds at the
+        // fastest.
+//WE7U
+// Does this 30-second check work?
+        //
+        if ( (last_ack_sent != (time_t)-1l)   // Not an error
+                && (last_ack_sent + 30 ) < sec_now()
+                && !satellite_ack_mode // Disable separate ack's for satellite work
+                && port != -1 ) {   // Not from a log file
+
+            char path[MAX_LINE_SIZE+1];
+
+            // Update the last_ack_sent field for the message
+            msg_update_ack_stamp(record);
+
+            pad_callsign(from_call,call);         /* ack the message */
+
+
+            // Attempt to snag a custom path out of the Send Message
+            // dialog, if set.  If not set, path will contain '\0';
+            get_send_message_path(call, path, MAX_LINE_SIZE+1);
+//fprintf(stderr,"Path: %s\n", path);
+
+
+            // In this case we want to send orig_msg_id back, not
+            // the (possibly) truncated msg_id.  This is per Bob B's
+            // Reply/Ack spec, sent to xastir-dev on Nov 14, 2001.
+            snprintf(ack, sizeof(ack), ":%s:ack%s",from_call,orig_msg_id);
+
+//WE7U
+// Need to figure out the reverse path for this one instead of
+// passing a NULL for the path?  Probably not, as auto-calculation
+// of paths isn't a good idea.
+//
+// What we need to do here is check whether we have a custom path
+// set for this QSO.  If so, pass that path along as the transmit
+// path.  messages.h:Message_Window struct has the send_message_path
+// variable in it.  If a Message_Window still exists for this QSO
+// then we can snag the user-entered path from there.  If the struct
+// has already been destroyed then we have nowhere to snag the
+// custom path from and have to rely on the default paths in each
+// interface properties dialog instead.  Then again, we _could_ snag
+// the path out of the last received message in the message database
+// for that case.  Might be better to disable the Close button, or
+// warn the user that the custom path will be lost if they close the
+// Send Message dialog.
+
+
+            // Send out the immediate ACK
+            if (path[0] == '\0')
+                transmit_message_data(call,ack,NULL);
+            else
+                transmit_message_data(call,ack,path);
+
+
+            if (record != -1l) { // Msg we've received before
+
+                // It's a message that we've received before,
+                // consider sending an extra ACK in about 30 seconds
+                // to try to get it to the remote station.  Perhaps
+                // another one in 60 seconds as well.
+
+                if (path[0] == '\0') {
+                    transmit_message_data_delayed(call,ack,NULL,sec_now()+30);
+                    transmit_message_data_delayed(call,ack,NULL,sec_now()+60);
+                    transmit_message_data_delayed(call,ack,NULL,sec_now()+120);
+                }
+                else {
+                    transmit_message_data_delayed(call,ack,path,sec_now()+30);
+                    transmit_message_data_delayed(call,ack,path,sec_now()+60);
+                    transmit_message_data_delayed(call,ack,path,sec_now()+120);
+                }
+            }
+
+/*
+ * TODO 
+            if (auto_reply == 1) {
+
+                snprintf(ipacket_message,
+                    sizeof(ipacket_message), "AA:%s", auto_reply_message);
+
+                if (!from_my_call) // Check SSID also
+                    output_message(my_callsign, call, ipacket_message, "");
+            }
+*/
+        }
+
+
+        done = 1;
+    }
+
+    //--------------------------------------------------------------------------
+    if (!done && strlen(msg_id) == 0 && to_my_call) {   // Message for me (including SSID check)
+                                                        // but without message-ID.
+        // These should appear in a Send Message dialog and should
+        // NOT get ack'ed.  Kenwood radios send this message type as
+        // an auto-answer or a buffer-full message.  They look
+        // something like:
+        //
+        //      :WE7U-13 :Not at keyboard.
+        //
+
+        time_t last_ack_sent;
+        long record;
+
+
+        if (len > 2
+                && message[0] == '?'
+                && port != -1   // Not from a log file
+                && to_my_call) { // directed query (check SSID also)
+            // Smallest query known is "?WX".
+            done = process_directed_query(call,path,message+1,port);
+        }
+
+        // fprintf(stderr,"found Msg w line to me: |%s| |%s|\n",message,msg_id);
+        last_ack_sent = msg_data_add(addr,
+                            call,
+                            message,
+                            msg_id,
+                            MESSAGE_MESSAGE,
+                            port,
+                            &record); // id_fixed
+
+        // Here we need to know if it is a new message or an old.
+        // If we've already received it, we don't want to kick off
+        // the alerts or pop up the Send Message dialog again.  If
+        // last_ack_sent == (time_t)0, then it is a new message.
+        //
+        if (last_ack_sent == (time_t)0l && record == -1l) { // Msg we've never received before
+
+            new_message_data += 1;
+
+            // Note that the check_popup_window() function will
+            // re-create a Send Message dialog if one doesn't exist
+            // for this QSO.  Only call it for the first message
+            // line or the first ack, not for any repeats.
+            //
+//fprintf(stderr,"***check_popup_window 1\n");
+            (void)check_popup_window(call, 2);  // Calls update_messages()
+
+            //update_messages(1); // Force an update
+
+        }
+
+        // Update the last_ack_sent field for the message, even
+        // though we won't be sending an ack in response.
+        msg_update_ack_stamp(record);
+
+
+        done = 1;
+    }
+
+    //--------------------------------------------------------------------------
+    if (!done
+            && ( (strncmp(addr,"NWS-",4) == 0)          // NWS weather alert
+              || (strncmp(addr,"NWS_",4) == 0) ) ) {    // NWS weather alert compressed
+
+        // could have sort of line number
+        //fprintf(stderr,"found NWS: |%s| |%s| |%s|\n",addr,message,msg_id);
+
+        (void)alert_data_add(addr,
+            call,
+            message,
+            msg_id,
+            MESSAGE_NWS,
+            port);
+
+        done = 1;
+        
+/*
+ * TODO - add igate support
+        if (operate_as_an_igate>1
+                && from==DATA_VIA_NET
+                && !from_my_call // Check SSID also
+                && port != -1) { // Not from a log file
+            char short_path[100];
+
+            shorten_path(path,short_path,sizeof(short_path));
+
+            snprintf(ipacket_message,
+                sizeof(ipacket_message),
+                "}%s>%s,TCPIP,%s*::%s:%s",
+                call,
+                short_path,
+                my_callsign,
+                addr9,
+                message);
+
+            output_nws_igate_rf(call,
+                path,
+                ipacket_message,
+                port,
+                third_party);
+        }
+*/
+    }
+
+    //--------------------------------------------------------------------------
+    if (!done && strncmp(addr,"SKY",3) == 0) {  // NWS weather alert additional info
+
+        // could have sort of line number
+        //fprintf(stderr,"found SKY: |%s| |%s| |%s|\n",addr,message,msg_id);
+
+               // We don't wish to record these in memory.  They cause an infinite
+               // loop in the current code and a massive memory leak.
+               return(1);  // Tell the calling program that the packet was ok so
+                           // that it doesn't add it with data_add() itself!
+               
+
+        done = 1;
+/*
+ * TODO - add igate support
+        if (operate_as_an_igate>1
+                && from==DATA_VIA_NET
+                && !from_my_call    // Check SSID also
+                && port != -1) { // Not from a log file
+            char short_path[100];
+
+            shorten_path(path,short_path,sizeof(short_path));
+
+            snprintf(ipacket_message,
+                sizeof(ipacket_message),
+                "}%s>%s,TCPIP,%s*::%s:%s",
+                call,
+                short_path,
+                my_callsign,
+                addr9,
+                message);
+
+            output_nws_igate_rf(call,
+                path,
+                ipacket_message,
+                port,
+                third_party);
+        }
+*/
+    }
+
+    //--------------------------------------------------------------------------
+    if (!done && strlen(msg_id) > 0) {  // Other message with linenumber.  This
+                                        // is either a message for someone else
+                                        // or a message for another one of my
+                                        // SSID's.
+        long record_out;
+        time_t last_ack_sent;
+        char message_plus_note[MAX_MESSAGE_LENGTH + 30];
+
+        if (to_my_base_call && !from_my_call) {
+            // Special case:  We saw a message w/msg_id that was to
+            // one of our other SSID's, but it was not from
+            // ourselves.  That last bit (!from_my_call) is
+            // important in the case where we're working an event
+            // with several stations using the same callsign.
+            //
+            // Store as if it came to my callsign, with a zeroed-out
+            // msg_id so we can't try to ack it.  We also need some
+            // other indication in the "Send Message" dialog as to
+            // what's happening.  Perhaps add the original callsign
+            // to the message itself in a note at the start?
+            //
+            snprintf(message_plus_note,
+                sizeof(message_plus_note),
+                "(Sent to:%s) %s",
+                addr,
+                message);
+            last_ack_sent = msg_data_add(_aprs_mycall,
+                call,
+                message_plus_note,
+                "",
+                MESSAGE_MESSAGE,
+                port,
+                &record_out);
+        }
+        else {  // Normal case, messaging between other people
+            last_ack_sent = msg_data_add(addr,
+                call,
+                message,
+                msg_id,
+                MESSAGE_MESSAGE,
+                port,
+                &record_out);
+        }
+        new_message_data += look_for_open_group_data(addr);
+        // Note that the check_popup_window() function will
+        // re-create a Send Message dialog if one doesn't exist for
+        // this QSO.  Only call it for the first message line or the
+        // first ack, not for any repeats.
+        //
+        if (last_ack_sent == (time_t)0l && record_out == -1l) { // Msg we've never received before
+
+            // Callsign check here also checks SSID for exact match
+// We need to do an SSID-non-specific check here so that we can pick
+// up messages intended for other stations of ours.
+
+            if ((to_my_base_call && check_popup_window(call, 2) != -1)
+                    || check_popup_window(call, 0) != -1
+                    || check_popup_window(addr, 1) != -1) {
+                update_messages(1); // Force an update
+            }
+        }
+
+        /* Now if I have Igate on and I allow to retransmit station data           */
+        /* check if this message is to a person I have heard on my TNC within an X */
+        /* time frame. If if is a station I heard and all the conditions are ok    */
+        /* spit the message out on the TNC -FG                                     */
+  /*
+   * TODO - add igate support
+        if (operate_as_an_igate>1
+                && last_ack_sent != (time_t)-1l
+                && from==DATA_VIA_NET
+                && !from_my_call        // Check SSID also
+                && !to_my_call          // Check SSID also
+                && port != -1) {    // Not from a log file
+            char short_path[100];
+
+            shorten_path(path,short_path,sizeof(short_path));
+            snprintf(ipacket_message,
+                sizeof(ipacket_message),
+                "}%s>%s,TCPIP,%s*::%s:%s",
+                call,
+                short_path,
+                my_callsign,
+                addr9,
+                message_plus_acks);
+
+
+            output_igate_rf(call,
+                addr,
+                path,
+                ipacket_message,
+                port,
+                third_party,
+                NULL);
+
+            igate_msgs_tx++;
+        }
+*/
+        done = 1;
+    }
+
+    //--------------------------------------------------------------------------
+
+    if (!done) {                                   // message without line number
+        long record_out;
+        time_t last_ack_sent;
+
+
+        last_ack_sent = msg_data_add(addr,
+            call,
+            message,
+            "",
+            MESSAGE_MESSAGE,
+            port,
+            &record_out);
+
+        new_message_data++;      // ??????
+
+        // Note that the check_popup_window() function will
+        // re-create a Send Message dialog if one doesn't exist for
+        // this QSO.  Only call it for the first message line or the
+        // first ack, not for any repeats.
+        //
+        if (last_ack_sent == (time_t)0l && record_out == -1l) { // Msg we've never received before
+            if (check_popup_window(addr, 1) != -1) {
+                //update_messages(1); // Force an update
+            }
+        }
+
+        // Could be response to a query.  Popup a messsage.
+
+// Check addr for my_call and !third_party, then check later in the
+// packet for my_call if it is a third_party message?  Depends on
+// what the packet looks like by this point.
+        if ( last_ack_sent != (time_t)-1l
+                && (message[0] != '?')
+                && to_my_call ) { // Check SSID also
+
+            // We no longer wish to have both popups and the Send
+            // Group Message dialogs come up for every query
+            // response, so we use popup_message() here instead of
+            // popup_message_always() so that by default we'll see
+            // the below message in STDERR.  If --with-errorpopups
+            // has been configured in, we'll get a popup as well.
+            // Send Group Message dialogs work well for multi-line
+            // query responses, so we'll leave it that way.
+            //
+// TODO
+//            popup_message(langcode("POPEM00018"),message);
+
+            // Check for Reply/Ack.  APRS+ sends an AA: response back
+            // for auto-reply, with an embedded free-ride Ack.
+            if (strlen(ack_string) != 0) {  // Have an extra ack to deal with
+
+                clear_acked_message(call,addr,ack_string);  // got an ACK for me
+
+                msg_record_ack(call,addr,ack_string,0,0);   // Record the ack for this message
+            }
+        }
+        // done = 1;
+    }
+
+    //--------------------------------------------------------------------------
+
+    if (ok)
+        (void)data_add(STATION_CALL_DATA,
+            call,
+            path,
+            message,
+            port,
+            NULL,
+            third_party,
+            0,  // Not a packet from my station
+            0); // Not my object/item
+
+
+    return(ok);
+}
+
+#endif //INCLUDE_APRS
diff --git a/src/aprs_message.h b/src/aprs_message.h
new file mode 100644 (file)
index 0000000..301a213
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * 
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Maemo Mapper is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Maemo Mapper.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * 
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ * 
+ *  * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000  Frank Giannandrea
+ * Copyright (C) 2000-2007  The Xastir Group
+ * 
+ */
+
+
+
+#ifdef HAVE_CONFIG_H
+#    include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#ifndef MAEMO_MAPPER_APRS_MESSAGE
+#define MAEMO_MAPPER_APRS_MESSAGE
+
+#define MSG_INCREMENT 200
+
+#include "types.h"
+
+
+// Used for messages and bulletins
+typedef struct {
+    char active;
+    TAprsPort port;
+    char type;
+    char heard_via_tnc;
+    time_t sec_heard;
+    time_t last_ack_sent;
+    char packet_time[MAX_TIME];
+    char call_sign[MAX_CALLSIGN+1];
+    char from_call_sign[MAX_CALLSIGN+1];
+    char message_line[MAX_MESSAGE_LENGTH+1];
+    char seq[MAX_MESSAGE_ORDER+1];
+    char acked;
+    char position_known;
+    time_t interval;
+    int tries;
+} Message;
+
+
+
+// Struct used to create linked list of most recent ack's
+typedef struct _ack_record {
+    char callsign[MAX_CALLSIGN+1];
+    char ack[5+1];
+    struct _ack_record *next;
+} ack_record;
+
+
+
+
+int decode_message(gchar *call,gchar *path,gchar *message,gint port,gint third_party);
+
+
+extern Message *msg_data;
+extern long msg_index_end;
+extern long *msg_index;
+#endif
+
+#endif //INCLUDE_APRS
index 1c5612def3474d62bf4f9b0fcd7e80cbefed50ca..b579fd0f1c7ee7e9ac5bb9e50466c129a7702d12 100644 (file)
@@ -61,21 +61,42 @@ static void
 cmenu_show_latlon(gint unitx, gint unity)
 {
   gdouble lat, lon;
 cmenu_show_latlon(gint unitx, gint unity)
 {
   gdouble lat, lon;
+  gint tmp_degformat = _degformat;
+  gint fallback_deg_format = _degformat;
   gchar buffer[80], tmp1[LL_FMT_LEN], tmp2[LL_FMT_LEN];
   gchar buffer[80], tmp1[LL_FMT_LEN], tmp2[LL_FMT_LEN];
+  
   printf("%s()\n", __PRETTY_FUNCTION__);
 
   unit2latlon(unitx, unity, lat, lon);
   printf("%s()\n", __PRETTY_FUNCTION__);
 
   unit2latlon(unitx, unity, lat, lon);
-  lat_format(lat, tmp1);
-  lon_format(lon, tmp2);
-
-  snprintf(buffer, sizeof(buffer),
-            "%s: %s\n"
-          "%s: %s",
-          _("Latitude"), tmp1,
-          _("Longitude"), tmp2);
-
+  
+  // Check that the current coord system supports the select position
+  if(!coord_system_check_lat_lon (lat, lon, &fallback_deg_format))
+  {
+       _degformat = fallback_deg_format;
+  }
+      
+  format_lat_lon(lat, lon, tmp1, tmp2);
+  
+
+  if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+  {
+         snprintf(buffer, sizeof(buffer),
+                   "%s: %s\n"
+                 "%s: %s",
+                 DEG_FORMAT_ENUM_TEXT[_degformat].long_field_1, tmp1,
+                 DEG_FORMAT_ENUM_TEXT[_degformat].long_field_2, tmp2);
+  }
+  else
+  {
+         snprintf(buffer, sizeof(buffer),
+                   "%s: %s",
+                 DEG_FORMAT_ENUM_TEXT[_degformat].long_field_1, tmp1);
+  }
+  
   MACRO_BANNER_SHOW_INFO(_window, buffer);
 
   MACRO_BANNER_SHOW_INFO(_window, buffer);
 
+  _degformat = tmp_degformat;
+  
   vprintf("%s(): return\n", __PRETTY_FUNCTION__);
 }
 
   vprintf("%s(): return\n", __PRETTY_FUNCTION__);
 }
 
@@ -651,7 +672,7 @@ void cmenu_init()
 
     /* Setup the map context menu. */
     gtk_menu_append(submenu, _cmenu_loc_show_latlon_item
 
     /* Setup the map context menu. */
     gtk_menu_append(submenu, _cmenu_loc_show_latlon_item
-            = gtk_menu_item_new_with_label(_("Show Lat/Lon")));
+            = gtk_menu_item_new_with_label(_("Show Position")));
     gtk_menu_append(submenu, gtk_separator_menu_item_new());
     gtk_menu_append(submenu, _cmenu_loc_distance_to_item
             = gtk_menu_item_new_with_label(_("Show Distance to")));
     gtk_menu_append(submenu, gtk_separator_menu_item_new());
     gtk_menu_append(submenu, _cmenu_loc_distance_to_item
             = gtk_menu_item_new_with_label(_("Show Distance to")));
@@ -685,11 +706,11 @@ void cmenu_init()
             submenu = gtk_menu_new());
 
     gtk_menu_append(submenu, _cmenu_way_show_latlon_item
             submenu = gtk_menu_new());
 
     gtk_menu_append(submenu, _cmenu_way_show_latlon_item
-            = gtk_menu_item_new_with_label(_("Show Lat/Lon")));
+            = gtk_menu_item_new_with_label(_("Show Position")));
     gtk_menu_append(submenu, _cmenu_way_show_desc_item
             = gtk_menu_item_new_with_label(_("Show Description")));
     gtk_menu_append(submenu, _cmenu_way_clip_latlon_item
     gtk_menu_append(submenu, _cmenu_way_show_desc_item
             = gtk_menu_item_new_with_label(_("Show Description")));
     gtk_menu_append(submenu, _cmenu_way_clip_latlon_item
-            = gtk_menu_item_new_with_label(_("Copy Lat/Lon")));
+            = gtk_menu_item_new_with_label(_("Copy Position")));
     gtk_menu_append(submenu, _cmenu_way_clip_desc_item
             = gtk_menu_item_new_with_label(_("Copy Description")));
     gtk_menu_append(submenu, gtk_separator_menu_item_new());
     gtk_menu_append(submenu, _cmenu_way_clip_desc_item
             = gtk_menu_item_new_with_label(_("Copy Description")));
     gtk_menu_append(submenu, gtk_separator_menu_item_new());
index fc68babc204cce591b6d690f93ee8ad36b4613bb..08df291a4c9407f6a5ed88aa233faec30b7dcc00 100644 (file)
  * along with Maemo Mapper.  If not, see <http://www.gnu.org/licenses/>.
  */
 
  * along with Maemo Mapper.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#ifdef HAVE_CONFIG_H
+#    include "config.h"
+#endif
+
 #define _GNU_SOURCE
 
 #include "types.h"
 #define _GNU_SOURCE
 
 #include "types.h"
@@ -58,9 +62,10 @@ GdkColor COLORABLE_DEFAULT[COLORABLE_ENUM_COUNT] =
     {0, 0x0000, 0xa000, 0x0000}, /* COLORABLE_ROUTE */
     {0, 0x0000, 0x8000, 0x0000}, /* COLORABLE_ROUTE_WAY */
     {0, 0x0000, 0x6000, 0x0000}, /* COLORABLE_ROUTE_BREAK */
     {0, 0x0000, 0xa000, 0x0000}, /* COLORABLE_ROUTE */
     {0, 0x0000, 0x8000, 0x0000}, /* COLORABLE_ROUTE_WAY */
     {0, 0x0000, 0x6000, 0x0000}, /* COLORABLE_ROUTE_BREAK */
-    {0, 0xa000, 0x0000, 0xa000}  /* COLORABLE_POI */
+    {0, 0xa000, 0x0000, 0xa000}, /* COLORABLE_POI */
+    {0, 0xa000, 0x0000, 0xa000}  /* COLORABLE_APRS_STATION */
 };
 };
-gchar *DEG_FORMAT_ENUM_TEXT[DEG_FORMAT_ENUM_COUNT];
+CoordFormatSetup DEG_FORMAT_ENUM_TEXT[DEG_FORMAT_ENUM_COUNT];
 gchar *SPEED_LOCATION_ENUM_TEXT[SPEED_LOCATION_ENUM_COUNT];
 gchar *GPS_RCVR_ENUM_TEXT[GPS_RCVR_ENUM_COUNT];
 
 gchar *SPEED_LOCATION_ENUM_TEXT[SPEED_LOCATION_ENUM_COUNT];
 gchar *GPS_RCVR_ENUM_TEXT[GPS_RCVR_ENUM_COUNT];
 
@@ -210,6 +215,47 @@ InfoFontSize _info_font_size = INFO_FONT_MEDIUM;
 GList *_repo_list = NULL;
 RepoData *_curr_repo = NULL;
 
 GList *_repo_list = NULL;
 RepoData *_curr_repo = NULL;
 
+// APRS config
+#ifdef INCLUDE_APRS
+
+gboolean _aprs_enable = FALSE;     // APRS support is enabled
+gboolean _aprs_inet_enable = FALSE; // Connection to APRS server is enabled
+gboolean _aprs_tty_enable = FALSE;
+
+gchar *  _aprs_tnc_bt_mac = NULL;
+TTncConnection _aprs_tnc_method = TNC_CONNECTION_BT;
+
+gchar *  _aprs_tty_port = NULL;
+gchar *  _aprs_server = NULL;      // Server address
+guint    _aprs_server_port = 23;   // Server port
+gint     _aprs_std_pos_hist = 1;   // Not currently implemented, so fix as 1
+gint     _aprs_max_stations = 200; // 0 - no limit
+gchar *  _aprs_inet_server_validation = NULL; // -1 if not known
+
+gboolean _aprs_enable_inet_tx = FALSE;
+gboolean _aprs_enable_tty_tx = FALSE;
+gchar *  _aprs_mycall = NULL;      // My APRS call
+gchar *  _aprs_beacon_comment = NULL;  // My TNC Aprs Beacon comment
+
+gint     _aprs_inet_beacon_interval = 0; // 0 = disabled
+gint     _aprs_tty_beacon_interval = 0;  // 0 = disabled
+gchar *  _aprs_inet_beacon_comment = NULL;
+gboolean _aprs_transmit_compressed_posit = TRUE;
+gboolean _aprs_show_new_station_alert = TRUE;
+time_t   _aprs_sec_remove;              /* Station removed after */
+ConnState _aprs_inet_state;             // State of APRS iNet connection
+ConnState _aprs_tty_state;
+gint     _aprs_server_auto_filter_km = 100;
+gboolean _aprs_server_auto_filter_on = FALSE;
+//GtkWidget *_aprs_connect_banner = NULL;
+gchar *  _aprs_unproto_path = NULL;
+gchar    _aprs_beacon_group;
+gchar    _aprs_beacon_symbol;
+
+#endif // INCLUDE_APRS
+//----------------------
+
+
 /** POI */
 gchar *_poi_db_filename = NULL;
 gchar *_poi_db_dirname = NULL;
 /** POI */
 gchar *_poi_db_filename = NULL;
 gchar *_poi_db_dirname = NULL;
@@ -302,6 +348,15 @@ GtkWidget *_menu_gps_show_info_item = NULL;
 GtkWidget *_menu_gps_details_item = NULL;
 GtkWidget *_menu_gps_reset_item = NULL;
 
 GtkWidget *_menu_gps_details_item = NULL;
 GtkWidget *_menu_gps_reset_item = NULL;
 
+#ifdef INCLUDE_APRS
+/* Menu items for the "APRS" menu items. */
+GtkWidget *_menu_aprs_settings_item = NULL;
+GtkWidget *_menu_enable_aprs_inet_item = NULL;
+GtkWidget *_menu_enable_aprs_tty_item = NULL;
+GtkWidget *_menu_list_aprs_stations_item = NULL;
+GtkWidget *_menu_list_aprs_messages_item = NULL;
+#endif // INCLUDE_APRS
+
 /* Menu items for the other menu items. */
 GtkWidget *_menu_settings_item = NULL;
 GtkWidget *_menu_help_item = NULL;
 /* Menu items for the other menu items. */
 GtkWidget *_menu_settings_item = NULL;
 GtkWidget *_menu_help_item = NULL;
index 1ac3b3da1678ca35da68ca574e4559d7b3aaf3cb..393d2ddabd6da5bc74f75607835b49d8081b001f 100644 (file)
 #ifndef MAEMO_MAPPER_DATA_H
 #define MAEMO_MAPPER_DATA_H
 
 #ifndef MAEMO_MAPPER_DATA_H
 #define MAEMO_MAPPER_DATA_H
 
+#ifdef HAVE_CONFIG_H
+#    include "config.h"
+#endif
+
 #include <libosso.h>
 #include <libosso.h>
+#include "types.h"
 
 /* Constants regarding enums and defaults. */
 extern gchar *UNITS_ENUM_TEXT[UNITS_ENUM_COUNT];
 
 /* Constants regarding enums and defaults. */
 extern gchar *UNITS_ENUM_TEXT[UNITS_ENUM_COUNT];
@@ -39,7 +44,7 @@ extern gchar *CUSTOM_KEY_ICON[CUSTOM_KEY_ENUM_COUNT];
 extern CustomAction CUSTOM_KEY_DEFAULT[CUSTOM_KEY_ENUM_COUNT];
 extern gchar *COLORABLE_GCONF[COLORABLE_ENUM_COUNT];
 extern GdkColor COLORABLE_DEFAULT[COLORABLE_ENUM_COUNT];
 extern CustomAction CUSTOM_KEY_DEFAULT[CUSTOM_KEY_ENUM_COUNT];
 extern gchar *COLORABLE_GCONF[COLORABLE_ENUM_COUNT];
 extern GdkColor COLORABLE_DEFAULT[COLORABLE_ENUM_COUNT];
-extern gchar *DEG_FORMAT_ENUM_TEXT[DEG_FORMAT_ENUM_COUNT];
+extern CoordFormatSetup DEG_FORMAT_ENUM_TEXT[DEG_FORMAT_ENUM_COUNT];
 extern gchar *SPEED_LOCATION_ENUM_TEXT[SPEED_LOCATION_ENUM_COUNT];
 extern gchar *GPS_RCVR_ENUM_TEXT[GPS_RCVR_ENUM_COUNT];
 
 extern gchar *SPEED_LOCATION_ENUM_TEXT[SPEED_LOCATION_ENUM_COUNT];
 extern gchar *GPS_RCVR_ENUM_TEXT[GPS_RCVR_ENUM_COUNT];
 
@@ -334,5 +339,47 @@ extern GtkWidget *_cmenu_poi_goto_nearpoi_item;
  * ABOVE: CONTEXT MENU ITEMS *
  *****************************/
 
  * ABOVE: CONTEXT MENU ITEMS *
  *****************************/
 
+#ifdef INCLUDE_APRS
+
+extern gboolean _aprs_enable;
+extern gboolean _aprs_inet_enable;
+extern gboolean _aprs_tty_enable;
+extern gchar *  _aprs_server;
+extern gchar *  _aprs_tty_port;
+extern guint     _aprs_server_port;
+extern gint     _aprs_std_pos_hist;
+extern gint     _aprs_max_stations;
+extern gchar *  _aprs_inet_server_validation;
+extern gchar *  _aprs_beacon_comment;
+extern gboolean _aprs_transmit_compressed_posit;
+extern gint     _aprs_inet_beacon_interval;
+extern gint     _aprs_tty_beacon_interval;
+extern gchar *  _aprs_inet_beacon_comment;
+extern gchar    _aprs_beacon_group;
+extern gchar    _aprs_beacon_symbol;
+
+extern gboolean _aprs_enable_inet_tx;
+extern gboolean _aprs_enable_tty_tx;
+extern gchar *  _aprs_mycall;
+extern gchar *  _aprs_beacon_comment;
+
+extern gboolean _aprs_show_new_station_alert;
+extern time_t   _aprs_sec_remove;
+extern GtkWidget *_menu_enable_aprs_inet_item;
+extern GtkWidget *_menu_enable_aprs_tty_item;
+extern GtkWidget *_menu_list_aprs_stations_item;
+extern GtkWidget *_menu_list_aprs_messages_item;
+extern GtkWidget *_menu_aprs_settings_item;
+extern ConnState _aprs_inet_state;
+extern ConnState _aprs_tty_state;
+extern gchar *  _aprs_tnc_bt_mac;
+extern TTncConnection _aprs_tnc_method;
+extern gint     _aprs_server_auto_filter_km;
+extern gboolean _aprs_server_auto_filter_on;
+//extern GtkWidget *_aprs_connect_banner;
+extern gchar *  _aprs_unproto_path;
+
+#endif // INCLUDE_APRS
+
 #endif /* ifndef MAEMO_MAPPER_DATA_H */
 
 #endif /* ifndef MAEMO_MAPPER_DATA_H */
 
index c156afb3af4e37332d29e121569f01d34a47a7c1..42fd470f6d3ee023690835536a457ed4a5af22e8 100644 (file)
 
 #define EARTH_RADIUS (3443.91847)
 
 
 #define EARTH_RADIUS (3443.91847)
 
+/* BT dbus service location */
+#define BASE_PATH                "/org/bluez"
+#define BASE_INTERFACE           "org.bluez"
+//#define ADAPTER_PATH             BASE_PATH
+#define ADAPTER_INTERFACE        BASE_INTERFACE ".Adapter"
+#define MANAGER_PATH             BASE_PATH
+#define MANAGER_INTERFACE        BASE_INTERFACE ".Manager"
+#define ERROR_INTERFACE          BASE_INTERFACE ".Error"
+#define SECURITY_INTERFACE       BASE_INTERFACE ".Security"
+#define RFCOMM_INTERFACE         BASE_INTERFACE ".RFCOMM"
+#define BLUEZ_DBUS               BASE_INTERFACE
+
+#define LIST_ADAPTERS            "ListAdapters"
+#define LIST_BONDINGS            "ListBondings"
+//#define CREATE_BONDING           "CreateBonding"
+#define GET_REMOTE_NAME          "GetRemoteName"
+#define GET_REMOTE_SERVICE_CLASSES "GetRemoteServiceClasses"
+
+#define BTCOND_PATH              "/com/nokia/btcond/request"
+#define BTCOND_BASE              "com.nokia.btcond"
+#define BTCOND_INTERFACE         BTCOND_BASE ".request"
+#define BTCOND_REQUEST           BTCOND_INTERFACE
+#define BTCOND_CONNECT           "rfcomm_connect"
+#define BTCOND_DISCONNECT        "rfcomm_disconnect"
+#define BTCOND_DBUS              BTCOND_BASE
+
+
 /** MAX_ZOOM defines the largest map zoom level we will download.
  * (MAX_ZOOM - 1) is the largest map zoom level that the user can zoom to.
  */
 /** MAX_ZOOM defines the largest map zoom level we will download.
  * (MAX_ZOOM - 1) is the largest map zoom level that the user can zoom to.
  */
@@ -63,6 +90,9 @@
 #define ARRAY_CHUNK_SIZE (1024)
 
 #define BUFFER_SIZE (2048)
 #define ARRAY_CHUNK_SIZE (1024)
 
 #define BUFFER_SIZE (2048)
+#define APRS_BUFFER_SIZE (4096)
+#define APRS_MAX_COMMENT  80
+#define APRS_CONVERSE_MODE "k"
 
 #define GPSD_PORT_DEFAULT (2947)
 
 
 #define GPSD_PORT_DEFAULT (2947)
 
             _view_height_pixels)
 
 /* Render all on-map metadata an annotations, including POI and paths. */
             _view_height_pixels)
 
 /* Render all on-map metadata an annotations, including POI and paths. */
+#ifdef INCLUDE_APRS
+#define MACRO_MAP_RENDER_DATA() { \
+    if(_show_poi) \
+        map_render_poi(); \
+    if(_show_paths > 0) \
+        map_render_paths(); \
+    if(_aprs_enable) \
+       map_render_aprs(); \
+}
+#else
 #define MACRO_MAP_RENDER_DATA() { \
     if(_show_poi) \
         map_render_poi(); \
     if(_show_paths > 0) \
         map_render_paths(); \
 }
 #define MACRO_MAP_RENDER_DATA() { \
     if(_show_poi) \
         map_render_poi(); \
     if(_show_paths > 0) \
         map_render_paths(); \
 }
+#endif //INCLUDE_APRS
 
 #define UNBLANK_SCREEN(MOVING, APPROACHING_WAYPOINT) { \
     /* Check if we need to unblank the screen. */ \
 
 #define UNBLANK_SCREEN(MOVING, APPROACHING_WAYPOINT) { \
     /* Check if we need to unblank the screen. */ \
index 381b92339912b6eda9b3679ab2916ad03f975ab9..cd79d3968e3f26448eae75c95ed75a2317d3b4dd 100644 (file)
@@ -30,6 +30,9 @@
 #include <stdlib.h>
 #include <string.h>
 #include <math.h>
 #include <stdlib.h>
 #include <string.h>
 #include <math.h>
+#include "aprs.h"
+#include "aprs_decode.h"
+#include "types.h"
 
 #ifndef LEGACY
 #    include <hildon/hildon-help.h>
 
 #ifndef LEGACY
 #    include <hildon/hildon-help.h>
@@ -106,6 +109,10 @@ PangoLayout *_sat_info_layout = NULL;
 PangoLayout *_sat_details_layout = NULL;
 PangoLayout *_sat_details_expose_layout = NULL;
 
 PangoLayout *_sat_details_layout = NULL;
 PangoLayout *_sat_details_expose_layout = NULL;
 
+#ifdef INCLUDE_APRS
+PangoLayout *_aprs_label_layout = NULL;
+#endif // INCLUDE_APRS
+
 #define SCALE_WIDTH (100)
 
 static gboolean
 #define SCALE_WIDTH (100)
 
 static gboolean
@@ -122,6 +129,200 @@ speed_excess(void)
     return TRUE;
 }
 
     return TRUE;
 }
 
+#ifdef INCLUDE_APRS
+//// START OF APRS CODE
+gint add_aprs_label(gchar *label, gint label_len, gint x, gint y)
+{
+    gint height = 0;
+    gint width = 0;
+    pango_layout_set_text(_aprs_label_layout, label, label_len);
+
+    pango_layout_get_pixel_size(_aprs_label_layout, &width, &height);
+
+    /* Draw the layout itself. */
+    gdk_draw_layout( _map_pixmap, 
+        _gc[COLORABLE_APRS_STATION],
+        x,
+        y - (gint)((gfloat)height/2.0f),
+        _aprs_label_layout);
+
+    return width;
+}
+
+gboolean extract_aprs_symbol(const gchar symbol, const char primary, GdkPixbuf **pixbuf, 
+               gint *symbol_size, 
+               gint *symbol_column, 
+               gint *symbol_row)
+{
+//    GdkPixbuf *pixbuf = NULL;
+    GError *error = NULL;
+    gchar filename[100];
+
+
+    const gint startSymbol = ' ';
+    const gint symbols_per_column = 16;
+    const gint symbols_per_row = 6;
+    /*const gint*/ *symbol_size = 96/symbols_per_row;
+
+    gint symbol_number = (gint)symbol - startSymbol;
+
+    if(symbol_number<0 || symbol_number > symbols_per_row * symbols_per_column) symbol_number = 0;
+
+    if(primary == '/') snprintf(filename, sizeof(filename), "/usr/share/icons/hicolor/scalable/hildon/allicons.png");
+    else               snprintf(filename, sizeof(filename), "/usr/share/icons/hicolor/scalable/hildon/allicon2.png");
+    
+    *symbol_column = (gint)(symbol_number/symbols_per_column);
+    *symbol_row    = symbol_number - ((*symbol_column)*symbols_per_column);
+
+
+    *pixbuf = gdk_pixbuf_new_from_file(filename, &error);
+    
+
+    if(error)
+    {
+        return FALSE;
+    }
+
+    if( (*symbol_size)*(*symbol_column) < 0 || (*symbol_size)*(*symbol_row) < 0 ) 
+    {
+        return FALSE;
+    }
+    
+    return TRUE;
+}
+//
+gboolean draw_aprs_symbol(const gchar symbol, const gchar primary, const gint poix, const gint poiy, gint* imageSize)
+{
+    GdkPixbuf *pixbuf = NULL;
+    
+    gint symbol_column = 0;
+    gint symbol_row    = 0;
+    gint symbol_size = 0;
+
+    if(!extract_aprs_symbol(symbol, primary, &pixbuf, 
+               &symbol_size, 
+               &symbol_column, 
+               &symbol_row))
+       return FALSE;
+    
+    
+    /* We found an icon to draw. */
+    gdk_draw_pixbuf(
+        _map_pixmap,
+        _gc[COLORABLE_POI],
+        pixbuf,
+        symbol_size*symbol_column, symbol_size*symbol_row,
+        poix - symbol_size / 2,
+        poiy - symbol_size / 2,
+        symbol_size, symbol_size,
+        GDK_RGB_DITHER_NONE, 0, 0);
+    g_object_unref(pixbuf);
+
+    *imageSize = symbol_size;
+   
+
+    return TRUE;
+}
+
+
+void plot_aprs_station(AprsDataRow *p_station, gboolean single )
+{
+       if(_poi_zoom <= _zoom) return ;
+       
+    gint image_size = 0;
+    gdouble lat1 = 0;
+    gdouble lon1 = 0;
+    gint poix, poiy;
+    gint unitx, unity;
+    
+    // TODO - if this station has been ploted before, then redraw the map at that position
+    /*
+    if(p_station->newest_trackpoint != NULL)
+    {
+       AprsTrackRow *pPreviousPoint = p_station->newest_trackpoint;
+       
+       lat1 = convert_lat_l2d(pPreviousPoint->trail_lat_pos);
+           lon1 = convert_lon_l2d(pPreviousPoint->trail_long_pos);
+
+        latlon2unit(lat1, lon1, unitx, unity);
+        unit2buf(unitx, unity, poix, poiy);
+        fprintf(stderr, "Removing old pos.\n");
+        
+        poix = poix - 40;
+        poiy = poiy - 25;
+        if(poix<0) poix = 0;
+        if(poiy<0) poiy = 0;
+        
+        if(poix+100 > _view_width_pixels) poix = _view_width_pixels;
+        if(poiy+50 > _view_height_pixels) poiy = _view_height_pixels;
+               
+        gdk_draw_pixbuf(
+                _map_pixmap,
+                _map_widget->style->fg_gc[GTK_STATE_ACTIVE],
+                _map_pixbuf,
+                poix, poiy,
+                poix, poiy,
+                100, 50,
+                GDK_RGB_DITHER_NONE, 0, 0);
+        
+        // TODO - this will only redraw the background, not any POI´s or other items
+    }
+*/
+    lat1 = convert_lat_l2d(p_station->coord_lat);
+    lon1 = convert_lon_l2d(p_station->coord_lon);
+
+    latlon2unit(lat1, lon1, unitx, unity);
+    unit2buf(unitx, unity, poix, poiy);
+
+    // Ignore this station if it's position is not on the screen
+    if(poix < 0 || poix > _view_width_pixels
+       || poiy < 0 || poiy > _view_height_pixels)
+       return ;
+
+    gchar label[MAX_CALLSIGN+1];
+    snprintf(label, sizeof(label), "%s", p_station->call_sign);
+
+    if(!draw_aprs_symbol(p_station->aprs_symbol.aprs_symbol, p_station->aprs_symbol.aprs_type, poix, poiy, &image_size))
+    {
+        /* No icon for POI or for category or default POI icon file.
+           Draw default purple square. */
+        gdk_draw_rectangle(_map_pixmap, _gc[COLORABLE_APRS_STATION], TRUE,
+            poix - (gint)(1.5f * _draw_width),
+            poiy - (gint)(1.5f * _draw_width),
+            3 * _draw_width,
+            3 * _draw_width);
+
+        image_size = 3 * _draw_width;
+    }
+
+    gint label_width = 0;
+    
+    gint _aprs_label_zoom = _poi_zoom - 1;
+    
+    if(_aprs_label_zoom > _zoom)
+    {
+       label_width = add_aprs_label(label, strlen(label), poix + image_size, poiy);
+    }
+
+    if(single)
+    {
+        gint offset = (gint)((gfloat)image_size/2.0f);
+
+        gtk_widget_queue_draw_area( 
+                _map_widget, 
+                poix - offset,
+                poiy - offset,
+                (2*image_size) + label_width,
+                image_size
+            );
+    }
+
+
+}
+#endif // INCLUDE_APRS
+
+//// END OF APRS CODE
+
 static void
 speed_limit(void)
 {
 static void
 speed_limit(void)
 {
@@ -197,7 +398,7 @@ speed_limit(void)
 gboolean
 gps_display_details(void)
 {
 gboolean
 gps_display_details(void)
 {
-    gchar *buffer, litbuf[16];
+    gchar *buffer, litbuf[LL_FMT_LEN], buffer2[LL_FMT_LEN];
     printf("%s()\n", __PRETTY_FUNCTION__);
 
     if(_gps.fix < 2)
     printf("%s()\n", __PRETTY_FUNCTION__);
 
     if(_gps.fix < 2)
@@ -214,13 +415,17 @@ gps_display_details(void)
     {
         gfloat speed = _gps.speed * UNITS_CONVERT[_units];
 
     {
         gfloat speed = _gps.speed * UNITS_CONVERT[_units];
 
+        format_lat_lon(_gps.lat, _gps.lon, litbuf, buffer2);
+        
         /* latitude */
         /* latitude */
-        lat_format(_gps.lat, litbuf);
+        //lat_format(_gps.lat, litbuf);
         gtk_label_set_label(GTK_LABEL(_sdi_lat), litbuf);
 
         /* longitude */
         gtk_label_set_label(GTK_LABEL(_sdi_lat), litbuf);
 
         /* longitude */
-        lon_format(_gps.lon, litbuf);
-        gtk_label_set_label(GTK_LABEL(_sdi_lon), litbuf);
+        //lon_format(_gps.lon, litbuf);
+        //gtk_label_set_label(GTK_LABEL(_sdi_lon), litbuf);
+        if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+               gtk_label_set_label(GTK_LABEL(_sdi_lon), buffer2);
 
         /* speed */
         switch(_units)
 
         /* speed */
         switch(_units)
@@ -337,7 +542,7 @@ gps_display_details(void)
 void
 gps_display_data(void)
 {
 void
 gps_display_data(void)
 {
-    gchar *buffer, litbuf[LL_FMT_LEN];
+    gchar *buffer, litbuf[LL_FMT_LEN], buffer2[LL_FMT_LEN];
     printf("%s()\n", __PRETTY_FUNCTION__);
 
     if(_gps.fix < 2)
     printf("%s()\n", __PRETTY_FUNCTION__);
 
     if(_gps.fix < 2)
@@ -353,13 +558,16 @@ gps_display_data(void)
     {
         gfloat speed = _gps.speed * UNITS_CONVERT[_units];
 
     {
         gfloat speed = _gps.speed * UNITS_CONVERT[_units];
 
+        format_lat_lon(_gps.lat, _gps.lon, litbuf, buffer2);
+        
         /* latitude */
         /* latitude */
-        lat_format(_gps.lat, litbuf);
+        //lat_format(_gps.lat, litbuf);
         gtk_label_set_label(GTK_LABEL(_text_lat), litbuf);
 
         /* longitude */
         gtk_label_set_label(GTK_LABEL(_text_lat), litbuf);
 
         /* longitude */
-        lon_format(_gps.lon, litbuf);
-        gtk_label_set_label(GTK_LABEL(_text_lon), litbuf);
+        //lon_format(_gps.lon, litbuf);
+        if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+               gtk_label_set_label(GTK_LABEL(_text_lon), buffer2);     
 
         /* speed */
         switch(_units)
 
         /* speed */
         switch(_units)
@@ -2471,12 +2679,33 @@ latlon_cb_fmt_changed(GtkWidget *widget, LatlonDialog *lld) {
   {
     gint old = _degformat; /* augh... */
     gchar buffer[LL_FMT_LEN];
   {
     gint old = _degformat; /* augh... */
     gchar buffer[LL_FMT_LEN];
+    gchar buffer2[LL_FMT_LEN];
 
     _degformat = fmt;
 
     _degformat = fmt;
-    lat_format(lld->glat, buffer);
+    
+    format_lat_lon(lld->glat, lld->glon, buffer, buffer2);
+    
+    //lat_format(lld->glat, buffer);
     gtk_label_set_label(GTK_LABEL(lld->lat), buffer);
     gtk_label_set_label(GTK_LABEL(lld->lat), buffer);
-    lon_format(lld->glon, buffer);
-    gtk_label_set_label(GTK_LABEL(lld->lon), buffer);
+    //lon_format(lld->glon, buffer);
+    
+    if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+       gtk_label_set_label(GTK_LABEL(lld->lon), buffer2);
+    else
+       gtk_label_set_label(GTK_LABEL(lld->lon), g_strdup(""));
+    
+    
+    // And set the titles
+    gtk_label_set_label(GTK_LABEL(lld->lat_title), 
+               DEG_FORMAT_ENUM_TEXT[_degformat].short_field_1 );
+    
+    if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+       gtk_label_set_label(GTK_LABEL(lld->lon_title), 
+                       DEG_FORMAT_ENUM_TEXT[_degformat].short_field_2 );
+    else
+       gtk_label_set_label(GTK_LABEL(lld->lon_title), g_strdup(""));
+    
+    
     _degformat = old;
   }
 }
     _degformat = old;
   }
 }
@@ -2488,12 +2717,24 @@ latlon_dialog(gdouble lat, gdouble lon)
     GtkWidget *dialog;
     GtkWidget *table;
     GtkWidget *label;
     GtkWidget *dialog;
     GtkWidget *table;
     GtkWidget *label;
+    GtkWidget *lbl_lon_title;
+    GtkWidget *lbl_lat_title;
     GtkWidget *txt_lat;
     GtkWidget *txt_lon;
     GtkWidget *cmb_format;
     GtkWidget *btn_copy = NULL;
     GtkWidget *txt_lat;
     GtkWidget *txt_lon;
     GtkWidget *cmb_format;
     GtkWidget *btn_copy = NULL;
+    gint prev_degformat = _degformat;
+    gint fallback_deg_format = _degformat;
+    
     printf("%s()\n", __PRETTY_FUNCTION__);
 
     printf("%s()\n", __PRETTY_FUNCTION__);
 
+    // Check that the current coord system supports the select position
+    if(!coord_system_check_lat_lon (lat, lon, &fallback_deg_format))
+    {
+       _degformat = fallback_deg_format;
+    }
+     
+    
     dialog = gtk_dialog_new_with_buttons(_("Show Position"),
             GTK_WINDOW(_window), GTK_DIALOG_MODAL,
             GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
     dialog = gtk_dialog_new_with_buttons(_("Show Position"),
             GTK_WINDOW(_window), GTK_DIALOG_MODAL,
             GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
@@ -2504,23 +2745,26 @@ latlon_dialog(gdouble lat, gdouble lon)
             table = gtk_table_new(5, 2, FALSE), TRUE, TRUE, 0);
 
     gtk_table_attach(GTK_TABLE(table),
             table = gtk_table_new(5, 2, FALSE), TRUE, TRUE, 0);
 
     gtk_table_attach(GTK_TABLE(table),
-            label = gtk_label_new(_("Lat")),
+               lbl_lat_title = gtk_label_new(/*_("Lat")*/ DEG_FORMAT_ENUM_TEXT[_degformat].short_field_1 ),
             0, 1, 0, 1, GTK_FILL, 0, 2, 4);
             0, 1, 0, 1, GTK_FILL, 0, 2, 4);
-    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+    gtk_misc_set_alignment(GTK_MISC(lbl_lat_title), 1.f, 0.5f);
     gtk_table_attach(GTK_TABLE(table),
             txt_lat = gtk_label_new(""),
             1, 2, 0, 1, GTK_FILL, 0, 2, 4);
     gtk_misc_set_alignment(GTK_MISC(txt_lat), 1.f, 0.5f);
 
     gtk_table_attach(GTK_TABLE(table),
             txt_lat = gtk_label_new(""),
             1, 2, 0, 1, GTK_FILL, 0, 2, 4);
     gtk_misc_set_alignment(GTK_MISC(txt_lat), 1.f, 0.5f);
 
+    
     gtk_table_attach(GTK_TABLE(table),
     gtk_table_attach(GTK_TABLE(table),
-            label = gtk_label_new(_("Lon")),
+            lbl_lon_title = gtk_label_new(/*_("Lon")*/ DEG_FORMAT_ENUM_TEXT[_degformat].short_field_2 ),
             0, 1, 1, 2, GTK_FILL, 0, 2, 4);
             0, 1, 1, 2, GTK_FILL, 0, 2, 4);
-    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+    gtk_misc_set_alignment(GTK_MISC(lbl_lon_title), 1.f, 0.5f);
+    
     gtk_table_attach(GTK_TABLE(table),
             txt_lon = gtk_label_new(""),
             1, 2, 1, 2, GTK_FILL, 0, 2, 4);
     gtk_misc_set_alignment(GTK_MISC(txt_lon), 1.f, 0.5f);
 
     gtk_table_attach(GTK_TABLE(table),
             txt_lon = gtk_label_new(""),
             1, 2, 1, 2, GTK_FILL, 0, 2, 4);
     gtk_misc_set_alignment(GTK_MISC(txt_lon), 1.f, 0.5f);
 
+    
     gtk_table_attach(GTK_TABLE(table),
             label = gtk_label_new(_("Format")),
             0, 1, 2, 3, GTK_FILL, 0, 2, 4);
     gtk_table_attach(GTK_TABLE(table),
             label = gtk_label_new(_("Format")),
             0, 1, 2, 3, GTK_FILL, 0, 2, 4);
@@ -2537,11 +2781,17 @@ latlon_dialog(gdouble lat, gdouble lon)
     /* Lat/Lon */
     {
       gchar buffer[LL_FMT_LEN];
     /* Lat/Lon */
     {
       gchar buffer[LL_FMT_LEN];
+      gchar buffer2[LL_FMT_LEN];
 
 
-      lat_format(lat, buffer);
+      format_lat_lon(lat, lon, buffer, buffer2);
+      
+      //lat_format(lat, buffer);
       gtk_label_set_label(GTK_LABEL(txt_lat), buffer);
       gtk_label_set_label(GTK_LABEL(txt_lat), buffer);
-      lat_format(lon, buffer);
-      gtk_label_set_label(GTK_LABEL(txt_lon), buffer);
+      //lat_format(lon, buffer);
+      if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+         gtk_label_set_label(GTK_LABEL(txt_lon), buffer2);
+      else
+         gtk_label_set_label(GTK_LABEL(txt_lon), g_strdup(""));
     }
 
     /* Fill in formats */
     }
 
     /* Fill in formats */
@@ -2550,7 +2800,7 @@ latlon_dialog(gdouble lat, gdouble lon)
 
       for(i = 0; i < DEG_FORMAT_ENUM_COUNT; i++) {
           gtk_combo_box_append_text(GTK_COMBO_BOX(cmb_format),
 
       for(i = 0; i < DEG_FORMAT_ENUM_COUNT; i++) {
           gtk_combo_box_append_text(GTK_COMBO_BOX(cmb_format),
-                  DEG_FORMAT_ENUM_TEXT[i]);
+                  DEG_FORMAT_ENUM_TEXT[i].name);
       }
       gtk_combo_box_set_active(GTK_COMBO_BOX(cmb_format), _degformat);
     }
       }
       gtk_combo_box_set_active(GTK_COMBO_BOX(cmb_format), _degformat);
     }
@@ -2563,6 +2813,9 @@ latlon_dialog(gdouble lat, gdouble lon)
     lld.lat = txt_lat;
     lld.lon = txt_lon;
 
     lld.lat = txt_lat;
     lld.lon = txt_lon;
 
+    lld.lat_title = lbl_lat_title;
+    lld.lon_title = lbl_lon_title;
+    
     /* Connect Signals */
     g_signal_connect(G_OBJECT(cmb_format), "changed",
                     G_CALLBACK(latlon_cb_fmt_changed), &lld);
     /* Connect Signals */
     g_signal_connect(G_OBJECT(cmb_format), "changed",
                     G_CALLBACK(latlon_cb_fmt_changed), &lld);
@@ -2572,6 +2825,9 @@ latlon_dialog(gdouble lat, gdouble lon)
     gtk_widget_show_all(dialog);
 
     gtk_dialog_run(GTK_DIALOG(dialog));
     gtk_widget_show_all(dialog);
 
     gtk_dialog_run(GTK_DIALOG(dialog));
+    
+    _degformat = prev_degformat; // Put back incase it had to be auto changed
+    
     gtk_widget_hide(dialog);
 
     vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
     gtk_widget_hide(dialog);
 
     vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
@@ -2686,6 +2942,16 @@ display_init()
     pango_font = pango_font_description_new();
     pango_font_description_set_size(pango_font, 12 * PANGO_SCALE);
     pango_layout_set_font_description(_scale_layout, pango_font);
     pango_font = pango_font_description_new();
     pango_font_description_set_size(pango_font, 12 * PANGO_SCALE);
     pango_layout_set_font_description(_scale_layout, pango_font);
+    
+#ifdef INCLUDE_APRS
+    /* APRS Labels */
+    pango_context = gtk_widget_get_pango_context(_map_widget);
+    _aprs_label_layout = pango_layout_new(pango_context);
+    pango_font = pango_font_description_new();
+    pango_font_description_set_size(pango_font, 10 * PANGO_SCALE);
+    pango_font_description_set_family(pango_font,"Sans Serif");
+    pango_layout_set_font_description(_aprs_label_layout, pango_font);
+#endif // INCLUDE_APRS
 
     /* zoom box */
     pango_context = gtk_widget_get_pango_context(_map_widget);
 
     /* zoom box */
     pango_context = gtk_widget_get_pango_context(_map_widget);
index 76973af9a22135281c9690de8a334e32711f93ad..7f60fb0506d84950586537ba05e06e602f30efd3 100644 (file)
@@ -29,6 +29,8 @@ typedef struct {
     GtkWidget *fmt_combo;
     GtkWidget *lat;
     GtkWidget *lon;
     GtkWidget *fmt_combo;
     GtkWidget *lat;
     GtkWidget *lon;
+    GtkWidget *lat_title;
+    GtkWidget *lon_title;
 } LatlonDialog;
 
 
 } LatlonDialog;
 
 
@@ -74,4 +76,8 @@ gboolean display_open_file(GtkWindow *parent, gchar **bytes_out,
 
 void display_init(void);
 
 
 void display_init(void);
 
+#ifdef INCLUDE_APRS
+void plot_aprs_station(AprsDataRow *p_station, gboolean single );
+#endif // INCLUDE_APRS
+
 #endif /* ifndef MAEMO_MAPPER_DISPLAY_H */
 #endif /* ifndef MAEMO_MAPPER_DISPLAY_H */
index 5dad65befdc26a7d05d082aa847d7b31ce0b030e..1a1deed0eba058ad339735231dcf01b9495451c6 100644 (file)
@@ -94,32 +94,6 @@ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WAR
 #define DEFAULT_TIMEOUT (1000*60) /* 60 second timeout (in ms) */
 #endif
 
 #define DEFAULT_TIMEOUT (1000*60) /* 60 second timeout (in ms) */
 #endif
 
-/* BT dbus service location */
-#define BASE_PATH                "/org/bluez"
-#define BASE_INTERFACE           "org.bluez"
-#define ADAPTER_PATH             BASE_PATH
-#define ADAPTER_INTERFACE        BASE_INTERFACE ".Adapter"
-#define MANAGER_PATH             BASE_PATH
-#define MANAGER_INTERFACE        BASE_INTERFACE ".Manager"
-#define ERROR_INTERFACE          BASE_INTERFACE ".Error"
-#define SECURITY_INTERFACE       BASE_INTERFACE ".Security"
-#define RFCOMM_INTERFACE         BASE_INTERFACE ".RFCOMM"
-#define BLUEZ_DBUS               BASE_INTERFACE
-
-#define LIST_ADAPTERS            "ListAdapters"
-#define LIST_BONDINGS            "ListBondings"
-#define CREATE_BONDING           "CreateBonding"
-#define GET_REMOTE_NAME          "GetRemoteName"
-#define GET_REMOTE_SERVICE_CLASSES "GetRemoteServiceClasses"
-
-#define BTCOND_PATH              "/com/nokia/btcond/request"
-#define BTCOND_BASE              "com.nokia.btcond"
-#define BTCOND_INTERFACE         BTCOND_BASE ".request"
-#define BTCOND_REQUEST           BTCOND_INTERFACE
-#define BTCOND_CONNECT           "rfcomm_connect"
-#define BTCOND_DISCONNECT        "rfcomm_disconnect"
-#define BTCOND_DBUS              BTCOND_BASE
-
 #define GPS_SERVICE_CLASS_STR "positioning"
 
 typedef struct {
 #define GPS_SERVICE_CLASS_STR "positioning"
 
 typedef struct {
diff --git a/src/hashtable.c b/src/hashtable.c
new file mode 100644 (file)
index 0000000..e005277
--- /dev/null
@@ -0,0 +1,296 @@
+
+
+/*
+ * 
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Maemo Mapper is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Maemo Mapper.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * 
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ * 
+ *  * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000  Frank Giannandrea
+ * Copyright (C) 2000-2007  The Xastir Group
+ * Copyright (C) 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk>
+ */
+
+#ifdef HAVE_CONFIG_H
+#    include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#include "hashtable.h"
+#include "hashtable_private.h"
+
+/*
+Credit for primes table: Aaron Krowne
+ http://br.endernet.org/~akrowne/
+ http://planetmath.org/encyclopedia/GoodHashTablePrimes.html
+*/
+static const unsigned int primes[] = {
+53, 97, 193, 389,
+769, 1543, 3079, 6151,
+12289, 24593, 49157, 98317,
+196613, 393241, 786433, 1572869,
+3145739, 6291469, 12582917, 25165843,
+50331653, 100663319, 201326611, 402653189,
+805306457, 1610612741
+};
+const unsigned int prime_table_length = sizeof(primes)/sizeof(primes[0]);
+const float max_load_factor = 0.65;
+
+/*****************************************************************************/
+struct hashtable *
+create_hashtable(unsigned int minsize,
+                 unsigned int (*hashf) (void*),
+                 int (*eqf) (void*,void*))
+{
+    struct hashtable *h;
+    unsigned int pindex, size = primes[0];
+    /* Check requested hashtable isn't too large */
+    if (minsize > (1u << 30)) return NULL;
+    /* Enforce size as prime */
+    for (pindex=0; pindex < prime_table_length; pindex++) {
+        if (primes[pindex] > minsize) { size = primes[pindex]; break; }
+    }
+    h = (struct hashtable *)malloc(sizeof(struct hashtable));
+    if (NULL == h) return NULL; /*oom*/
+    h->table = (struct entry **)malloc(sizeof(struct entry*) * size);
+    if (NULL == h->table) { free(h); return NULL; } /*oom*/
+    memset(h->table, 0, size * sizeof(struct entry *));
+    h->tablelength  = size;
+    h->primeindex   = pindex;
+    h->entrycount   = 0;
+    h->hashfn       = hashf;
+    h->eqfn         = eqf;
+    h->loadlimit    = (unsigned int) ceil(size * max_load_factor);
+    return h;
+}
+
+/*****************************************************************************/
+unsigned int
+hash(struct hashtable *h, void *k)
+{
+    /* Aim to protect against poor hash functions by adding logic here
+     * - logic taken from java 1.4 hashtable source */
+    unsigned int i = h->hashfn(k);
+    i += ~(i << 9);
+    i ^=  ((i >> 14) | (i << 18)); /* >>> */
+    i +=  (i << 4);
+    i ^=  ((i >> 10) | (i << 22)); /* >>> */
+    return i;
+}
+
+/*****************************************************************************/
+static int
+hashtable_expand(struct hashtable *h)
+{
+    /* Double the size of the table to accomodate more entries */
+    struct entry **newtable;
+    struct entry *e;
+    struct entry **pE;
+    unsigned int newsize, i, index;
+    /* Check we're not hitting max capacity */
+    if (h->primeindex == (prime_table_length - 1)) return 0;
+    newsize = primes[++(h->primeindex)];
+
+    newtable = (struct entry **)malloc(sizeof(struct entry*) * newsize);
+    if (NULL != newtable)
+    {
+        memset(newtable, 0, newsize * sizeof(struct entry *));
+        /* This algorithm is not 'stable'. ie. it reverses the list
+         * when it transfers entries between the tables */
+        for (i = 0; i < h->tablelength; i++) {
+            while (NULL != (e = h->table[i])) {
+                h->table[i] = e->next;
+                index = indexFor(newsize,e->h);
+                e->next = newtable[index];
+                newtable[index] = e;
+            }
+        }
+        free(h->table);
+        h->table = newtable;
+    }
+    /* Plan B: realloc instead */
+    else 
+    {
+        newtable = (struct entry **)
+                   realloc(h->table, newsize * sizeof(struct entry *));
+        if (NULL == newtable) { (h->primeindex)--; return 0; }
+        h->table = newtable;
+        memset(newtable[h->tablelength], 0, newsize - h->tablelength);
+        for (i = 0; i < h->tablelength; i++) {
+            for (pE = &(newtable[i]), e = *pE; e != NULL; e = *pE) {
+                index = indexFor(newsize,e->h);
+                if (index == i)
+                {
+                    pE = &(e->next);
+                }
+                else
+                {
+                    *pE = e->next;
+                    e->next = newtable[index];
+                    newtable[index] = e;
+                }
+            }
+        }
+    }
+    h->tablelength = newsize;
+    h->loadlimit   = (unsigned int) ceil(newsize * max_load_factor);
+    return -1;
+}
+
+/*****************************************************************************/
+unsigned int
+hashtable_count(struct hashtable *h)
+{
+    return h->entrycount;
+}
+
+/*****************************************************************************/
+int
+hashtable_insert(struct hashtable *h, void *k, void *v)
+{
+    /* This method allows duplicate keys - but they shouldn't be used */
+    unsigned int index;
+    struct entry *e;
+    if (++(h->entrycount) > h->loadlimit)
+    {
+        /* Ignore the return value. If expand fails, we should
+         * still try cramming just this value into the existing table
+         * -- we may not have memory for a larger table, but one more
+         * element may be ok. Next time we insert, we'll try expanding again.*/
+        hashtable_expand(h);
+    }
+    e = (struct entry *)malloc(sizeof(struct entry));
+    if (NULL == e) { --(h->entrycount); return 0; } /*oom*/
+    e->h = hash(h,k);
+    index = indexFor(h->tablelength,e->h);
+    e->k = k;
+    e->v = v;
+    e->next = h->table[index];
+    h->table[index] = e;
+    return -1;
+}
+
+/*****************************************************************************/
+void * /* returns value associated with key */
+hashtable_search(struct hashtable *h, void *k)
+{
+    struct entry *e;
+    unsigned int hashvalue, index;
+    hashvalue = hash(h,k);
+    index = indexFor(h->tablelength,hashvalue);
+    e = h->table[index];
+    while (NULL != e)
+    {
+        /* Check hash value to short circuit heavier comparison */
+        if ((hashvalue == e->h) && (h->eqfn(k, e->k))) return e->v;
+        e = e->next;
+    }
+    return NULL;
+}
+
+/*****************************************************************************/
+void * /* returns value associated with key */
+hashtable_remove(struct hashtable *h, void *k)
+{
+    /* TODO: consider compacting the table when the load factor drops enough,
+     *       or provide a 'compact' method. */
+
+    struct entry *e;
+    struct entry **pE;
+    void *v;
+    unsigned int hashvalue, index;
+
+    hashvalue = hash(h,k);
+    index = indexFor(h->tablelength,hash(h,k));
+    pE = &(h->table[index]);
+    e = *pE;
+    while (NULL != e)
+    {
+        /* Check hash value to short circuit heavier comparison */
+        if ((hashvalue == e->h) && (h->eqfn(k, e->k)))
+        {
+            *pE = e->next;
+            h->entrycount--;
+            v = e->v;
+            freekey(e->k);
+            free(e);
+            return v;
+        }
+        pE = &(e->next);
+        e = e->next;
+    }
+    return NULL;
+}
+
+/*****************************************************************************/
+/* destroy */
+void
+hashtable_destroy(struct hashtable *h, int free_values)
+{
+    unsigned int i;
+    struct entry *e, *f;
+    struct entry **table = h->table;
+    if (free_values)
+    {
+        for (i = 0; i < h->tablelength; i++)
+        {
+            e = table[i];
+            while (NULL != e)
+            { f = e; e = e->next; freekey(f->k); free(f->v); free(f); }
+        }
+    }
+    else
+    {
+        for (i = 0; i < h->tablelength; i++)
+        {
+            e = table[i];
+            while (NULL != e)
+            { f = e; e = e->next; freekey(f->k); free(f); }
+        }
+    }
+    free(h->table);
+    free(h);
+}
+
+#endif // INCLUDE_APRS
+
+/*
+ * Copyright (C) 2002 Christopher Clark <firstname.lastname@cl.cam.ac.uk>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * */
+
+
diff --git a/src/hashtable.h b/src/hashtable.h
new file mode 100644 (file)
index 0000000..eb31085
--- /dev/null
@@ -0,0 +1,220 @@
+
+
+/*
+ * 
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Maemo Mapper is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Maemo Mapper.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * 
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ * 
+ *  * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000  Frank Giannandrea
+ * Copyright (C) 2000-2007  The Xastir Group
+ * Copyright (C) 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk>
+ */
+
+#ifdef HAVE_CONFIG_H
+#    include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#ifndef __HASHTABLE_CWC22_H__
+#define __HASHTABLE_CWC22_H__
+
+struct hashtable;
+
+/* Example of use:
+ *
+ *      struct hashtable  *h;
+ *      struct some_key   *k;
+ *      struct some_value *v;
+ *
+ *      static unsigned int         hash_from_key_fn( void *k );
+ *      static int                  keys_equal_fn ( void *key1, void *key2 );
+ *
+ *      h = create_hashtable(16, hash_from_key_fn, keys_equal_fn);
+ *      k = (struct some_key *)     malloc(sizeof(struct some_key));
+ *      v = (struct some_value *)   malloc(sizeof(struct some_value));
+ *
+ *      (initialise k and v to suitable values)
+ * 
+ *      if (! hashtable_insert(h,k,v) )
+ *      {     exit(-1);               }
+ *
+ *      if (NULL == (found = hashtable_search(h,k) ))
+ *      {    printf("not found!");                  }
+ *
+ *      if (NULL == (found = hashtable_remove(h,k) ))
+ *      {    printf("Not found\n");                 }
+ *
+ */
+
+/* Macros may be used to define type-safe(r) hashtable access functions, with
+ * methods specialized to take known key and value types as parameters.
+ * 
+ * Example:
+ *
+ * Insert this at the start of your file:
+ *
+ * DEFINE_HASHTABLE_INSERT(insert_some, struct some_key, struct some_value);
+ * DEFINE_HASHTABLE_SEARCH(search_some, struct some_key, struct some_value);
+ * DEFINE_HASHTABLE_REMOVE(remove_some, struct some_key, struct some_value);
+ *
+ * This defines the functions 'insert_some', 'search_some' and 'remove_some'.
+ * These operate just like hashtable_insert etc., with the same parameters,
+ * but their function signatures have 'struct some_key *' rather than
+ * 'void *', and hence can generate compile time errors if your program is
+ * supplying incorrect data as a key (and similarly for value).
+ *
+ * Note that the hash and key equality functions passed to create_hashtable
+ * still take 'void *' parameters instead of 'some key *'. This shouldn't be
+ * a difficult issue as they're only defined and passed once, and the other
+ * functions will ensure that only valid keys are supplied to them.
+ *
+ * The cost for this checking is increased code size and runtime overhead
+ * - if performance is important, it may be worth switching back to the
+ * unsafe methods once your program has been debugged with the safe methods.
+ * This just requires switching to some simple alternative defines - eg:
+ * #define insert_some hashtable_insert
+ *
+ */
+
+/*****************************************************************************
+ * create_hashtable
+   
+ * @name                    create_hashtable
+ * @param   minsize         minimum initial size of hashtable
+ * @param   hashfunction    function for hashing keys
+ * @param   key_eq_fn       function for determining key equality
+ * @return                  newly created hashtable or NULL on failure
+ */
+
+struct hashtable *
+create_hashtable(unsigned int minsize,
+                 unsigned int (*hashfunction) (void*),
+                 int (*key_eq_fn) (void*,void*));
+
+/*****************************************************************************
+ * hashtable_insert
+   
+ * @name        hashtable_insert
+ * @param   h   the hashtable to insert into
+ * @param   k   the key - hashtable claims ownership and will free on removal
+ * @param   v   the value - does not claim ownership
+ * @return      non-zero for successful insertion
+ *
+ * This function will cause the table to expand if the insertion would take
+ * the ratio of entries to table size over the maximum load factor.
+ *
+ * This function does not check for repeated insertions with a duplicate key.
+ * The value returned when using a duplicate key is undefined -- when
+ * the hashtable changes size, the order of retrieval of duplicate key
+ * entries is reversed.
+ * If in doubt, remove before insert.
+ */
+
+int 
+hashtable_insert(struct hashtable *h, void *k, void *v);
+
+#define DEFINE_HASHTABLE_INSERT(fnname, keytype, valuetype) \
+int fnname (struct hashtable *h, keytype *k, valuetype *v) \
+{ \
+    return hashtable_insert(h,k,v); \
+}
+
+/*****************************************************************************
+ * hashtable_search
+   
+ * @name        hashtable_search
+ * @param   h   the hashtable to search
+ * @param   k   the key to search for  - does not claim ownership
+ * @return      the value associated with the key, or NULL if none found
+ */
+
+void *
+hashtable_search(struct hashtable *h, void *k);
+
+#define DEFINE_HASHTABLE_SEARCH(fnname, keytype, valuetype) \
+valuetype * fnname (struct hashtable *h, keytype *k) \
+{ \
+    return (valuetype *) (hashtable_search(h,k)); \
+}
+
+/*****************************************************************************
+ * hashtable_remove
+   
+ * @name        hashtable_remove
+ * @param   h   the hashtable to remove the item from
+ * @param   k   the key to search for  - does not claim ownership
+ * @return      the value associated with the key, or NULL if none found
+ */
+
+void * /* returns value */
+hashtable_remove(struct hashtable *h, void *k);
+
+#define DEFINE_HASHTABLE_REMOVE(fnname, keytype, valuetype) \
+valuetype * fnname (struct hashtable *h, keytype *k) \
+{ \
+    return (valuetype *) (hashtable_remove(h,k)); \
+}
+
+
+/*****************************************************************************
+ * hashtable_count
+   
+ * @name        hashtable_count
+ * @param   h   the hashtable
+ * @return      the number of items stored in the hashtable
+ */
+unsigned int
+hashtable_count(struct hashtable *h);
+
+
+/*****************************************************************************
+ * hashtable_destroy
+   
+ * @name        hashtable_destroy
+ * @param   h   the hashtable
+ * @param       free_values     whether to call 'free' on the remaining values
+ */
+
+void
+hashtable_destroy(struct hashtable *h, int free_values);
+
+#endif /* __HASHTABLE_CWC22_H__ */
+
+#endif // INCLUDE_APRS
+
+/*
+ * Copyright (C) 2002 Christopher Clark <firstname.lastname@cl.cam.ac.uk>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * */
+
+
diff --git a/src/hashtable_itr.c b/src/hashtable_itr.c
new file mode 100644 (file)
index 0000000..f2ca872
--- /dev/null
@@ -0,0 +1,236 @@
+/*
+ * 
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Maemo Mapper is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Maemo Mapper.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * 
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ * 
+ *  * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000  Frank Giannandrea
+ * Copyright (C) 2000-2007  The Xastir Group
+ * Copyright (C) 2002, 2004 Christopher Clark  <firstname.lastname@cl.cam.ac.uk> 
+ * 
+ */
+
+#ifdef HAVE_CONFIG_H
+#    include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#include <stdlib.h> /* defines NULL */
+#include <stdio.h>
+//#include <string.h>
+//#include <math.h>
+
+#include "hashtable.h"
+#include "hashtable_private.h"
+#include "hashtable_itr.h"
+
+// Must be last include file
+//#include "leak_detection.h" /* defines GC_MALLOC/GC_FREE */
+
+
+
+
+
+/*****************************************************************************/
+/* hashtable_iterator    - iterator constructor */
+
+struct hashtable_itr *
+hashtable_iterator(struct hashtable *h)
+{
+    unsigned int i, tablelength;
+    struct hashtable_itr *itr = (struct hashtable_itr *)
+        malloc(sizeof(struct hashtable_itr));
+    if (NULL == itr) return NULL;
+    itr->h = h;
+    itr->e = NULL;
+    itr->parent = NULL;
+    tablelength = h->tablelength;
+    itr->index = tablelength;
+    if (0 == h->entrycount) return itr;
+
+    for (i = 0; i < tablelength; i++)
+    {
+        if (NULL != h->table[i])
+        {
+            itr->e = h->table[i];
+            itr->index = i;
+            break;
+        }
+    }
+    return itr;
+}
+
+/*****************************************************************************/
+/* key      - return the key of the (key,value) pair at the current position */
+/* value    - return the value of the (key,value) pair at the current position */
+
+void *
+hashtable_iterator_key(struct hashtable_itr *i)
+{ 
+    if (!i) 
+        return NULL;
+    if (i->e) 
+        return i->e->k;
+    else 
+        return NULL;
+}
+
+void *
+hashtable_iterator_value(struct hashtable_itr *i)
+{ 
+    if (!i) 
+        return NULL;
+    if (i->e) {
+        return i->e->v;
+    } else {
+        return NULL;
+    }
+
+}
+
+/*****************************************************************************/
+/* advance - advance the iterator to the next element
+ *           returns zero if advanced to end of table */
+
+int
+hashtable_iterator_advance(struct hashtable_itr *itr)
+{
+    unsigned int j,tablelength;
+    struct entry **table;
+    struct entry *next;
+    if (NULL == itr->e) return 0; /* stupidity check */
+
+    next = itr->e->next;
+    if (NULL != next)
+    {
+        itr->parent = itr->e;
+        itr->e = next;
+        return -1;
+    }
+    tablelength = itr->h->tablelength;
+    itr->parent = NULL;
+    if (tablelength <= (j = ++(itr->index)))
+    {
+        itr->e = NULL;
+        return 0;
+    }
+    table = itr->h->table;
+    while (NULL == (next = table[j]))
+    {
+        if (++j >= tablelength)
+        {
+            itr->index = tablelength;
+            itr->e = NULL;
+            return 0;
+        }
+    }
+    itr->index = j;
+    itr->e = next;
+    return -1;
+}
+
+/*****************************************************************************/
+/* remove - remove the entry at the current iterator position
+ *          and advance the iterator, if there is a successive
+ *          element.
+ *          If you want the value, read it before you remove:
+ *          beware memory leaks if you don't.
+ *          Returns zero if end of iteration. */
+
+int
+hashtable_iterator_remove(struct hashtable_itr *itr)
+{
+    struct entry *remember_e, *remember_parent;
+    int ret;
+
+    /* Do the removal */
+    if (NULL == (itr->parent))
+    {
+        /* element is head of a chain */
+        itr->h->table[itr->index] = itr->e->next;
+    } else {
+        /* element is mid-chain */
+        itr->parent->next = itr->e->next;
+    }
+    /* itr->e is now outside the hashtable */
+    remember_e = itr->e;
+    itr->h->entrycount--;
+    freekey(remember_e->k);
+
+    /* Advance the iterator, correcting the parent */
+    remember_parent = itr->parent;
+    ret = hashtable_iterator_advance(itr);
+    if (itr->parent == remember_e) { itr->parent = remember_parent; }
+    free(remember_e);
+    return ret;
+}
+
+/*****************************************************************************/
+int /* returns zero if not found */
+hashtable_iterator_search(struct hashtable_itr *itr,
+                          struct hashtable *h, void *k)
+{
+    struct entry *e, *parent;
+    unsigned int hashvalue, index;
+
+    hashvalue = hash(h,k);
+    index = indexFor(h->tablelength,hashvalue);
+
+    e = h->table[index];
+    parent = NULL;
+    while (NULL != e)
+    {
+        /* Check hash value to short circuit heavier comparison */
+        if ((hashvalue == e->h) && (h->eqfn(k, e->k)))
+        {
+            itr->index = index;
+            itr->e = e;
+            itr->parent = parent;
+            itr->h = h;
+            return -1;
+        }
+        parent = e;
+        e = e->next;
+    }
+    return 0;
+}
+
+
+/*
+ * Copyright (C) 2002, 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * */
+
+
+#endif // INCLUDE_APRS
+
+
diff --git a/src/hashtable_itr.h b/src/hashtable_itr.h
new file mode 100644 (file)
index 0000000..35dc8ea
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * 
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Maemo Mapper is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Maemo Mapper.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * 
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ * 
+ *  * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000  Frank Giannandrea
+ * Copyright (C) 2000-2007  The Xastir Group
+ * Copyright (C) 2002, 2004 Christopher Clark  <firstname.lastname@cl.cam.ac.uk> 
+ * 
+ */
+
+#ifdef HAVE_CONFIG_H
+#    include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#ifndef __HASHTABLE_ITR_CWC22__
+#define __HASHTABLE_ITR_CWC22__
+#include "hashtable.h"
+#include "hashtable_private.h" /* needed to enable inlining */
+
+/*****************************************************************************/
+/* This struct is only concrete here to allow the inlining of two of the
+ * accessor functions. */
+struct hashtable_itr
+{
+    struct hashtable *h;
+    struct entry *e;
+    struct entry *parent;
+    unsigned int index;
+};
+
+
+/*****************************************************************************/
+/* hashtable_iterator
+ */
+
+struct hashtable_itr *
+hashtable_iterator(struct hashtable *h);
+
+#if 0
+// BZZZZT!  it is very, very wrong to be inlining this this way.
+// If one calls hashtable_iterator on a hash table from which everything
+// has been deleted, the iterator has a null for i->e.  
+// It is not good to require the caller to check the internals of the iterator
+// structure just to be sure there are no null pointers inside.
+// For whatever reason, these are defined again in the hashtable_iterator.c
+// file, not inlined.  I have modified the ones in hashtable_iterator so they
+// actually check for nulls and don't try to dereference them.
+/*****************************************************************************/
+/* hashtable_iterator_key
+ * - return the value of the (key,value) pair at the current position */
+
+extern inline void *
+hashtable_iterator_key(struct hashtable_itr *i)
+{
+    return i->e->k;
+}
+
+/*****************************************************************************/
+/* value - return the value of the (key,value) pair at the current position */
+
+extern inline void *
+hashtable_iterator_value(struct hashtable_itr *i)
+{
+    return i->e->v;
+}
+#else
+// SO instead of inlining, just declare.  No need to be "extern"
+// The ones in the .c file check their arguments and return nulls if they
+// can't comply with the request.  Much nicer for the calling routine to 
+// check a return value than to monkey with the internals of the struct.
+void * hashtable_iterator_key(struct hashtable_itr *i);
+void * hashtable_iterator_value(struct hashtable_itr *i);
+#endif 
+
+/*****************************************************************************/
+/* advance - advance the iterator to the next element
+ *           returns zero if advanced to end of table */
+
+int
+hashtable_iterator_advance(struct hashtable_itr *itr);
+
+/*****************************************************************************/
+/* remove - remove current element and advance the iterator to the next element
+ *          NB: if you need the value to free it, read it before
+ *          removing. ie: beware memory leaks!
+ *          returns zero if advanced to end of table */
+
+int
+hashtable_iterator_remove(struct hashtable_itr *itr);
+
+/*****************************************************************************/
+/* search - overwrite the supplied iterator, to point to the entry
+ *          matching the supplied key.
+            h points to the hashtable to be searched.
+ *          returns zero if not found. */
+int
+hashtable_iterator_search(struct hashtable_itr *itr,
+                          struct hashtable *h, void *k);
+
+#define DEFINE_HASHTABLE_ITERATOR_SEARCH(fnname, keytype) \
+int fnname (struct hashtable_itr *i, struct hashtable *h, keytype *k) \
+{ \
+    return (hashtable_iterator_search(i,h,k)); \
+}
+
+
+
+#endif /* __HASHTABLE_ITR_CWC22__*/
+
+#endif // INCLUDE_APRS
+
+/*
+ * Copyright (C) 2002, 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * */
+
+
diff --git a/src/hashtable_private.h b/src/hashtable_private.h
new file mode 100644 (file)
index 0000000..74796e1
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * 
+ * This file is part of Maemo Mapper.
+ *
+ * Maemo Mapper is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Maemo Mapper is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Maemo Mapper.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * 
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ * 
+ *  * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000  Frank Giannandrea
+ * Copyright (C) 2000-2007  The Xastir Group
+ * Copyright (C) 2002, 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk> 
+ * 
+ */
+
+#ifdef HAVE_CONFIG_H
+#    include "config.h"
+#endif
+
+#ifdef INCLUDE_APRS
+
+#ifndef __HASHTABLE_PRIVATE_CWC22_H__
+#define __HASHTABLE_PRIVATE_CWC22_H__
+
+#include "hashtable.h"
+
+
+/*****************************************************************************/
+struct entry
+{
+    void *k, *v;
+    unsigned int h;
+    struct entry *next;
+};
+
+struct hashtable {
+    unsigned int tablelength;
+    struct entry **table;
+    unsigned int entrycount;
+    unsigned int loadlimit;
+    unsigned int primeindex;
+    unsigned int (*hashfn) (void *k);
+    int (*eqfn) (void *k1, void *k2);
+};
+
+/*****************************************************************************/
+unsigned int
+hash(struct hashtable *h, void *k);
+
+/*****************************************************************************/
+/* indexFor */
+static inline unsigned int
+indexFor(unsigned int tablelength, unsigned int hashvalue) {
+    return (hashvalue % tablelength);
+}
+
+/* Only works if tablelength == 2^N */
+/*static inline unsigned int
+indexFor(unsigned int tablelength, unsigned int hashvalue)
+{
+    return (hashvalue & (tablelength - 1u));
+}
+*/
+
+/*****************************************************************************/
+#define freekey(X) free(X)
+/*define freekey(X) ; */
+
+
+/*****************************************************************************/
+
+#endif /* __HASHTABLE_PRIVATE_CWC22_H__*/
+
+
+#endif // INCLUDE_APRS
+
+/*
+ * Copyright (C) 2002 Christopher Clark <firstname.lastname@cl.cam.ac.uk>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ * */
+
+
index bc48c1f9984324906dcb31ae78138563cdd46f58..4af8e219c33a74c67bd9ab5ab2380c85b7e12653 100644 (file)
@@ -643,7 +643,22 @@ map_cb_button_release(GtkWidget *widget, GdkEventButton *event)
         Point clkpt;
         screen2unit(event->x, event->y, clkpt.unitx, clkpt.unity);
 
         Point clkpt;
         screen2unit(event->x, event->y, clkpt.unitx, clkpt.unity);
 
+#ifdef INCLUDE_APRS        
+        gboolean aprs_found = FALSE;
+
+        if(_aprs_enable &&  (_poi_zoom > _zoom) )
+        {
+            AprsDisplayData poi;
+            selected_point = select_aprs(
+                    clkpt.unitx, clkpt.unity, TRUE);
+            
+            aprs_found = selected_point;
+        }
+
+        if(!aprs_found && _show_poi && (_poi_zoom > _zoom))
+#else
         if(_show_poi && (_poi_zoom > _zoom))
         if(_show_poi && (_poi_zoom > _zoom))
+#endif // INCLUDE_APRS
         {
             PoiInfo poi;
             selected_point = select_poi(
         {
             PoiInfo poi;
             selected_point = select_poi(
index 5ce2abf349c819d5205b9880722a8b48e04bd96c..0a30384b6876992841a3396c57bbfa8176971822 100644 (file)
@@ -60,6 +60,7 @@
 #include "dbus-ifc.h"
 #include "display.h"
 #include "gps.h"
 #include "dbus-ifc.h"
 #include "display.h"
 #include "gps.h"
+#include "aprs.h"
 #include "gpx.h"
 #include "input.h"
 #include "main.h"
 #include "gpx.h"
 #include "input.h"
 #include "main.h"
@@ -232,6 +233,72 @@ maemo_mapper_destroy()
     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
 }
 
     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
 }
 
+// Called once per second
+gboolean timer_callback (gpointer data) {
+       
+#ifdef INCLUDE_APRS
+       static int inet_beacon_count = 0;
+       static int inet_aprs_filter = 0;
+       static int tty_beacon_count = 0;
+       
+       if(_aprs_enable)
+       {
+               // Check if we need to resent the filter every 5 minutes
+               // Note - this should really be dependent on speed and/or range
+               if(_aprs_inet_enable && inet_aprs_filter > (60*5))
+               {
+                       // Currently this only needs to be sent on connection
+                       //update_aprs_inet_options(FALSE);
+                       inet_aprs_filter = 0;
+               }
+               
+               if(_aprs_inet_enable && _aprs_enable_inet_tx && _aprs_inet_beacon_interval > 0 && _aprs_inet_state      == RCVR_UP)
+               {
+                       
+                       
+                       if(inet_beacon_count >= _aprs_inet_beacon_interval)
+                       {
+                               // Send inet APRS beacon
+                               aprs_send_beacon(APRS_PORT_INET);
+                               //g_idle_add((GSourceFunc)aprs_send_beacon, APRS_PORT_INET);
+                               //fprintf(stderr, "timer %u, %u\n", inet_beacon_count, _aprs_inet_beacon_interval);
+                               inet_beacon_count = 0;
+                       }
+                       
+               }
+               
+               if(_aprs_tty_enable && _aprs_enable_tty_tx )
+               {
+                       // Send tty APRS beacon
+                       
+                       if(tty_beacon_count >= _aprs_tty_beacon_interval && _aprs_tty_beacon_interval > 0)
+                       {
+                               // Send inet APRS beacon
+                               aprs_send_beacon(APRS_PORT_TTY);
+                               
+                               //g_idle_add((GSourceFunc)aprs_send_beacon_inet, NULL);
+                               
+                               tty_beacon_count = 0;
+                       }
+
+               }
+               
+
+               inet_aprs_filter++;
+               inet_beacon_count++;
+               tty_beacon_count++;
+       }
+#endif //INCLUDE_APRS
+       
+       return TRUE; // Continue timer
+}
+
+// Start a one second timer - this can be used to execute any periodic tasks 
+void timer_init()
+{
+       g_timeout_add(1000 , timer_callback, NULL);
+}
+
 /**
  * Initialize everything required in preparation for calling gtk_main().
  */
 /**
  * Initialize everything required in preparation for calling gtk_main().
  */
@@ -345,16 +412,92 @@ maemo_mapper_init(gint argc, gchar **argv)
         = _("Reset Bluetooth");
     CUSTOM_ACTION_ENUM_TEXT[CUSTOM_ACTION_TOGGLE_LAYERS] = _("Toggle Layers");
 
         = _("Reset Bluetooth");
     CUSTOM_ACTION_ENUM_TEXT[CUSTOM_ACTION_TOGGLE_LAYERS] = _("Toggle Layers");
 
-    DEG_FORMAT_ENUM_TEXT[DDPDDDDD] = "-dd.ddddd°";
-    DEG_FORMAT_ENUM_TEXT[DD_MMPMMM] = "-dd°mm.mmm'";
-    DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS] = "-dd°mm'ss.s\"";
-    DEG_FORMAT_ENUM_TEXT[DDPDDDDD_NSEW] = "dd.ddddd° S";
-    DEG_FORMAT_ENUM_TEXT[DD_MMPMMM_NSEW] = "dd°mm.mmm' S";
-    DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS_NSEW] = "dd°mm'ss.s\" S";
-    DEG_FORMAT_ENUM_TEXT[NSEW_DDPDDDDD] = "S dd.ddddd°";
-    DEG_FORMAT_ENUM_TEXT[NSEW_DD_MMPMMM] = "S dd° mm.mmm'";
-    DEG_FORMAT_ENUM_TEXT[NSEW_DD_MM_SSPS] = "S dd° mm' ss.s\"";
-
+    DEG_FORMAT_ENUM_TEXT[DDPDDDDD].name = "-dd.ddddd°";
+    DEG_FORMAT_ENUM_TEXT[DDPDDDDD].short_field_1 = "Lat";
+    DEG_FORMAT_ENUM_TEXT[DDPDDDDD].long_field_1 = "Latitude";
+    DEG_FORMAT_ENUM_TEXT[DDPDDDDD].short_field_2 = "Lon";
+    DEG_FORMAT_ENUM_TEXT[DDPDDDDD].long_field_2 = "Longitude";
+    DEG_FORMAT_ENUM_TEXT[DDPDDDDD].field_2_in_use = TRUE;
+    
+    DEG_FORMAT_ENUM_TEXT[DD_MMPMMM].name = "-dd°mm.mmm'";
+    DEG_FORMAT_ENUM_TEXT[DD_MMPMMM].short_field_1 = "Lat";
+    DEG_FORMAT_ENUM_TEXT[DD_MMPMMM].long_field_1 = "Latitude";
+    DEG_FORMAT_ENUM_TEXT[DD_MMPMMM].short_field_2 = "Lon";
+    DEG_FORMAT_ENUM_TEXT[DD_MMPMMM].long_field_2 = "Longitude";
+    DEG_FORMAT_ENUM_TEXT[DD_MMPMMM].field_2_in_use = TRUE;
+    
+    DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS].name = "-dd°mm'ss.s\"";
+    DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS].short_field_1 = "Lat";
+    DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS].long_field_1 = "Latitude";
+    DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS].short_field_2 = "Lon";
+    DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS].long_field_2 = "Longitude";
+    DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS].field_2_in_use = TRUE;
+    
+    DEG_FORMAT_ENUM_TEXT[DDPDDDDD_NSEW].name = "dd.ddddd° S";
+    DEG_FORMAT_ENUM_TEXT[DDPDDDDD_NSEW].short_field_1 = "Lat";
+    DEG_FORMAT_ENUM_TEXT[DDPDDDDD_NSEW].long_field_1 = "Latitude";
+    DEG_FORMAT_ENUM_TEXT[DDPDDDDD_NSEW].short_field_2 = "Lon";
+    DEG_FORMAT_ENUM_TEXT[DDPDDDDD_NSEW].long_field_2 = "Longitude";
+    DEG_FORMAT_ENUM_TEXT[DDPDDDDD_NSEW].field_2_in_use = TRUE;
+    
+    DEG_FORMAT_ENUM_TEXT[DD_MMPMMM_NSEW].name = "dd°mm.mmm' S";
+    DEG_FORMAT_ENUM_TEXT[DD_MMPMMM_NSEW].short_field_1 = "Lat";
+    DEG_FORMAT_ENUM_TEXT[DD_MMPMMM_NSEW].long_field_1 = "Latitude";
+    DEG_FORMAT_ENUM_TEXT[DD_MMPMMM_NSEW].short_field_2 = "Lon";
+    DEG_FORMAT_ENUM_TEXT[DD_MMPMMM_NSEW].long_field_2 = "Longitude";
+    DEG_FORMAT_ENUM_TEXT[DD_MMPMMM_NSEW].field_2_in_use = TRUE;
+    
+    DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS_NSEW].name = "dd°mm'ss.s\" S";
+    DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS_NSEW].short_field_1 = "Lat";
+    DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS_NSEW].long_field_1 = "Latitude";
+    DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS_NSEW].short_field_2 = "Lon";
+    DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS_NSEW].long_field_2 = "Longitude";
+    DEG_FORMAT_ENUM_TEXT[DD_MM_SSPS_NSEW].field_2_in_use = TRUE;
+    
+    DEG_FORMAT_ENUM_TEXT[NSEW_DDPDDDDD].name = "S dd.ddddd°";
+    DEG_FORMAT_ENUM_TEXT[NSEW_DDPDDDDD].short_field_1 = "Lat";
+    DEG_FORMAT_ENUM_TEXT[NSEW_DDPDDDDD].long_field_1 = "Latitude";
+    DEG_FORMAT_ENUM_TEXT[NSEW_DDPDDDDD].short_field_2 = "Lon";
+    DEG_FORMAT_ENUM_TEXT[NSEW_DDPDDDDD].long_field_2 = "Longitude";
+    DEG_FORMAT_ENUM_TEXT[NSEW_DDPDDDDD].field_2_in_use = TRUE;
+    
+    DEG_FORMAT_ENUM_TEXT[NSEW_DD_MMPMMM].name = "S dd° mm.mmm'";
+    DEG_FORMAT_ENUM_TEXT[NSEW_DD_MMPMMM].short_field_1 = "Lat";
+    DEG_FORMAT_ENUM_TEXT[NSEW_DD_MMPMMM].long_field_1 = "Latitude";
+    DEG_FORMAT_ENUM_TEXT[NSEW_DD_MMPMMM].short_field_2 = "Lon";
+    DEG_FORMAT_ENUM_TEXT[NSEW_DD_MMPMMM].long_field_2 = "Longitude";
+    DEG_FORMAT_ENUM_TEXT[NSEW_DD_MMPMMM].field_2_in_use = TRUE;
+    
+    DEG_FORMAT_ENUM_TEXT[NSEW_DD_MM_SSPS].name = "S dd° mm' ss.s\"";
+    DEG_FORMAT_ENUM_TEXT[NSEW_DD_MM_SSPS].short_field_1 = "Lat";
+    DEG_FORMAT_ENUM_TEXT[NSEW_DD_MM_SSPS].long_field_1 = "Latitude";
+    DEG_FORMAT_ENUM_TEXT[NSEW_DD_MM_SSPS].short_field_2 = "Lon";
+    DEG_FORMAT_ENUM_TEXT[NSEW_DD_MM_SSPS].long_field_2 = "Longitude";
+    DEG_FORMAT_ENUM_TEXT[NSEW_DD_MM_SSPS].field_2_in_use = TRUE;
+
+    // Used by Radio Amateurs
+    DEG_FORMAT_ENUM_TEXT[IARU_LOC].name = "IARU Locator";
+    DEG_FORMAT_ENUM_TEXT[IARU_LOC].short_field_1 = "Locator";
+    DEG_FORMAT_ENUM_TEXT[IARU_LOC].long_field_1 = "Lacator";
+    DEG_FORMAT_ENUM_TEXT[IARU_LOC].short_field_2 = "";
+    DEG_FORMAT_ENUM_TEXT[IARU_LOC].long_field_2 = "";
+    DEG_FORMAT_ENUM_TEXT[IARU_LOC].field_2_in_use = FALSE;
+
+    
+    DEG_FORMAT_ENUM_TEXT[UK_OSGB].name = "OSGB X,Y Grid";
+    DEG_FORMAT_ENUM_TEXT[UK_OSGB].short_field_1 = "X";
+    DEG_FORMAT_ENUM_TEXT[UK_OSGB].long_field_1 = "OS X";
+    DEG_FORMAT_ENUM_TEXT[UK_OSGB].short_field_2 = "Y";
+    DEG_FORMAT_ENUM_TEXT[UK_OSGB].long_field_2 = "OS Y";
+    DEG_FORMAT_ENUM_TEXT[UK_OSGB].field_2_in_use = TRUE;
+    
+    DEG_FORMAT_ENUM_TEXT[UK_NGR].name = "OSGB Landranger Grid";
+    DEG_FORMAT_ENUM_TEXT[UK_NGR].short_field_1 = "GR";
+    DEG_FORMAT_ENUM_TEXT[UK_NGR].long_field_1 = "OS Grid";
+    DEG_FORMAT_ENUM_TEXT[UK_NGR].short_field_2 = "";
+    DEG_FORMAT_ENUM_TEXT[UK_NGR].long_field_2 = "";
+    DEG_FORMAT_ENUM_TEXT[UK_NGR].field_2_in_use = FALSE;
+    
     SPEED_LOCATION_ENUM_TEXT[SPEED_LOCATION_TOP_LEFT] = _("Top-Left");
     SPEED_LOCATION_ENUM_TEXT[SPEED_LOCATION_TOP_RIGHT] = _("Top-Right");
     SPEED_LOCATION_ENUM_TEXT[SPEED_LOCATION_BOTTOM_RIGHT] = _("Bottom-Right");
     SPEED_LOCATION_ENUM_TEXT[SPEED_LOCATION_TOP_LEFT] = _("Top-Left");
     SPEED_LOCATION_ENUM_TEXT[SPEED_LOCATION_TOP_RIGHT] = _("Top-Right");
     SPEED_LOCATION_ENUM_TEXT[SPEED_LOCATION_BOTTOM_RIGHT] = _("Bottom-Right");
@@ -513,7 +656,12 @@ maemo_mapper_init(gint argc, gchar **argv)
     poi_db_connect();
     display_init();
     dbus_ifc_init();
     poi_db_connect();
     display_init();
     dbus_ifc_init();
-
+    
+#ifdef INCLUDE_APRS
+    aprs_init();
+    timer_init();
+#endif //INCLUDE_APRS
+    
     /* If present, attempt to load the file specified on the command line. */
     if(argc > 1)
     {
     /* If present, attempt to load the file specified on the command line. */
     if(argc > 1)
     {
index 2ac2b433e6459f4261c442e573e640bf1f8e336d..d3044586954432e455348913440d1c9acccbda9f 100644 (file)
@@ -3389,9 +3389,16 @@ mapman_dialog()
     static GtkWidget *lbl_center_lat = NULL;
     static GtkWidget *lbl_center_lon = NULL;
     static MapmanInfo mapman_info;
     static GtkWidget *lbl_center_lat = NULL;
     static GtkWidget *lbl_center_lon = NULL;
     static MapmanInfo mapman_info;
+    static gint last_deg_format = 0;
+    
     gchar buffer[80];
     gdouble lat, lon;
     gint z;
     gchar buffer[80];
     gdouble lat, lon;
     gint z;
+    gint prev_degformat = _degformat;
+    gint fallback_deg_format = _degformat;
+    gdouble top_left_lat, top_left_lon, bottom_right_lat, bottom_right_lon;
+
+    
     printf("%s()\n", __PRETTY_FUNCTION__);
 
     if(!_curr_repo->db)
     printf("%s()\n", __PRETTY_FUNCTION__);
 
     if(!_curr_repo->db)
@@ -3402,6 +3409,52 @@ mapman_dialog()
         return TRUE;
     }
 
         return TRUE;
     }
 
+    // - If the coord system has changed then we need to update certain values
+    
+    /* Initialize to the bounds of the screen. */
+    unit2latlon(
+            _center.unitx - pixel2unit(MAX(_view_width_pixels,
+                    _view_height_pixels) / 2),
+            _center.unity - pixel2unit(MAX(_view_width_pixels,
+                    _view_height_pixels) / 2), top_left_lat, top_left_lon);
+    
+    BOUND(top_left_lat, -90.f, 90.f);
+    BOUND(top_left_lon, -180.f, 180.f);
+
+        
+    unit2latlon(
+            _center.unitx + pixel2unit(MAX(_view_width_pixels,
+                    _view_height_pixels) / 2),
+            _center.unity + pixel2unit(MAX(_view_width_pixels,
+                    _view_height_pixels) / 2), bottom_right_lat, bottom_right_lon);
+    BOUND(bottom_right_lat, -90.f, 90.f);
+    BOUND(bottom_right_lon, -180.f, 180.f);
+    
+    
+
+    
+    if(!coord_system_check_lat_lon (top_left_lat, top_left_lon, &fallback_deg_format))
+    {
+       _degformat = fallback_deg_format;
+    }
+    else
+    {
+       // top left is valid, also check bottom right
+        if(!coord_system_check_lat_lon (bottom_right_lat, bottom_right_lon, &fallback_deg_format))
+        {
+               _degformat = fallback_deg_format;
+        }
+    }
+    
+    
+    if(_degformat != last_deg_format)
+    {
+       last_deg_format = _degformat;
+       
+               if(dialog != NULL) gtk_widget_destroy(dialog);
+       dialog = NULL;
+    }
+
     if(dialog == NULL)
     {
         mapman_info.dialog = dialog = gtk_dialog_new_with_buttons(
     if(dialog == NULL)
     {
         mapman_info.dialog = dialog = gtk_dialog_new_with_buttons(
@@ -3520,14 +3573,18 @@ mapman_dialog()
 
         /* Label Columns. */
         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
 
         /* Label Columns. */
         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
-                label = gtk_label_new(_("Latitude")),
+                       label = gtk_label_new(DEG_FORMAT_ENUM_TEXT[_degformat].long_field_1),
                 1, 2, 0, 1, GTK_FILL, 0, 4, 0);
                 1, 2, 0, 1, GTK_FILL, 0, 4, 0);
-        gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
-        gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
-                label = gtk_label_new(_("Longitude")),
-                2, 3, 0, 1, GTK_FILL, 0, 4, 0);
-        gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
-
+        gtk_misc_set_alignment(GTK_MISC(label), 0.5f, 0.5f);
+        
+        if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+        {
+               gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
+                               label = gtk_label_new(DEG_FORMAT_ENUM_TEXT[_degformat].long_field_2),
+                       2, 3, 0, 1, GTK_FILL, 0, 4, 0);
+               gtk_misc_set_alignment(GTK_MISC(label), 0.5f, 0.5f);
+        }
+        
         /* GPS. */
         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
                 label = gtk_label_new(_("GPS Location")),
         /* GPS. */
         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
                 label = gtk_label_new(_("GPS Location")),
@@ -3538,12 +3595,16 @@ mapman_dialog()
                 1, 2, 1, 2, GTK_FILL, 0, 4, 0);
         gtk_label_set_selectable(GTK_LABEL(lbl_gps_lat), TRUE);
         gtk_misc_set_alignment(GTK_MISC(lbl_gps_lat), 1.f, 0.5f);
                 1, 2, 1, 2, GTK_FILL, 0, 4, 0);
         gtk_label_set_selectable(GTK_LABEL(lbl_gps_lat), TRUE);
         gtk_misc_set_alignment(GTK_MISC(lbl_gps_lat), 1.f, 0.5f);
-        gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
-                lbl_gps_lon = gtk_label_new(""),
-                2, 3, 1, 2, GTK_FILL, 0, 4, 0);
-        gtk_label_set_selectable(GTK_LABEL(lbl_gps_lon), TRUE);
-        gtk_misc_set_alignment(GTK_MISC(lbl_gps_lon), 1.f, 0.5f);
-
+        
+        if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+        {
+               gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
+                       lbl_gps_lon = gtk_label_new(""),
+                       2, 3, 1, 2, GTK_FILL, 0, 4, 0);
+               gtk_label_set_selectable(GTK_LABEL(lbl_gps_lon), TRUE);
+               gtk_misc_set_alignment(GTK_MISC(lbl_gps_lon), 1.f, 0.5f);
+        }
+        
         /* Center. */
         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
                 label = gtk_label_new(_("View Center")),
         /* Center. */
         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
                 label = gtk_label_new(_("View Center")),
@@ -3554,11 +3615,16 @@ mapman_dialog()
                 1, 2, 2, 3, GTK_FILL, 0, 4, 0);
         gtk_label_set_selectable(GTK_LABEL(lbl_center_lat), TRUE);
         gtk_misc_set_alignment(GTK_MISC(lbl_center_lat), 1.f, 0.5f);
                 1, 2, 2, 3, GTK_FILL, 0, 4, 0);
         gtk_label_set_selectable(GTK_LABEL(lbl_center_lat), TRUE);
         gtk_misc_set_alignment(GTK_MISC(lbl_center_lat), 1.f, 0.5f);
-        gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
-                lbl_center_lon = gtk_label_new(""),
-                2, 3, 2, 3, GTK_FILL, 0, 4, 0);
-        gtk_label_set_selectable(GTK_LABEL(lbl_center_lon), TRUE);
-        gtk_misc_set_alignment(GTK_MISC(lbl_center_lon), 1.f, 0.5f);
+        
+    
+        if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+        {
+               gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
+                       lbl_center_lon = gtk_label_new(""),
+                       2, 3, 2, 3, GTK_FILL, 0, 4, 0);
+               gtk_label_set_selectable(GTK_LABEL(lbl_center_lon), TRUE);
+               gtk_misc_set_alignment(GTK_MISC(lbl_center_lon), 1.f, 0.5f);
+        }
 
         /* default values for Top Left and Bottom Right are defined by the
          * rectangle of the current and the previous Center */
 
         /* default values for Top Left and Bottom Right are defined by the
          * rectangle of the current and the previous Center */
@@ -3586,25 +3652,30 @@ mapman_dialog()
                 FALSE, NULL);
 #endif
 #endif
                 FALSE, NULL);
 #endif
 #endif
-        gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
-                mapman_info.txt_topleft_lon = gtk_entry_new(),
-                2, 3, 3, 4, GTK_FILL, 0, 4, 0);
-        gtk_entry_set_width_chars(GTK_ENTRY(mapman_info.txt_topleft_lon), 12);
-        gtk_entry_set_alignment(GTK_ENTRY(mapman_info.txt_topleft_lon), 1.f);
+        
+        if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+        {
+               gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
+                       mapman_info.txt_topleft_lon = gtk_entry_new(),
+                       2, 3, 3, 4, GTK_FILL, 0, 4, 0);
+               gtk_entry_set_width_chars(GTK_ENTRY(mapman_info.txt_topleft_lon), 12);
+               gtk_entry_set_alignment(GTK_ENTRY(mapman_info.txt_topleft_lon), 1.f);
 #ifdef MAEMO_CHANGES
 #ifdef MAEMO_CHANGES
-        g_object_set(G_OBJECT(mapman_info.txt_topleft_lon),
+                   g_object_set(G_OBJECT(mapman_info.txt_topleft_lon),
 #ifndef LEGACY
 #ifndef LEGACY
-                "hildon-input-mode",
-                HILDON_GTK_INPUT_MODE_FULL, NULL);
+                       "hildon-input-mode",
+                       HILDON_GTK_INPUT_MODE_FULL, NULL);
 #else
 #else
-                HILDON_INPUT_MODE_HINT,
-                HILDON_INPUT_MODE_HINT_ALPHANUMERICSPECIAL, NULL);
-        g_object_set(G_OBJECT(mapman_info.txt_topleft_lon),
-                HILDON_AUTOCAP,
-                FALSE, NULL);
+                       HILDON_INPUT_MODE_HINT,
+                       HILDON_INPUT_MODE_HINT_ALPHANUMERICSPECIAL, NULL);
+               g_object_set(G_OBJECT(mapman_info.txt_topleft_lon),
+                       HILDON_AUTOCAP,
+                       FALSE, NULL);
 #endif
 #endif
 
 #endif
 #endif
 
+        }
+        
         /* Bottom Right. */
         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
                 label = gtk_label_new(_("Bottom-Right")),
         /* Bottom Right. */
         gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
                 label = gtk_label_new(_("Bottom-Right")),
@@ -3628,25 +3699,30 @@ mapman_dialog()
                 FALSE, NULL);
 #endif
 #endif
                 FALSE, NULL);
 #endif
 #endif
-        gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
-                mapman_info.txt_botright_lon = gtk_entry_new(),
-                2, 3, 4, 5, GTK_FILL, 0, 4, 0);
-        gtk_entry_set_width_chars(GTK_ENTRY(mapman_info.txt_botright_lat), 12);
-        gtk_entry_set_alignment(GTK_ENTRY(mapman_info.txt_botright_lon), 1.f);
+        
+        if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+        {
+               gtk_table_attach(GTK_TABLE(mapman_info.tbl_area),
+                       mapman_info.txt_botright_lon = gtk_entry_new(),
+                       2, 3, 4, 5, GTK_FILL, 0, 4, 0);
+               gtk_entry_set_width_chars(GTK_ENTRY(mapman_info.txt_botright_lon), 12);
+               gtk_entry_set_alignment(GTK_ENTRY(mapman_info.txt_botright_lon), 1.f);
 #ifdef MAEMO_CHANGES
 #ifdef MAEMO_CHANGES
-        g_object_set(G_OBJECT(mapman_info.txt_botright_lon),
+               g_object_set(G_OBJECT(mapman_info.txt_botright_lon),
 #ifndef LEGACY
 #ifndef LEGACY
-                "hildon-input-mode",
-                HILDON_GTK_INPUT_MODE_FULL, NULL);
+                       "hildon-input-mode",
+                       HILDON_GTK_INPUT_MODE_FULL, NULL);
 #else
 #else
-                HILDON_INPUT_MODE_HINT,
-                HILDON_INPUT_MODE_HINT_ALPHANUMERICSPECIAL, NULL);
-        g_object_set(G_OBJECT(mapman_info.txt_botright_lon),
-                HILDON_AUTOCAP,
-                FALSE, NULL);
+                       HILDON_INPUT_MODE_HINT,
+                       HILDON_INPUT_MODE_HINT_ALPHANUMERICSPECIAL, NULL);
+               g_object_set(G_OBJECT(mapman_info.txt_botright_lon),
+                       HILDON_AUTOCAP,
+                       FALSE, NULL);
 #endif
 #endif
 
 #endif
 #endif
 
+        }
+        
         /* Default action is to download by area. */
         gtk_toggle_button_set_active(
                 GTK_TOGGLE_BUTTON(mapman_info.rad_by_area), TRUE);
         /* Default action is to download by area. */
         gtk_toggle_button_set_active(
                 GTK_TOGGLE_BUTTON(mapman_info.rad_by_area), TRUE);
@@ -3667,41 +3743,38 @@ mapman_dialog()
     gtk_widget_set_sensitive(mapman_info.rad_by_route,
             _route.head != _route.tail);
 
     gtk_widget_set_sensitive(mapman_info.rad_by_route,
             _route.head != _route.tail);
 
-    lat_format(_gps.lat, buffer);
-    gtk_label_set_text(GTK_LABEL(lbl_gps_lat), buffer);
-    lon_format(_gps.lon, buffer);
-    gtk_label_set_text(GTK_LABEL(lbl_gps_lon), buffer);
-
+    
+    gchar buffer1[15];
+    gchar buffer2[15];
+    format_lat_lon(_gps.lat, _gps.lon, buffer1, buffer2);
+    //lat_format(_gps.lat, buffer);
+    gtk_label_set_text(GTK_LABEL(lbl_gps_lat), buffer1);
+    //lon_format(_gps.lon, buffer);
+    if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+       gtk_label_set_text(GTK_LABEL(lbl_gps_lon), buffer2);
+    
     unit2latlon(_center.unitx, _center.unity, lat, lon);
     unit2latlon(_center.unitx, _center.unity, lat, lon);
-    lat_format(lat, buffer);
-    gtk_label_set_text(GTK_LABEL(lbl_center_lat), buffer);
-    lon_format(lon, buffer);
-    gtk_label_set_text(GTK_LABEL(lbl_center_lon), buffer);
-
-    /* Initialize to the bounds of the screen. */
-    unit2latlon(
-            _center.unitx - pixel2unit(MAX(_view_width_pixels,
-                    _view_height_pixels) / 2),
-            _center.unity - pixel2unit(MAX(_view_width_pixels,
-                    _view_height_pixels) / 2), lat, lon);
-    BOUND(lat, -90.f, 90.f);
-    BOUND(lon, -180.f, 180.f);
-    lat_format(lat, buffer);
-    gtk_entry_set_text(GTK_ENTRY(mapman_info.txt_topleft_lat), buffer);
-    lon_format(lon, buffer);
-    gtk_entry_set_text(GTK_ENTRY(mapman_info.txt_topleft_lon), buffer);
-
-    unit2latlon(
-            _center.unitx + pixel2unit(MAX(_view_width_pixels,
-                    _view_height_pixels) / 2),
-            _center.unity + pixel2unit(MAX(_view_width_pixels,
-                    _view_height_pixels) / 2), lat, lon);
-    BOUND(lat, -90.f, 90.f);
-    BOUND(lon, -180.f, 180.f);
-    lat_format(lat, buffer);
-    gtk_entry_set_text(GTK_ENTRY(mapman_info.txt_botright_lat), buffer);
-    lon_format(lon, buffer);
-    gtk_entry_set_text(GTK_ENTRY(mapman_info.txt_botright_lon), buffer);
+    
+    format_lat_lon(lat, lon, buffer1, buffer2);
+    //lat_format(lat, buffer);
+    gtk_label_set_text(GTK_LABEL(lbl_center_lat), buffer1);
+    //lon_format(lon, buffer);
+    if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+       gtk_label_set_text(GTK_LABEL(lbl_center_lon), buffer2);
+
+    format_lat_lon(top_left_lat, top_left_lon, buffer1, buffer2);
+    
+    gtk_entry_set_text(GTK_ENTRY(mapman_info.txt_topleft_lat), buffer1);
+    
+    if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+       gtk_entry_set_text(GTK_ENTRY(mapman_info.txt_topleft_lon), buffer2);
+    
+    format_lat_lon(bottom_right_lat, bottom_right_lon, buffer1, buffer2);
+    //lat_format(lat, buffer);
+    gtk_entry_set_text(GTK_ENTRY(mapman_info.txt_botright_lat), buffer1);
+    //lon_format(lon, buffer);
+    if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+       gtk_entry_set_text(GTK_ENTRY(mapman_info.txt_botright_lon), buffer2);
 
     /* Initialize zoom levels. */
     {
 
     /* Initialize zoom levels. */
     {
@@ -3755,37 +3828,29 @@ mapman_dialog()
         }
         else
         {
         }
         else
         {
-            const gchar *text;
-            gchar *error_check;
+            const gchar *text_lon, *text_lat;
+            //gchar *error_check;
             gdouble start_lat, start_lon, end_lat, end_lon;
 
             gdouble start_lat, start_lon, end_lat, end_lon;
 
-            text = gtk_entry_get_text(GTK_ENTRY(mapman_info.txt_topleft_lat));
-            start_lat = strdmstod(text, &error_check);
-            if(text == error_check || start_lat < -90. || start_lat > 90.) {
-                popup_error(dialog, _("Invalid Top-Left Latitude"));
-                continue;
-            }
-
-            text = gtk_entry_get_text(GTK_ENTRY(mapman_info.txt_topleft_lon));
-            start_lon = strdmstod(text, &error_check);
-            if(text == error_check || start_lon < -180. || start_lon>180.) {
-                popup_error(dialog, _("Invalid Top-Left Longitude"));
-                continue;
+            text_lat = gtk_entry_get_text(GTK_ENTRY(mapman_info.txt_topleft_lat));
+            text_lon = gtk_entry_get_text(GTK_ENTRY(mapman_info.txt_topleft_lon));
+            
+            if(!parse_coords(text_lat, text_lon, &start_lat, &start_lon))
+            {
+               popup_error(dialog, _("Invalid Top-Left coordinate specified"));
+               continue;
             }
             }
+            
+            text_lat = gtk_entry_get_text(GTK_ENTRY(mapman_info.txt_botright_lat));
+            text_lon = gtk_entry_get_text(GTK_ENTRY(mapman_info.txt_botright_lon));
 
 
-            text = gtk_entry_get_text(GTK_ENTRY(mapman_info.txt_botright_lat));
-            end_lat = strdmstod(text, &error_check);
-            if(text == error_check || end_lat < -90. || end_lat > 90.) {
-                popup_error(dialog, _("Invalid Bottom-Right Latitude"));
-                continue;
+            if(!parse_coords(text_lat, text_lon, &end_lat, &end_lon))
+            {
+               popup_error(dialog, _("Invalid Bottom-Right coordinate specified"));
+               continue;
             }
 
             }
 
-            text = gtk_entry_get_text(GTK_ENTRY(mapman_info.txt_botright_lon));
-            end_lon = strdmstod(text, &error_check);
-            if(text == error_check || end_lon < -180. || end_lon > 180.) {
-                popup_error(dialog,_("Invalid Bottom-Right Longitude"));
-                continue;
-            }
+  
 
             if(mapman_by_area(start_lat, start_lon, end_lat, end_lon,
                         &mapman_info, update_type, download_batch_id))
 
             if(mapman_by_area(start_lat, start_lon, end_lat, end_lon,
                         &mapman_info, update_type, download_batch_id))
@@ -3794,6 +3859,8 @@ mapman_dialog()
     }
 
     gtk_widget_hide(dialog);
     }
 
     gtk_widget_hide(dialog);
+    
+    _degformat = prev_degformat;
 
     vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
     return TRUE;
 
     vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
     return TRUE;
index 0d9171c3178d166420cef4ee39ff7bfde46d6e86..f9e0600008ea4c55effc57eca0c52c50478dc2bb 100644 (file)
@@ -47,6 +47,9 @@
 
 #include "display.h"
 #include "gps.h"
 
 #include "display.h"
 #include "gps.h"
+#include "aprs.h"
+#include "aprs_kiss.h"
+#include "aprs_display.h"
 #include "gdk-pixbuf-rotate.h"
 #include "gpx.h"
 #include "maps.h"
 #include "gdk-pixbuf-rotate.h"
 #include "gpx.h"
 #include "maps.h"
@@ -295,8 +298,10 @@ menu_cb_track_insert_mark(GtkMenuItem *item)
     }
 
     unit2latlon(_pos.unitx, _pos.unity, lat, lon);
     }
 
     unit2latlon(_pos.unitx, _pos.unity, lat, lon);
-    lat_format(lat, tmp1);
-    lon_format(lon, tmp2);
+    
+    format_lat_lon(lat, lon, tmp1, tmp2);
+    //lat_format(lat, tmp1);
+    //lon_format(lon, tmp2);
     p_latlon = g_strdup_printf("%s, %s", tmp1, tmp2);
     gtk_label_set_text(GTK_LABEL(lbl_latlon), p_latlon);
     g_free(p_latlon);
     p_latlon = g_strdup_printf("%s, %s", tmp1, tmp2);
     gtk_label_set_text(GTK_LABEL(lbl_latlon), p_latlon);
     g_free(p_latlon);
@@ -853,13 +858,15 @@ menu_cb_view_goto_latlon(GtkMenuItem *item)
 
     /* Initialize with the current center position. */
     {
 
     /* Initialize with the current center position. */
     {
-        gchar buffer[32];
+        gchar buffer1[32];
+        gchar buffer2[32];
         gdouble lat, lon;
         unit2latlon(_center.unitx, _center.unity, lat, lon);
         gdouble lat, lon;
         unit2latlon(_center.unitx, _center.unity, lat, lon);
-        lat_format(lat, buffer);
-        gtk_entry_set_text(GTK_ENTRY(txt_lat), buffer);
-        lon_format(lon, buffer);
-        gtk_entry_set_text(GTK_ENTRY(txt_lon), buffer);
+        //lat_format(lat, buffer1);
+        //lon_format(lon, buffer2);
+        format_lat_lon(lat, lon, buffer1, buffer2);
+        gtk_entry_set_text(GTK_ENTRY(txt_lat), buffer1);
+        gtk_entry_set_text(GTK_ENTRY(txt_lon), buffer2);
     }
 
     gtk_widget_show_all(dialog);
     }
 
     gtk_widget_show_all(dialog);
@@ -1242,6 +1249,125 @@ menu_cb_view_fullscreen(GtkMenuItem *item)
     return TRUE;
 }
 
     return TRUE;
 }
 
+/****************************************************************************
+ * BELOW: APRS MENU *********************************************************
+ ****************************************************************************/
+#ifdef INCLUDE_APRS
+
+static gboolean menu_cb_aprs_settings(GtkMenuItem *item)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    gboolean aprs_inet_config_changed = FALSE;
+    gboolean aprs_tty_config_changed = FALSE;
+
+    aprs_settings_dialog(&aprs_inet_config_changed, &aprs_tty_config_changed);
+    
+    if(aprs_inet_config_changed)
+    {
+        gtk_widget_set_sensitive(GTK_WIDGET(_menu_enable_aprs_inet_item), _aprs_enable);
+        gtk_widget_set_sensitive(GTK_WIDGET(_menu_enable_aprs_tty_item), _aprs_enable);
+        gtk_widget_set_sensitive(GTK_WIDGET(_menu_list_aprs_stations_item), _aprs_enable);
+        gtk_widget_set_sensitive(GTK_WIDGET(_menu_list_aprs_messages_item), _aprs_enable);
+
+       
+       aprs_server_disconnect();
+       
+       if(_aprs_enable && _aprs_inet_enable)
+       {
+               aprs_server_connect();
+       }
+    }
+    
+    vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean menu_cb_enable_tty_aprs(GtkMenuItem *item)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(_aprs_enable)
+    {
+
+           if((_aprs_tty_enable = gtk_check_menu_item_get_active(
+                       GTK_CHECK_MENU_ITEM(_menu_enable_aprs_tty_item))))
+               aprs_tty_connect();
+           else
+               aprs_tty_disconnect();
+    }
+    
+
+    vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+
+static gboolean
+menu_cb_enable_inet_aprs(GtkMenuItem *item)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(_aprs_enable)
+    {
+
+           if((_aprs_inet_enable = gtk_check_menu_item_get_active(
+                       GTK_CHECK_MENU_ITEM(_menu_enable_aprs_inet_item))))
+               aprs_server_connect();
+           else
+               aprs_server_disconnect();
+
+
+    }
+    else
+    {
+       // APRS not enabled
+    }
+    
+
+    vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+static gboolean menu_cb_list_aprs_messages(GtkMenuItem *item)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(_aprs_enable)
+    {
+       list_messages();
+    }
+    else
+    {
+       // APRS not enabled
+    }
+    
+
+    vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
+    return TRUE;       
+}
+
+static gboolean
+menu_cb_list_aprs_stations(GtkMenuItem *item)
+{
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    if(_aprs_enable)
+    {
+       list_stations();
+    }
+    else
+    {
+       // APRS not enabled
+    }
+    
+
+    vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
+    return TRUE;
+}
+
+#endif // INCLUDE_APRS
+
 /****************************************************************************
  * BELOW: GPS MENU **********************************************************
  ****************************************************************************/
 /****************************************************************************
  * BELOW: GPS MENU **********************************************************
  ****************************************************************************/
@@ -1757,6 +1883,47 @@ menu_init()
 
     gtk_menu_append(menu, gtk_separator_menu_item_new());
 
 
     gtk_menu_append(menu, gtk_separator_menu_item_new());
 
+#ifdef INCLUDE_APRS
+    /* The "APRS" submenu. */
+    gtk_menu_append(menu, menu_item
+            = gtk_menu_item_new_with_label(_("APRS")));
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item),
+            submenu = gtk_menu_new());
+    
+    gtk_menu_append(submenu, _menu_enable_aprs_inet_item
+            = gtk_check_menu_item_new_with_label(_("Connect to APRS Server")));
+    gtk_check_menu_item_set_active(
+            GTK_CHECK_MENU_ITEM(_menu_enable_aprs_inet_item), _aprs_inet_enable);
+    gtk_widget_set_sensitive(GTK_WIDGET(_menu_enable_aprs_inet_item), _aprs_enable);
+    
+    
+    gtk_menu_append(submenu, _menu_enable_aprs_tty_item
+                = gtk_check_menu_item_new_with_label(_("Connect to TNC")));
+        gtk_check_menu_item_set_active(
+                GTK_CHECK_MENU_ITEM(_menu_enable_aprs_tty_item), _aprs_tty_enable);
+        gtk_widget_set_sensitive(GTK_WIDGET(_menu_enable_aprs_tty_item), _aprs_enable);
+        
+    
+    gtk_menu_append(submenu, _menu_list_aprs_stations_item
+                = gtk_menu_item_new_with_label(_("List Stations...")));
+        
+    gtk_widget_set_sensitive(GTK_WIDGET(_menu_list_aprs_stations_item), _aprs_enable);
+
+
+    gtk_menu_append(submenu, _menu_list_aprs_messages_item
+                = gtk_menu_item_new_with_label(_("List Messages...")));
+        
+    gtk_widget_set_sensitive(GTK_WIDGET(_menu_list_aprs_messages_item), _aprs_enable);
+
+    
+    gtk_menu_append(submenu, _menu_aprs_settings_item
+                    = gtk_menu_item_new_with_label(_("APRS Settings...")));
+    
+    
+    gtk_menu_append(menu, gtk_separator_menu_item_new());
+
+#endif // INCLUDE_APRS
+
     /* The other menu items. */
     gtk_menu_append(menu, _menu_settings_item
         = gtk_menu_item_new_with_label(_("Settings...")));
     /* The other menu items. */
     gtk_menu_append(menu, _menu_settings_item
         = gtk_menu_item_new_with_label(_("Settings...")));
@@ -1905,6 +2072,24 @@ menu_init()
     g_signal_connect(G_OBJECT(_menu_gps_reset_item), "activate",
                       G_CALLBACK(menu_cb_gps_reset), NULL);
 
     g_signal_connect(G_OBJECT(_menu_gps_reset_item), "activate",
                       G_CALLBACK(menu_cb_gps_reset), NULL);
 
+#ifdef INCLUDE_APRS
+    /* Connect the "APRS" signals. */
+    g_signal_connect(G_OBJECT(_menu_enable_aprs_inet_item), "toggled",
+                      G_CALLBACK(menu_cb_enable_inet_aprs), NULL);
+
+    g_signal_connect(G_OBJECT(_menu_enable_aprs_tty_item), "toggled",
+                      G_CALLBACK(menu_cb_enable_tty_aprs), NULL);
+
+    g_signal_connect(G_OBJECT(_menu_list_aprs_stations_item), "activate",
+                          G_CALLBACK(menu_cb_list_aprs_stations), NULL);
+
+    g_signal_connect(G_OBJECT(_menu_list_aprs_messages_item), "activate",
+                              G_CALLBACK(menu_cb_list_aprs_messages), NULL);
+    
+    g_signal_connect(G_OBJECT(_menu_aprs_settings_item), "activate",
+                                  G_CALLBACK(menu_cb_aprs_settings), NULL);
+#endif // INCLUDE_APRS        
+    
     /* Connect the other menu item signals. */
     g_signal_connect(G_OBJECT(_menu_settings_item), "activate",
                       G_CALLBACK(menu_cb_settings), NULL);
     /* Connect the other menu item signals. */
     g_signal_connect(G_OBJECT(_menu_settings_item), "activate",
                       G_CALLBACK(menu_cb_settings), NULL);
index 70abfffae597baffafa6ca61738bbd668a0595e2..38f1f33483a1aa774ccdce2759e90979e6157c62 100644 (file)
--- a/src/poi.c
+++ b/src/poi.c
@@ -420,8 +420,10 @@ select_poi(gint unitx, gint unity, PoiInfo *poi, gboolean quick)
         gdouble lat, lon;
         lat = sqlite3_column_double(_stmt_select_poi, 0);
         lon = sqlite3_column_double(_stmt_select_poi, 1);
         gdouble lat, lon;
         lat = sqlite3_column_double(_stmt_select_poi, 0);
         lon = sqlite3_column_double(_stmt_select_poi, 1);
-        lat_format(lat, tmp1);
-        lon_format(lon, tmp2);
+        
+        format_lat_lon(lat, lon, tmp1, tmp2);
+        //lat_format(lat, tmp1);
+        //lon_format(lon, tmp2);
         gtk_list_store_append(store, &iter);
         gtk_list_store_set(store, &iter,
                 POI_POIID, sqlite3_column_int(_stmt_select_poi, 2),
         gtk_list_store_append(store, &iter);
         gtk_list_store_set(store, &iter,
                 POI_POIID, sqlite3_column_int(_stmt_select_poi, 2),
@@ -1232,8 +1234,9 @@ poi_add_dialog(GtkWidget *parent, gint unitx, gint unity)
     {
         gchar tmp1[LL_FMT_LEN], tmp2[LL_FMT_LEN];
 
     {
         gchar tmp1[LL_FMT_LEN], tmp2[LL_FMT_LEN];
 
-        lat_format(poi.lat, tmp1);
-        lon_format(poi.lon, tmp2);
+        format_lat_lon(poi.lat, poi.lon, tmp1, tmp2);
+        //lat_format(poi.lat, tmp1);
+        //lon_format(poi.lon, tmp2);
 
         gtk_entry_set_text(GTK_ENTRY(txt_lat), tmp1);
         gtk_entry_set_text(GTK_ENTRY(txt_lon), tmp2);
 
         gtk_entry_set_text(GTK_ENTRY(txt_lat), tmp1);
         gtk_entry_set_text(GTK_ENTRY(txt_lon), tmp2);
@@ -1386,7 +1389,18 @@ poi_view_dialog(GtkWidget *parent, PoiInfo *poi)
     static GtkTextIter begin, end;
     static DeletePOI dpoi = {NULL, NULL, 0};
     static PoiCategoryEditInfo pcedit;
     static GtkTextIter begin, end;
     static DeletePOI dpoi = {NULL, NULL, 0};
     static PoiCategoryEditInfo pcedit;
+    static int last_deg_format = 0;
+    
     printf("%s()\n", __PRETTY_FUNCTION__);
     printf("%s()\n", __PRETTY_FUNCTION__);
+    
+
+    if(_degformat != last_deg_format)
+    {
+       last_deg_format = _degformat;
+       
+               if(dialog != NULL) gtk_widget_destroy(dialog);
+       dialog = NULL;
+    }
 
     if(dialog == NULL)
     {
 
     if(dialog == NULL)
     {
@@ -1406,21 +1420,24 @@ poi_view_dialog(GtkWidget *parent, PoiInfo *poi)
                 table = gtk_table_new(6, 4, FALSE), TRUE, TRUE, 0);
 
         gtk_table_attach(GTK_TABLE(table),
                 table = gtk_table_new(6, 4, FALSE), TRUE, TRUE, 0);
 
         gtk_table_attach(GTK_TABLE(table),
-                label = gtk_label_new(_("Lat")),
+                label = gtk_label_new(DEG_FORMAT_ENUM_TEXT[_degformat].short_field_1),
                 0, 1, 0, 1, GTK_FILL, 0, 2, 0);
         gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
         gtk_table_attach(GTK_TABLE(table),
                 txt_lat = gtk_entry_new(),
                 1, 2, 0, 1, GTK_FILL, 0, 2, 0);
 
                 0, 1, 0, 1, GTK_FILL, 0, 2, 0);
         gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
         gtk_table_attach(GTK_TABLE(table),
                 txt_lat = gtk_entry_new(),
                 1, 2, 0, 1, GTK_FILL, 0, 2, 0);
 
-        gtk_table_attach(GTK_TABLE(table),
-                label = gtk_label_new(_("Lon")),
-                2, 3, 0, 1, GTK_FILL, 0, 2, 0);
-        gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
-        gtk_table_attach(GTK_TABLE(table),
-                txt_lon = gtk_entry_new(),
-                3, 4, 0, 1, GTK_FILL, 0, 2, 0);
-
+        if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+        {
+               gtk_table_attach(GTK_TABLE(table),
+                       label = gtk_label_new(DEG_FORMAT_ENUM_TEXT[_degformat].short_field_2),
+                       2, 3, 0, 1, GTK_FILL, 0, 2, 0);
+               gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+               gtk_table_attach(GTK_TABLE(table),
+                       txt_lon = gtk_entry_new(),
+                       3, 4, 0, 1, GTK_FILL, 0, 2, 0);
+        }
+        
         gtk_table_attach(GTK_TABLE(table),
                 label = gtk_label_new(_("Label")),
                 0, 1, 1, 2, GTK_FILL, 0, 2, 0);
         gtk_table_attach(GTK_TABLE(table),
                 label = gtk_label_new(_("Label")),
                 0, 1, 1, 2, GTK_FILL, 0, 2, 0);
@@ -1484,11 +1501,16 @@ poi_view_dialog(GtkWidget *parent, PoiInfo *poi)
     {
         gchar tmp1[LL_FMT_LEN], tmp2[LL_FMT_LEN];
 
     {
         gchar tmp1[LL_FMT_LEN], tmp2[LL_FMT_LEN];
 
-        lat_format(poi->lat, tmp1);
-        lon_format(poi->lon, tmp2);
+        format_lat_lon(poi->lat, poi->lon, tmp1, tmp2);
+        //lat_format(poi->lat, tmp1);
+        //lon_format(poi->lon, tmp2);
 
         gtk_entry_set_text(GTK_ENTRY(txt_lat), tmp1);
 
         gtk_entry_set_text(GTK_ENTRY(txt_lat), tmp1);
-        gtk_entry_set_text(GTK_ENTRY(txt_lon), tmp2);
+        
+        if(DEG_FORMAT_ENUM_TEXT[_degformat].field_2_in_use)
+               gtk_entry_set_text(GTK_ENTRY(txt_lon), tmp2);
+        else
+               gtk_entry_set_text(GTK_ENTRY(txt_lon), g_strdup(""));
     }
 
     /* label */
     }
 
     /* label */
@@ -1513,23 +1535,19 @@ poi_view_dialog(GtkWidget *parent, PoiInfo *poi)
 
     while(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog)))
     {
 
     while(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog)))
     {
-        const gchar *text;
+        const gchar *text, *text_lat, *text_lon;
         gchar *error_check;
 
         gchar *error_check;
 
-        text = gtk_entry_get_text(GTK_ENTRY(txt_lat));
-        poi->lat = strdmstod(text, &error_check);
-        if(text == error_check || poi->lat < -90. || poi->lat > 90.) {
-            popup_error(dialog, _("Invalid Latitude"));
-            continue;
-        }
-
-        text = gtk_entry_get_text(GTK_ENTRY(txt_lon));
-        poi->lon = strdmstod(text, &error_check);
-        if(text == error_check || poi->lon < -180. || poi->lon > 180.) {
-            popup_error(dialog, _("Invalid Longitude"));
-            continue;
+        text_lat = gtk_entry_get_text(GTK_ENTRY(txt_lat));
+        text_lon = gtk_entry_get_text(GTK_ENTRY(txt_lon));
+        
+        if(!parse_coords(text_lat, text_lon, &poi->lat, &poi->lon))
+        {
+               popup_error(dialog, _("Invalid coordinate specified"));
+               continue;
         }
 
         }
 
+        
         if(strlen(gtk_entry_get_text(GTK_ENTRY(txt_label))))
         {
             if(poi->label)
         if(strlen(gtk_entry_get_text(GTK_ENTRY(txt_label))))
         {
             if(poi->label)
@@ -2307,8 +2325,9 @@ poi_list_dialog(GtkWidget *parent, gint unitx, gint unity, GList *poi_list)
                 poi_info->lat, poi_info->lon,
                 poi_info->label, poi_info->desc);
 
                 poi_info->lat, poi_info->lon,
                 poi_info->label, poi_info->desc);
 
-        lat_format(poi_info->lat, tmp1);
-        lon_format(poi_info->lon, tmp2);
+        format_lat_lon(poi_info->lat, poi_info->lon, tmp1, tmp2);
+        //lat_format(poi_info->lat, tmp1);
+        //lon_format(poi_info->lon, tmp2);
 
         gtk_list_store_append(store, &iter);
         gtk_list_store_set(store, &iter,
 
         gtk_list_store_append(store, &iter);
         gtk_list_store_set(store, &iter,
@@ -3261,3 +3280,44 @@ poi_destroy()
 
     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
 }
 
     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
 }
+
+#ifdef INCLUDE_APRS
+extern AprsDataRow *n_first;               // pointer to first element in name sorted station list
+
+
+/////////////////////
+
+/**
+ * Render all the APRS data.
+ */
+void
+map_render_aprs()
+{
+
+    printf("%s()\n", __PRETTY_FUNCTION__);
+    int mm;
+
+    if(_poi_zoom > _zoom)
+    {
+
+        AprsDataRow *p_station = n_first;
+
+        while ( (p_station) != NULL) 
+        {
+            if( p_station->coord_lat != 0.0f 
+                || p_station->coord_lon != 0.0f  ) 
+            {
+                       plot_aprs_station( p_station, FALSE);
+            } // If valid data
+
+            (p_station) = (p_station)->n_next;  // Next element in list
+        } // End of while loop
+
+
+    } // check for zoom level
+
+    vprintf("%s(): return\n", __PRETTY_FUNCTION__);
+}
+
+#endif // INCLUDE_APRS
+
index ba46750127c82cc26023f020ea08bc0a91722308..a0b814602f62ff451daa2b1aab03f0b12f9c4b95 100644 (file)
 #define GCONF_KEY_POI_DL_URL GCONF_KEY_PREFIX"/poi_dl_url" 
 #define GCONF_KEY_DEG_FORMAT GCONF_KEY_PREFIX"/deg_format" 
 
 #define GCONF_KEY_POI_DL_URL GCONF_KEY_PREFIX"/poi_dl_url" 
 #define GCONF_KEY_DEG_FORMAT GCONF_KEY_PREFIX"/deg_format" 
 
+// APRS
+#ifdef INCLUDE_APRS
+#define GCONF_KEY_APRS_SERVER      GCONF_KEY_PREFIX"/aprs_server" 
+#define GCONF_KEY_APRS_SERVER_PORT GCONF_KEY_PREFIX"/aprs_server_port" 
+#define GCONF_KEY_APRS_SERVER_VAL  GCONF_KEY_PREFIX"/aprs_mycall_val" 
+#define GCONF_KEY_APRS_MYCALL      GCONF_KEY_PREFIX"/aprs_mycall"
+#define GCONF_KEY_APRS_INET_BEACON      GCONF_KEY_PREFIX"/aprs_inet_beacon"
+#define GCONF_KEY_APRS_INET_BEACON_INTERVAL      GCONF_KEY_PREFIX"/aprs_inet_beacon_interval"
+#define GCONF_KEY_APRS_ENABLE                                  GCONF_KEY_PREFIX"/aprs_enable"
+#define GCONF_KEY_APRS_ENABLE_INET_TX                  GCONF_KEY_PREFIX"/aprs_enable_inet_tx"
+#define GCONF_KEY_APRS_ENABLE_TTY_TX                   GCONF_KEY_PREFIX"/aprs_enable_tty_tx"
+
+#define GCONF_KEY_APRS_MAX_TRACK_PTS                   GCONF_KEY_PREFIX"/aprs_max_track_points"
+#define GCONF_KEY_APRS_MAX_STATIONS                    GCONF_KEY_PREFIX"/aprs_max_stations"
+#define GCONF_KEY_APRS_TTY_PORT                                GCONF_KEY_PREFIX"/aprs_tty_port"
+#define GCONF_KEY_APRS_TNC_CONN_METHOD                         GCONF_KEY_PREFIX"/aprs_tnc_conn_method"
+#define GCONF_KEY_APRS_TNC_BT_MAC                              GCONF_KEY_PREFIX"/aprs_tnc_bt_mac"
+#define GCONF_KEY_APRS_INET_AUTO_FILTER                GCONF_KEY_PREFIX"/aprs_inet_auto_filter"
+#define GCONF_KEY_APRS_INET_AUTO_FILTER_RANGE  GCONF_KEY_PREFIX"/aprs_inet_auto_filter_range"
+#define GCONF_KEY_APRS_SHOW_NEW_STATION_ALERT  GCONF_KEY_PREFIX"/aprs_show_new_station_alert"
+#define GCONF_KEY_APRS_BEACON_COMMENT                  GCONF_KEY_PREFIX"/aprs_beacon_comment"
+#define GCONF_KEY_APRS_TTY_BEACON_INTERVAL             GCONF_KEY_PREFIX"/aprs_tty_beacon_interval"
+
+#define GCONF_KEY_APRS_BEACON_PATH                             GCONF_KEY_PREFIX"/aprs_beacon_path"
+#define GCONF_KEY_APRS_BEACON_SYM_GROUP                GCONF_KEY_PREFIX"/aprs_beacon_symbol_group"
+#define GCONF_KEY_APRS_BEACON_SYMBOL                   GCONF_KEY_PREFIX"/aprs_beacon_symbol"
+#define GCONF_KEY_APRS_BEACON_COMPRESSED               GCONF_KEY_PREFIX"/aprs_beacon_compressed"
+
+//////////
+#endif // INCLUDE_APRS
+
 
 typedef struct _ScanInfo ScanInfo;
 struct _ScanInfo {
 
 typedef struct _ScanInfo ScanInfo;
 struct _ScanInfo {
@@ -145,6 +176,44 @@ struct _ScanInfo {
     DBusGProxy *sig_proxy;
 };
 
     DBusGProxy *sig_proxy;
 };
 
+#ifdef INCLUDE_APRS
+
+typedef struct
+{
+    GtkWidget *txt_aprs_mycall;
+    GtkWidget *txt_aprs_inet_server_validation;
+    GtkWidget *txt_aprs_server;
+    GtkWidget *txt_aprs_inet_beacon_interval;
+    GtkWidget *txt_aprs_inet_beacon_comment;
+    GtkWidget *txt_aprs_max_stations;
+    GtkWidget *txt_aprs_tty_port;
+    GtkWidget *txt_tnc_bt_mac;    
+    GtkWidget *txt_auto_filter_range;
+    GtkWidget *txt_beacon_comment;
+    GtkWidget *num_aprs_server_port;
+    GtkWidget *txt_tty_beacon_interval;
+    
+    GtkWidget *rad_aprs_file;
+    GtkWidget *rad_tnc_bt;
+    GtkWidget *rad_tnc_file;
+    
+    GtkWidget *chk_enable_inet_auto_filter;
+    GtkWidget *chk_enable_inet_tx;
+    GtkWidget *chk_enable_tty_tx;
+    GtkWidget *chk_aprs_show_new_station_alert;
+    GtkWidget *chk_aprs_enabled;
+    
+    GtkWidget *btn_scan_bt_tnc;
+    
+    GtkWidget *txt_unproto_path;
+    GtkWidget *chk_compressed_beacon;
+    GtkWidget *txt_beacon_group;
+    GtkWidget *txt_beacon_symbol;
+    
+} TAprsSettings;
+#endif // INCLUDE_APRS
+
+
 typedef struct _KeysDialogInfo KeysDialogInfo;
 struct _KeysDialogInfo {
     GtkWidget *cmb[CUSTOM_KEY_ENUM_COUNT];
 typedef struct _KeysDialogInfo KeysDialogInfo;
 struct _KeysDialogInfo {
     GtkWidget *cmb[CUSTOM_KEY_ENUM_COUNT];
@@ -155,6 +224,131 @@ struct _ColorsDialogInfo {
     GtkWidget *col[COLORABLE_ENUM_COUNT];
 };
 
     GtkWidget *col[COLORABLE_ENUM_COUNT];
 };
 
+#ifdef INCLUDE_APRS
+typedef enum
+{
+       ALIGN_TOP_LEFT,
+       ALIGN_TOP_CENTER,
+       ALIGN_TOP_RIGHT,
+       ALIGN_CENTER_LEFT,
+       ALIGN_CENTER_CENTER,
+       ALIGN_CENTER_RIGHT,
+       ALIGN_BOTTOM_LEFT,
+       ALIGN_BOTTOM_CENTER,
+       ALIGN_BOTTOM_RIGHT
+} TCellAlignment;
+
+void set_ctrl_alignment(GtkWidget *ctrl, TCellAlignment alignment)
+{
+       gfloat align_hor = 0.0f, align_vert = 0.0f;
+
+       switch(alignment)
+    {
+    case ALIGN_CENTER_LEFT:
+    case ALIGN_CENTER_CENTER:
+    case ALIGN_CENTER_RIGHT:
+       align_vert = 0.5f;
+       break;
+    case ALIGN_BOTTOM_LEFT:
+    case ALIGN_BOTTOM_CENTER:
+    case ALIGN_BOTTOM_RIGHT:
+       align_vert = 1.0f;
+       break;
+    case ALIGN_TOP_LEFT:
+    case ALIGN_TOP_CENTER:
+    case ALIGN_TOP_RIGHT:
+    default:
+       align_vert = 0.0f;
+       break;
+    }
+    
+
+    switch(alignment)
+    {
+    case ALIGN_CENTER_CENTER:
+    case ALIGN_BOTTOM_CENTER:
+    case ALIGN_TOP_CENTER:
+       align_hor = 0.5f;
+       break;
+    case ALIGN_CENTER_RIGHT:
+    case ALIGN_BOTTOM_RIGHT:
+    case ALIGN_TOP_RIGHT:
+       align_hor = 1.0f;
+       break;
+    case ALIGN_CENTER_LEFT:
+    case ALIGN_BOTTOM_LEFT:
+    case ALIGN_TOP_LEFT:
+    default:
+       align_hor = 0.0f;
+       break;
+    }
+    
+    gtk_misc_set_alignment(GTK_MISC(ctrl), align_hor, align_vert);
+}
+
+void set_ctrl_width(GtkWidget *ctrl, gint width)
+{
+       gtk_widget_set_size_request(ctrl, width, -1);
+}
+
+
+void add_edit_box(GtkWidget *table, gint xmin, gint xmax, gint ymin, gint ymax, gint width, 
+               GtkWidget **ctrl, TCellAlignment alignment, gchar * initial_value)
+{
+       GtkWidget * hbox;
+       
+    gtk_table_attach(GTK_TABLE(table),
+        hbox = gtk_hbox_new(FALSE, 4),
+            xmin, xmax, ymin, ymax, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+    gtk_box_pack_start(GTK_BOX(hbox),
+               *ctrl = gtk_entry_new(),
+        TRUE, TRUE, 0);
+
+#ifndef LEGACY
+    g_object_set(G_OBJECT(*ctrl), "hildon-input-mode",
+        HILDON_GTK_INPUT_MODE_FULL, NULL);
+#else
+    g_object_set(G_OBJECT(*ctrl), HILDON_AUTOCAP, FALSE, NULL);
+#endif
+
+    set_ctrl_alignment(*ctrl, alignment);
+    set_ctrl_width(*ctrl, width);    
+    
+    // Set the initial value
+    if(initial_value)
+    {
+       gtk_entry_set_text(GTK_ENTRY(*ctrl), initial_value);
+    }
+}
+
+void add_check_box(GtkWidget *table, gint xmin, gint xmax, gint ymin, gint ymax, gint width, 
+               GtkWidget **ctrl, TCellAlignment alignment, gchar * caption, gboolean initial_value)
+{
+       gtk_table_attach(GTK_TABLE(table),
+                       *ctrl = gtk_check_button_new_with_label(caption),
+        xmin, xmax, ymin, ymax, GTK_FILL, 0, 2, 4);
+       
+    set_ctrl_alignment(*ctrl, alignment);
+    set_ctrl_width(*ctrl, width);
+    
+    gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(*ctrl), initial_value);
+}
+
+void add_label_box(GtkWidget *table, gint xmin, gint xmax, gint ymin, gint ymax, gint width,  
+               GtkWidget **ctrl, TCellAlignment alignment, gchar * initial_value)
+{
+       
+    gtk_table_attach(GTK_TABLE(table),
+            *ctrl = gtk_label_new(initial_value),
+            xmin, xmax, ymin, ymax, GTK_FILL, 0, 2, 4);
+//        gtk_misc_set_alignment(GTK_MISC(ctrl), 1.f, 0.5f);
+
+    set_ctrl_alignment(*ctrl, alignment);
+    set_ctrl_width(*ctrl, width);
+}
+
+#endif // INCLUDE_APRS
+
 
 /**
  * Save all configuration data to GCONF.
 
 /**
  * Save all configuration data to GCONF.
@@ -281,7 +475,7 @@ settings_save()
 
     /* Save Deg Format. */
     gconf_client_set_string(gconf_client,
 
     /* Save Deg Format. */
     gconf_client_set_string(gconf_client,
-            GCONF_KEY_DEG_FORMAT, DEG_FORMAT_ENUM_TEXT[_degformat], NULL);
+            GCONF_KEY_DEG_FORMAT, DEG_FORMAT_ENUM_TEXT[_degformat].name , NULL);
 
     /* Save Speed Limit On flag. */
     gconf_client_set_bool(gconf_client,
 
     /* Save Speed Limit On flag. */
     gconf_client_set_bool(gconf_client,
@@ -1127,12 +1321,96 @@ settings_dialog_colors(GtkWidget *widget, GtkWidget *parent)
     return TRUE;
 }
 
     return TRUE;
 }
 
+#ifdef INCLUDE_APRS
+
+void load_aprs_options(GConfClient *gconf_client)
+{
+    _aprs_server = gconf_client_get_string(
+            gconf_client, GCONF_KEY_APRS_SERVER, NULL);
+
+    _aprs_server_port = gconf_client_get_int(
+            gconf_client, GCONF_KEY_APRS_SERVER_PORT, NULL);
+
+    _aprs_inet_server_validation = gconf_client_get_string(
+            gconf_client, GCONF_KEY_APRS_SERVER_VAL, NULL);
+
+    _aprs_mycall = gconf_client_get_string(gconf_client, 
+        GCONF_KEY_APRS_MYCALL, NULL);
+    
+
+    _aprs_tty_port = gconf_client_get_string(gconf_client, 
+        GCONF_KEY_APRS_TTY_PORT, NULL);
+    
+    
+    
+    _aprs_inet_beacon_comment = gconf_client_get_string(gconf_client, 
+       GCONF_KEY_APRS_INET_BEACON, NULL);
+
+    _aprs_inet_beacon_interval = gconf_client_get_int(
+        gconf_client, GCONF_KEY_APRS_INET_BEACON_INTERVAL, NULL);
+    
+    _aprs_std_pos_hist = gconf_client_get_int(
+        gconf_client, GCONF_KEY_APRS_MAX_TRACK_PTS, NULL);
+
+    _aprs_max_stations = gconf_client_get_int(
+            gconf_client, GCONF_KEY_APRS_MAX_STATIONS, NULL);
+    
+    _aprs_enable = gconf_client_get_bool(gconf_client,
+               GCONF_KEY_APRS_ENABLE, NULL);
+
+    _aprs_show_new_station_alert = gconf_client_get_bool(gconf_client,
+               GCONF_KEY_APRS_SHOW_NEW_STATION_ALERT, NULL);
+
+    _aprs_tnc_method = gconf_client_get_int(
+            gconf_client, GCONF_KEY_APRS_TNC_CONN_METHOD, NULL);
+
+    _aprs_tnc_bt_mac = gconf_client_get_string(gconf_client, 
+               GCONF_KEY_APRS_TNC_BT_MAC, NULL);
+    
+    _aprs_server_auto_filter_km = gconf_client_get_int(
+                gconf_client, GCONF_KEY_APRS_INET_AUTO_FILTER_RANGE, NULL);
+    
+    _aprs_server_auto_filter_on = gconf_client_get_bool(gconf_client,
+               GCONF_KEY_APRS_INET_AUTO_FILTER, NULL);
+    
+    _aprs_beacon_comment = gconf_client_get_string(gconf_client, 
+               GCONF_KEY_APRS_BEACON_COMMENT, NULL);
+    
+    _aprs_enable_inet_tx = gconf_client_get_bool(gconf_client,
+               GCONF_KEY_APRS_ENABLE_INET_TX, NULL);
+    _aprs_enable_tty_tx = gconf_client_get_bool(gconf_client,
+               GCONF_KEY_APRS_ENABLE_TTY_TX, NULL);
+    
+    _aprs_tty_beacon_interval = gconf_client_get_int(
+            gconf_client, GCONF_KEY_APRS_TTY_BEACON_INTERVAL, NULL);
+    
+    
+    //_aprs_unproto_path = g_strdup_printf("WIDE2-2");
+    _aprs_unproto_path = gconf_client_get_string(gconf_client, 
+                               GCONF_KEY_APRS_BEACON_PATH, NULL);
+    
+    _aprs_transmit_compressed_posit = gconf_client_get_bool(gconf_client,
+               GCONF_KEY_APRS_BEACON_COMPRESSED, NULL);
+    
+    gchar *tmp;
+    tmp = gconf_client_get_string(gconf_client, 
+                       GCONF_KEY_APRS_BEACON_SYM_GROUP, NULL);
+    if(tmp && strlen(tmp)>0) _aprs_beacon_group = tmp[0];
+    else _aprs_beacon_group = '/'; 
+    
+    //_aprs_beacon_symbol = 'k';
+    tmp = gconf_client_get_string(gconf_client, 
+                               GCONF_KEY_APRS_BEACON_SYMBOL, NULL);
+    if(tmp && strlen(tmp)>0) _aprs_beacon_symbol  = tmp[0];
+    else _aprs_beacon_symbol = 'l';
+}
+#endif // INCLUDE_APRS
+
 /**
  * Bring up the Settings dialog.  Return TRUE if and only if the recever
  * information has changed (MAC or channel).
  */
 /**
  * Bring up the Settings dialog.  Return TRUE if and only if the recever
  * information has changed (MAC or channel).
  */
-gboolean
-settings_dialog()
+gboolean settings_dialog()
 {
     static GtkWidget *dialog = NULL;
     static GtkWidget *notebook = NULL;
 {
     static GtkWidget *dialog = NULL;
     static GtkWidget *notebook = NULL;
@@ -1476,7 +1754,7 @@ settings_dialog()
                 cmb_degformat = gtk_combo_box_new_text());
         for(i = 0; i < DEG_FORMAT_ENUM_COUNT; i++)
             gtk_combo_box_append_text(GTK_COMBO_BOX(cmb_degformat),
                 cmb_degformat = gtk_combo_box_new_text());
         for(i = 0; i < DEG_FORMAT_ENUM_COUNT; i++)
             gtk_combo_box_append_text(GTK_COMBO_BOX(cmb_degformat),
-                DEG_FORMAT_ENUM_TEXT[i]);
+                DEG_FORMAT_ENUM_TEXT[i].name);
 
         gtk_table_attach(GTK_TABLE(table),
                 label = gtk_label_new(_("Auto-Download Pre-cache")),
 
         gtk_table_attach(GTK_TABLE(table),
                 label = gtk_label_new(_("Auto-Download Pre-cache")),
@@ -1965,7 +2243,11 @@ settings_init()
     COLORABLE_GCONF[COLORABLE_ROUTE_BREAK]
         = GCONF_KEY_PREFIX"/color_route_break";
     COLORABLE_GCONF[COLORABLE_POI] = GCONF_KEY_PREFIX"/color_poi";
     COLORABLE_GCONF[COLORABLE_ROUTE_BREAK]
         = GCONF_KEY_PREFIX"/color_route_break";
     COLORABLE_GCONF[COLORABLE_POI] = GCONF_KEY_PREFIX"/color_poi";
-
+    
+#ifdef INCLUDE_APRS
+    COLORABLE_GCONF[COLORABLE_APRS_STATION] = GCONF_KEY_PREFIX"/color_aprs_station";
+#endif // INCLUDE_APRS
+    
     if(!gconf_client)
     {
         popup_error(_window, _("Failed to initialize GConf.  Quitting."));
     if(!gconf_client)
     {
         popup_error(_window, _("Failed to initialize GConf.  Quitting."));
@@ -2149,7 +2431,7 @@ settings_init()
         gint i = 0;
         if(degformat_key_str)
             for(i = DEG_FORMAT_ENUM_COUNT - 1; i > 0; i--)
         gint i = 0;
         if(degformat_key_str)
             for(i = DEG_FORMAT_ENUM_COUNT - 1; i > 0; i--)
-                if(!strcmp(degformat_key_str, DEG_FORMAT_ENUM_TEXT[i]))
+                if(!strcmp(degformat_key_str, DEG_FORMAT_ENUM_TEXT[i].name))
                     break;
         _degformat = i;
     }
                     break;
         _degformat = i;
     }
@@ -2190,6 +2472,10 @@ settings_init()
         _unblank_option = i;
     }
 
         _unblank_option = i;
     }
 
+#ifdef INCLUDE_APRS
+    load_aprs_options(gconf_client);
+#endif // INCLUDE_APRS
+    
     /* Get Info Font Size.  Default is INFO_FONT_MEDIUM. */
     {
         gchar *info_font_size_str = gconf_client_get_string(gconf_client,
     /* Get Info Font Size.  Default is INFO_FONT_MEDIUM. */
     {
         gchar *info_font_size_str = gconf_client_get_string(gconf_client,
@@ -2551,3 +2837,598 @@ settings_init()
     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
 }
 
     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
 }
 
+
+#ifdef INCLUDE_APRS
+////// APRS Settings start
+
+void read_aprs_options(TAprsSettings *aprsSettings )
+{
+       // Basic options
+    _aprs_mycall = g_strdup(gtk_entry_get_text(
+        GTK_ENTRY(aprsSettings->txt_aprs_mycall)));
+    
+     
+    _aprs_enable = gtk_toggle_button_get_active(
+                       GTK_TOGGLE_BUTTON(aprsSettings->chk_aprs_enabled ));
+
+    _aprs_show_new_station_alert = gtk_toggle_button_get_active(
+                       GTK_TOGGLE_BUTTON(aprsSettings->chk_aprs_show_new_station_alert ));
+
+    
+    gchar * s_max_stations = g_strdup(gtk_entry_get_text(
+            GTK_ENTRY(aprsSettings->txt_aprs_max_stations)));
+    gint64 i64_max_stations = g_ascii_strtoll (s_max_stations, NULL, 10);
+    _aprs_max_stations = (gint)i64_max_stations;
+        
+
+    // Inet options
+    _aprs_server = g_strdup(gtk_entry_get_text(
+        GTK_ENTRY(aprsSettings->txt_aprs_server)));
+
+    
+    _aprs_tty_port = g_strdup(gtk_entry_get_text(
+            GTK_ENTRY(aprsSettings->txt_aprs_tty_port)));
+    
+    _aprs_inet_server_validation = g_strdup(gtk_entry_get_text(
+        GTK_ENTRY(aprsSettings->txt_aprs_inet_server_validation)));
+
+    gchar * s_port = g_strdup(gtk_entry_get_text(
+        GTK_ENTRY(aprsSettings->num_aprs_server_port)));
+    gint64 i64_port = g_ascii_strtoll (s_port, NULL, 10);
+    _aprs_server_port = (gint)i64_port;
+
+    
+    gchar * s_beacon_interval = g_strdup(gtk_entry_get_text(
+        GTK_ENTRY(aprsSettings->txt_aprs_inet_beacon_interval )));
+    gint64 i64_beacon_interval = g_ascii_strtoll (s_beacon_interval, NULL, 10);
+    g_free(s_beacon_interval);
+    _aprs_inet_beacon_interval = (gint)i64_beacon_interval;
+
+    s_beacon_interval = g_strdup(gtk_entry_get_text(
+        GTK_ENTRY(aprsSettings->txt_tty_beacon_interval )));
+    i64_beacon_interval = g_ascii_strtoll (s_beacon_interval, NULL, 10);
+    g_free(s_beacon_interval);
+    _aprs_tty_beacon_interval = (gint)i64_beacon_interval;
+    
+    
+    _aprs_inet_beacon_comment = g_strdup(gtk_entry_get_text(
+        GTK_ENTRY(aprsSettings->txt_aprs_inet_beacon_comment )));
+    
+   _aprs_tnc_bt_mac =  g_strdup(gtk_entry_get_text(
+               GTK_ENTRY(aprsSettings->txt_tnc_bt_mac )));
+    
+   _aprs_tnc_method = TNC_CONNECTION_BT;
+   if( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(aprsSettings->rad_tnc_file) ))
+   {
+          _aprs_tnc_method = TNC_CONNECTION_FILE;
+   }
+
+
+   _aprs_server_auto_filter_on = gtk_toggle_button_get_active(
+           GTK_TOGGLE_BUTTON(aprsSettings->chk_enable_inet_auto_filter ));
+   
+   gchar * s_server_auto_filter_km = g_strdup(gtk_entry_get_text(
+       GTK_ENTRY(aprsSettings->txt_auto_filter_range )));
+   gint64 i64_server_auto_filter_km = g_ascii_strtoll (s_server_auto_filter_km, NULL, 10);
+   _aprs_server_auto_filter_km = (gint)i64_server_auto_filter_km;
+
+
+   _aprs_enable_inet_tx = gtk_toggle_button_get_active(
+           GTK_TOGGLE_BUTTON(aprsSettings->chk_enable_inet_tx ));
+
+   _aprs_enable_tty_tx = gtk_toggle_button_get_active(
+           GTK_TOGGLE_BUTTON(aprsSettings->chk_enable_tty_tx ));
+   
+   _aprs_beacon_comment = g_strdup(gtk_entry_get_text(
+           GTK_ENTRY(aprsSettings->txt_beacon_comment )));
+   
+   _aprs_beacon_comment = g_strdup(gtk_entry_get_text(
+              GTK_ENTRY(aprsSettings->txt_beacon_comment )));
+   
+
+   _aprs_unproto_path = g_strdup(gtk_entry_get_text(
+                  GTK_ENTRY(aprsSettings->txt_unproto_path )));
+   
+   gchar *tmp;
+   tmp = g_strdup(gtk_entry_get_text(
+                  GTK_ENTRY(aprsSettings->txt_beacon_group )));
+   if(strlen(tmp)>0) _aprs_beacon_group = tmp[0]; 
+
+   tmp = g_strdup(gtk_entry_get_text(
+                  GTK_ENTRY(aprsSettings->txt_beacon_symbol )));
+   if(strlen(tmp)>0) _aprs_beacon_symbol = tmp[0]; 
+   
+   _aprs_transmit_compressed_posit  = FALSE; // Not currently supported
+   
+}
+
+void save_aprs_options()
+{
+       GConfClient *gconf_client = gconf_client_get_default();
+    if(!gconf_client)
+    {
+        popup_error(_window,
+                _("Failed to initialize GConf.  Settings were not saved."));
+        return;
+    }
+
+    
+    /* APRS */
+    if(_aprs_server)
+        gconf_client_set_string(gconf_client,
+                GCONF_KEY_APRS_SERVER, _aprs_server, NULL);
+
+    if(_aprs_server_port)
+        gconf_client_set_int(gconf_client,
+                GCONF_KEY_APRS_SERVER_PORT, _aprs_server_port, NULL);
+
+    if(_aprs_inet_server_validation)
+        gconf_client_set_string(gconf_client,
+                GCONF_KEY_APRS_SERVER_VAL, _aprs_inet_server_validation, NULL);
+
+    if(_aprs_mycall)
+        gconf_client_set_string(gconf_client,
+                GCONF_KEY_APRS_MYCALL, _aprs_mycall, NULL);
+
+    if(_aprs_tty_port)
+        gconf_client_set_string(gconf_client,
+                GCONF_KEY_APRS_TTY_PORT, _aprs_tty_port, NULL);
+
+    
+    
+    
+    if(_aprs_inet_beacon_comment)
+        gconf_client_set_string(gconf_client,
+                       GCONF_KEY_APRS_INET_BEACON, _aprs_inet_beacon_comment, NULL);
+
+    gconf_client_set_int(gconf_client,
+               GCONF_KEY_APRS_INET_BEACON_INTERVAL, _aprs_inet_beacon_interval, NULL);
+               
+    gconf_client_set_int(gconf_client,
+               GCONF_KEY_APRS_MAX_TRACK_PTS, _aprs_std_pos_hist, NULL);
+    
+    gconf_client_set_int(gconf_client,
+               GCONF_KEY_APRS_MAX_STATIONS, _aprs_max_stations, NULL);
+    
+    gconf_client_set_bool(gconf_client,
+               GCONF_KEY_APRS_ENABLE, _aprs_enable, NULL);
+    
+
+    gconf_client_set_bool(gconf_client,
+               GCONF_KEY_APRS_SHOW_NEW_STATION_ALERT, _aprs_show_new_station_alert, NULL);
+   
+    if(_aprs_tnc_bt_mac)
+        gconf_client_set_string(gconf_client,
+                       GCONF_KEY_APRS_TNC_BT_MAC, _aprs_tnc_bt_mac, NULL);
+  
+    gconf_client_set_int(gconf_client,
+               GCONF_KEY_APRS_TNC_CONN_METHOD, _aprs_tnc_method, NULL);
+
+    gconf_client_set_int(gconf_client,
+               GCONF_KEY_APRS_INET_AUTO_FILTER_RANGE, _aprs_server_auto_filter_km, NULL);
+
+    gconf_client_set_bool(gconf_client,
+               GCONF_KEY_APRS_INET_AUTO_FILTER, _aprs_server_auto_filter_on, NULL);
+
+    gconf_client_set_bool(gconf_client,
+               GCONF_KEY_APRS_ENABLE_INET_TX, _aprs_enable_inet_tx, NULL);
+       gconf_client_set_bool(gconf_client,
+                       GCONF_KEY_APRS_ENABLE_TTY_TX, _aprs_enable_tty_tx, NULL);
+       
+    if(_aprs_beacon_comment)
+        gconf_client_set_string(gconf_client,
+                       GCONF_KEY_APRS_BEACON_COMMENT, _aprs_beacon_comment, NULL);
+
+    gconf_client_set_int(gconf_client,
+               GCONF_KEY_APRS_TTY_BEACON_INTERVAL, _aprs_tty_beacon_interval, NULL);
+    
+    gconf_client_set_bool(gconf_client,
+               GCONF_KEY_APRS_BEACON_COMPRESSED, _aprs_transmit_compressed_posit, NULL);
+
+    if(_aprs_unproto_path)
+        gconf_client_set_string(gconf_client,
+                       GCONF_KEY_APRS_BEACON_PATH, _aprs_unproto_path, NULL);
+
+    //if(_aprs_beacon_group)
+    {
+       gchar tmp[5];
+       snprintf(tmp, 5, "%c", _aprs_beacon_group);
+       
+        gconf_client_set_string(gconf_client,
+                       GCONF_KEY_APRS_BEACON_SYM_GROUP, tmp, NULL);
+            
+        //if(strlen(tmp)> 1) _aprs_beacon_group = tmp[0]; 
+    }
+    
+    //if(_aprs_beacon_symbol)
+    {
+       gchar tmp[5];
+       snprintf(tmp, 5, "%c", _aprs_beacon_symbol);
+       
+       gconf_client_set_string(gconf_client,
+                                       GCONF_KEY_APRS_BEACON_SYMBOL, tmp, NULL);
+       
+       //if(strlen(tmp)> 1) _aprs_beacon_symbol = tmp[0];
+    }
+                
+}
+
+
+
+
+
+void setup_aprs_options_page(GtkWidget *notebook, TAprsSettings *aprsSettings)
+{
+    GtkWidget *table;
+    GtkWidget *label;
+//    GtkWidget *hbox;
+
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+        table = gtk_table_new(3/*rows*/, 4/*columns*/, FALSE /*Auto re-size*/),
+        label = gtk_label_new(_("Station")));
+
+    /* Callsign. */
+    // Label
+    add_label_box(table, 0, 1, 0, 1, -1, &label, ALIGN_TOP_LEFT, "Callsign");
+    add_edit_box (table, 1, 2, 0, 1, -1, &(aprsSettings->txt_aprs_mycall), ALIGN_TOP_LEFT, _aprs_mycall);
+
+
+    add_check_box(table, 2, 4, 0, 1, -1, &(aprsSettings->chk_compressed_beacon), ALIGN_TOP_LEFT, 
+               "Compress Beacons", _aprs_transmit_compressed_posit);
+    // Not yet supported
+    gtk_widget_set_sensitive (GTK_WIDGET (aprsSettings->chk_compressed_beacon), FALSE);
+    
+    
+    
+    add_label_box(table, 0, 1, 1, 2, -1, &label, ALIGN_TOP_LEFT, "Beacon Path");
+    add_edit_box (table, 1, 2, 1, 2, -1, &(aprsSettings->txt_unproto_path), ALIGN_TOP_LEFT, _aprs_unproto_path);
+
+
+    add_check_box(table, 2, 4, 1, 2, -1, &(aprsSettings->chk_aprs_show_new_station_alert), ALIGN_TOP_LEFT, 
+       "Show New Station Alerts", _aprs_show_new_station_alert);
+
+    
+    add_label_box(table, 0, 1, 2, 3, -1, &label, ALIGN_TOP_LEFT, "Symbol Group");
+    gchar initialValue[2];
+    snprintf(initialValue, 2, "%c", _aprs_beacon_group);
+    add_edit_box (table, 1, 2, 2, 3, -1, &(aprsSettings->txt_beacon_group), ALIGN_TOP_LEFT, initialValue);
+    
+    add_label_box(table, 2, 3, 2, 3, -1, &label, ALIGN_TOP_LEFT, "Symbol");
+    
+    snprintf(initialValue, 2, "%c", _aprs_beacon_symbol);
+    add_edit_box (table, 3, 4, 2, 3, -1, &(aprsSettings->txt_beacon_symbol), ALIGN_TOP_LEFT, initialValue);
+   
+}
+
+
+
+void setup_aprs_tty_page_options_page(GtkWidget *notebook, TAprsSettings *aprsSettings)
+{
+    GtkWidget *table;
+    GtkWidget *label;
+    GtkWidget *hbox;
+
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+        table = gtk_table_new(3/*rows*/, 2/*columns*/, FALSE /*Auto re-size*/),
+        label = gtk_label_new(_("TNC 1")));
+
+    
+       // Receiver MAC Address. 
+    gtk_table_attach(GTK_TABLE(table),
+               aprsSettings->rad_tnc_bt = gtk_radio_button_new_with_label(
+                NULL, _("Bluetooth")),
+            0, 1, 0, 1, GTK_FILL, 0, 2, 4);
+//    gtk_misc_set_alignment(/*GTK_MISC*/(aprsSettings->rad_tnc_bt), 1.f, 0.5f);
+    gtk_table_attach(GTK_TABLE(table),
+            hbox = gtk_hbox_new(FALSE, 4),
+            1, 2, 0, 1, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+  
+    gtk_box_pack_start(GTK_BOX(hbox),
+               aprsSettings->txt_tnc_bt_mac = gtk_entry_new(),
+            TRUE, TRUE, 0);
+#ifdef MAEMO_CHANGES
+#ifndef LEGACY
+    g_object_set(G_OBJECT(aprsSettings->txt_tnc_bt_mac), "hildon-input-mode",
+            HILDON_GTK_INPUT_MODE_FULL, NULL);
+#else
+    g_object_set(G_OBJECT(txt_gps_bt_mac), HILDON_AUTOCAP, FALSE, NULL);
+#endif
+#endif
+    gtk_box_pack_start(GTK_BOX(hbox),
+               aprsSettings->btn_scan_bt_tnc = gtk_button_new_with_label(_("Scan...")),
+            FALSE, FALSE, 0);
+        
+#ifndef HAVE_LIBGPSBT
+gtk_widget_set_sensitive(aprsSettings->rad_tnc_bt, FALSE);
+gtk_widget_set_sensitive(aprsSettings->txt_tnc_bt_mac, FALSE);
+gtk_widget_set_sensitive(aprsSettings->btn_scan_bt_tnc, FALSE);
+#endif
+
+    // File Path (RFComm). 
+       gtk_table_attach(GTK_TABLE(table),
+               aprsSettings->rad_tnc_file = gtk_radio_button_new_with_label_from_widget(
+                GTK_RADIO_BUTTON(aprsSettings->rad_tnc_bt), _("File Path")),
+            0, 1, 1, 2, GTK_FILL, 0, 2, 4);
+//    gtk_misc_set_alignment(/*GTK_MISC*/(aprsSettings->rad_tnc_file), 1.f, 0.5f);
+    gtk_table_attach(GTK_TABLE(table),
+            hbox = gtk_hbox_new(FALSE, 4),
+            1, 2, 1, 2, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+    gtk_box_pack_start(GTK_BOX(hbox),
+               aprsSettings->txt_aprs_tty_port = gtk_entry_new(),
+            TRUE, TRUE, 0);
+#ifdef MAEMO_CHANGES
+#ifndef LEGACY
+    g_object_set(G_OBJECT(aprsSettings->txt_aprs_tty_port), "hildon-input-mode",
+            HILDON_GTK_INPUT_MODE_FULL, NULL);
+#else
+    g_object_set(G_OBJECT(txt_gps_file_path), HILDON_AUTOCAP, FALSE, NULL);
+#endif
+#endif
+
+    
+
+       add_check_box(table, 0, 1, 2, 3, -1, &(aprsSettings->chk_enable_tty_tx), ALIGN_TOP_LEFT, 
+               "Enable TX", _aprs_enable_tty_tx);
+
+       gtk_table_attach(GTK_TABLE(table),
+           label = gtk_label_new(_("Only KISS TNC's are supported (9600 8N1)")),
+               1, 2, 2, 3, GTK_FILL, 0, 2, 4);
+       gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+       
+       
+       
+       // Init
+
+    if(aprsSettings->txt_aprs_tty_port)
+        gtk_entry_set_text(GTK_ENTRY(aprsSettings->txt_aprs_tty_port), _aprs_tty_port);
+    
+    if(aprsSettings->txt_tnc_bt_mac)
+            gtk_entry_set_text(GTK_ENTRY(aprsSettings->txt_tnc_bt_mac), _aprs_tnc_bt_mac);
+    
+  
+    if(_aprs_tnc_method == TNC_CONNECTION_BT)
+       gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(aprsSettings->rad_tnc_bt), TRUE);
+    else if(_aprs_tnc_method == TNC_CONNECTION_FILE)
+       gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(aprsSettings->rad_tnc_file), TRUE);
+
+   
+}
+
+void setup_aprs_tty2_page_options_page(GtkWidget *notebook, TAprsSettings *aprsSettings)
+{
+    GtkWidget *table;
+    GtkWidget *label;
+
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+        table = gtk_table_new(2/*rows*/, 1/*columns*/, FALSE /*Auto re-size*/),
+        label = gtk_label_new(_("TNC 2")));
+
+    add_label_box(table, 0, 1, 0, 1, -1, &label, ALIGN_TOP_LEFT, "Beacon Interval");
+    
+    gchar s_interval[8]; s_interval[0] = 0;
+    if(_aprs_tty_beacon_interval>0) snprintf(s_interval, 8, "%u", _aprs_tty_beacon_interval );
+    add_edit_box (table, 1, 3, 0, 1, -1, &(aprsSettings->txt_tty_beacon_interval), ALIGN_TOP_LEFT, 
+               s_interval);
+    add_label_box(table, 3, 4, 0, 1, -1, &label, ALIGN_TOP_LEFT, "seconds");
+    
+    add_label_box(table, 0, 1, 1, 2, -1, &label, ALIGN_TOP_LEFT, "Beacon Text");
+    add_edit_box (table, 1, 4, 1, 2, -1, &(aprsSettings->txt_beacon_comment), ALIGN_TOP_LEFT, _aprs_beacon_comment);
+
+}
+
+
+void setup_aprs_general_options_page(GtkWidget *notebook, TAprsSettings *aprsSettings)
+{
+    GtkWidget *table;
+    GtkWidget *label;
+
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+        table = gtk_table_new(2/*rows*/, 1/*columns*/, FALSE /*Auto re-size*/),
+        label = gtk_label_new(_("APRS")));
+    
+    // Notice
+    add_label_box(table, 0, 1, 0, 1, -1, &label, ALIGN_TOP_LEFT, 
+       "APRS (Automatic/Amateur Position Reporting System) is \na system used by Radio Amateurs to communicate position, \nweather, and short messages.");
+
+    // Enabled    
+    add_check_box(table, 0, 1, 1, 2, -1, &(aprsSettings->chk_aprs_enabled), ALIGN_TOP_LEFT, 
+       "Enable APRS functionality", _aprs_enable);
+}
+
+void setup_aprs_inet_options_page(GtkWidget *notebook, TAprsSettings *aprsSettings)
+{
+    GtkWidget *table;
+    GtkWidget *label;
+
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+        table = gtk_table_new(3/*rows*/, 5/*columns*/, FALSE /*Auto re-size*/),
+        label = gtk_label_new(_("Internet")));
+
+
+    /*iNet server*/
+    add_label_box(table, 0, 1, 0, 1, -1, &label, ALIGN_TOP_LEFT, "Server");
+    add_edit_box (table, 1, 3, 0, 1, -1, &(aprsSettings->txt_aprs_server), ALIGN_TOP_LEFT, _aprs_server);
+
+    /*iNet server port*/
+    add_label_box(table, 3, 4, 0, 1, -1, &label, ALIGN_TOP_RIGHT, "Port");
+    add_edit_box (table, 4, 5, 0, 1, 0.5, &(aprsSettings->num_aprs_server_port), ALIGN_TOP_LEFT, 
+               g_strdup_printf("%u", _aprs_server_port));
+    
+
+    /* Validation */
+    add_label_box(table, 0, 1, 1, 2, -1, &label, ALIGN_TOP_LEFT, "Server Validation");
+    add_edit_box (table, 1, 3, 1, 2, 0.5, &(aprsSettings->txt_aprs_inet_server_validation), ALIGN_TOP_LEFT, _aprs_inet_server_validation);
+    add_check_box(table, 3, 5, 1, 2, -1, &(aprsSettings->chk_enable_inet_tx), ALIGN_TOP_LEFT, 
+       "Enable TX", _aprs_enable_inet_tx);
+
+    /*iNet server filter*/
+    add_label_box(table, 0, 1, 2, 3, -1, &label, ALIGN_TOP_LEFT, "Filter data");
+    add_check_box(table, 1, 2, 2, 3, -1, &(aprsSettings->chk_enable_inet_auto_filter), ALIGN_TOP_LEFT, "Enable", 
+               _aprs_server_auto_filter_on);
+    add_label_box(table, 2, 3, 2, 3, -1, &label, ALIGN_TOP_RIGHT, "Range");
+    add_edit_box (table, 3, 4, 2, 3, 0.75, &(aprsSettings->txt_auto_filter_range), ALIGN_TOP_LEFT, 
+               g_strdup_printf("%u", _aprs_server_auto_filter_km));
+    add_label_box(table, 4, 5, 2, 3, -1, &label, ALIGN_TOP_LEFT, "km");
+
+}
+
+
+
+void setup_aprs_inet2_options_page(GtkWidget *notebook, TAprsSettings *aprsSettings)
+{
+    GtkWidget *table;
+    GtkWidget *label;
+    GtkWidget *hbox;
+
+    gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+        table = gtk_table_new(2/*rows*/, 3/*columns*/, FALSE /*Auto re-size*/),
+        label = gtk_label_new(_("Internet 2")));
+
+
+    /* iNet Beacon interval */
+    // Label
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new(_("Beacon interval")),
+            0, 1, 0, 1, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    // Edit box
+    gtk_table_attach(GTK_TABLE(table),
+        hbox = gtk_hbox_new(FALSE, 4),
+            1, 2, 0, 1, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+    gtk_box_pack_start(GTK_BOX(hbox),
+        aprsSettings->txt_aprs_inet_beacon_interval = gtk_entry_new(),
+        TRUE, TRUE, 0);
+
+#ifndef LEGACY
+    g_object_set(G_OBJECT(aprsSettings->txt_aprs_inet_beacon_interval), "hildon-input-mode",
+        HILDON_GTK_INPUT_MODE_FULL, NULL);
+#else
+    g_object_set(G_OBJECT(aprsSettings->txt_aprs_inet_beacon_interval), HILDON_AUTOCAP, FALSE, NULL);
+#endif
+
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new(_("seconds")),
+            2, 3, 0, 1, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    /* Beacon comment */
+    // Label
+    gtk_table_attach(GTK_TABLE(table),
+        label = gtk_label_new(_("Beacon Comment")),
+            0, 1, 1, 2, GTK_FILL, 0, 2, 4);
+    gtk_misc_set_alignment(GTK_MISC(label), 1.f, 0.5f);
+
+    // Edit box
+    gtk_table_attach(GTK_TABLE(table),
+        hbox = gtk_hbox_new(FALSE, 4),
+            1, 3, 1, 2, GTK_EXPAND | GTK_FILL, 0, 2, 4);
+    gtk_box_pack_start(GTK_BOX(hbox),
+        aprsSettings->txt_aprs_inet_beacon_comment = gtk_entry_new(),
+        TRUE, TRUE, 0);
+
+#ifndef LEGACY
+    g_object_set(G_OBJECT(aprsSettings->txt_aprs_inet_beacon_comment), "hildon-input-mode",
+        HILDON_GTK_INPUT_MODE_FULL, NULL);
+#else
+    g_object_set(G_OBJECT(aprsSettings->txt_aprs_inet_beacon_comment), HILDON_AUTOCAP, FALSE, NULL);
+#endif
+    
+    // Init values
+    if(_aprs_inet_beacon_interval)
+    {
+        gchar interval[8];
+        snprintf(interval, 8, "%d", _aprs_inet_beacon_interval);
+
+       gtk_entry_set_text(GTK_ENTRY(aprsSettings->txt_aprs_inet_beacon_interval), interval);
+    }
+        
+    if(_aprs_inet_beacon_comment)
+        gtk_entry_set_text(GTK_ENTRY(aprsSettings->txt_aprs_inet_beacon_comment), _aprs_inet_beacon_comment);
+
+}
+
+void aprs_settings_dialog(gboolean *aprs_inet_config_changed, gboolean *aprs_tty_config_changed)
+{
+    static GtkWidget *dialog = NULL;
+    static GtkWidget *notebook = NULL;
+    static TAprsSettings aprs_settings;
+    static ScanInfo scan_info = {0};
+    
+    printf("%s()\n", __PRETTY_FUNCTION__);
+
+    
+    *aprs_inet_config_changed = FALSE;
+    *aprs_tty_config_changed = FALSE;
+    
+    if(dialog == NULL)
+    {
+        dialog = gtk_dialog_new_with_buttons(_("APRS Settings"),
+                GTK_WINDOW(_window), GTK_DIALOG_MODAL,
+                GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+                NULL);
+
+        /* Enable the help button. */
+#ifndef LEGACY
+        hildon_help_dialog_help_enable(
+#else
+        ossohelp_dialog_help_enable(
+#endif 
+                GTK_DIALOG(dialog), HELP_ID_SETTINGS, _osso);
+
+        gtk_dialog_add_button(GTK_DIALOG(dialog),
+                GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT);
+
+        gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
+                notebook = gtk_notebook_new(), TRUE, TRUE, 0);
+
+
+        
+        // Add enable, and info page
+        setup_aprs_general_options_page(notebook, &aprs_settings);
+        
+        // Add station details page
+        setup_aprs_options_page(notebook, &aprs_settings);
+        
+        // Add iNet page
+        setup_aprs_inet_options_page(notebook, &aprs_settings);
+        setup_aprs_inet2_options_page(notebook, &aprs_settings);
+        
+        // Add TTY page
+        setup_aprs_tty_page_options_page(notebook, &aprs_settings);
+        setup_aprs_tty2_page_options_page(notebook, &aprs_settings);
+        
+
+
+        /* Connect signals. */
+        memset(&scan_info, 0, sizeof(scan_info));
+        scan_info.settings_dialog = dialog;
+        scan_info.txt_gps_bt_mac = aprs_settings.txt_tnc_bt_mac;
+        g_signal_connect(G_OBJECT(aprs_settings.btn_scan_bt_tnc), "clicked",
+                         G_CALLBACK(scan_bluetooth), &scan_info);
+
+    }
+  
+    gtk_widget_show_all(dialog);
+
+    if(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(dialog)))
+    {
+       // TODO - check if settings have really changed 
+        *aprs_inet_config_changed = TRUE;
+        *aprs_tty_config_changed = TRUE;
+        
+        
+       read_aprs_options(&aprs_settings );
+
+        save_aprs_options();
+
+    }
+
+    gtk_widget_hide(dialog);
+
+    vprintf("%s(): return %d\n", __PRETTY_FUNCTION__, rcvr_changed);
+
+}
+#endif // INCLUDE_APRS
+
index e707991d1f8c5f6f332b19ffdbe0a18171e49cfd..7850670d4a0de2b38c3c1e33a56d766250692175 100644 (file)
@@ -31,4 +31,8 @@ void settings_save(void);
 
 gboolean settings_dialog(void);
 
 
 gboolean settings_dialog(void);
 
+#ifdef INCLUDE_APRS
+void aprs_settings_dialog(gboolean *aprs_inet_config_changed, gboolean *aprs_tty_config_changed);
+#endif // INCLUDE_APRS
+
 #endif /* ifndef MAEMO_MAPPER_SETTINGS_H */
 #endif /* ifndef MAEMO_MAPPER_SETTINGS_H */
index 08fa06462636c9fb56e940722ccc2d836a4efac6..85bdb9b28517d10f50f6c51a0e2b2d51c9d47b19 100644 (file)
  *
  * You should have received a copy of the GNU General Public License
  * along with Maemo Mapper.  If not, see <http://www.gnu.org/licenses/>.
  *
  * You should have received a copy of the GNU General Public License
  * along with Maemo Mapper.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *  
+ * Parts of this code have been ported from Xastir by Rob Williams (10 Aug 2008):
+ * 
+ *  * XASTIR, Amateur Station Tracking and Information Reporting
+ * Copyright (C) 1999,2000  Frank Giannandrea
+ * Copyright (C) 2000-2007  The Xastir Group
+ * 
  */
 
 #ifndef MAEMO_MAPPER_TYPES_H
 #define MAEMO_MAPPER_TYPES_H
 
  */
 
 #ifndef MAEMO_MAPPER_TYPES_H
 #define MAEMO_MAPPER_TYPES_H
 
+
+#ifdef HAVE_CONFIG_H
+#    include "config.h"
+#endif
+
 #include <time.h>
 #include <gdbm.h>
 #include <gtk/gtk.h>
 #include <time.h>
 #include <gdbm.h>
 #include <gtk/gtk.h>
 #include "sqlite3.h"
 #endif
 
 #include "sqlite3.h"
 #endif
 
+// Latitude and longitude string formats.
+#define CONVERT_HP_NORMAL       0
+#define CONVERT_HP_NOSP         1
+#define CONVERT_LP_NORMAL       2
+#define CONVERT_LP_NOSP         3
+#define CONVERT_DEC_DEG         4
+#define CONVERT_UP_TRK          5
+#define CONVERT_DMS_NORMAL      6
+#define CONVERT_VHP_NOSP        7
+#define CONVERT_DMS_NORMAL_FORMATED      8
+#define CONVERT_HP_NORMAL_FORMATED       9
+#define CONVERT_DEC_DEG_N       10
+
+#define MAX_DEVICE_BUFFER       4096
+
 /** This enumerated type defines the possible connection states. */
 typedef enum
 {
 /** This enumerated type defines the possible connection states. */
 typedef enum
 {
@@ -224,6 +252,9 @@ typedef enum
     COLORABLE_ROUTE_WAY,
     COLORABLE_ROUTE_BREAK,
     COLORABLE_POI,
     COLORABLE_ROUTE_WAY,
     COLORABLE_ROUTE_BREAK,
     COLORABLE_POI,
+#ifdef INCLUDE_APRS
+    COLORABLE_APRS_STATION,
+#endif // INCLUDE_APRS
     COLORABLE_ENUM_COUNT
 } Colorable;
 
     COLORABLE_ENUM_COUNT
 } Colorable;
 
@@ -238,9 +269,23 @@ typedef enum
     NSEW_DDPDDDDD,
     NSEW_DD_MMPMMM,
     NSEW_DD_MM_SSPS,
     NSEW_DDPDDDDD,
     NSEW_DD_MMPMMM,
     NSEW_DD_MM_SSPS,
+    IARU_LOC,
+    UK_OSGB,
+    UK_NGR,
     DEG_FORMAT_ENUM_COUNT
 } DegFormat;
 
     DEG_FORMAT_ENUM_COUNT
 } DegFormat;
 
+typedef struct _CoordFormatSetup CoordFormatSetup;
+struct _CoordFormatSetup 
+{
+       gchar    *name;
+       gchar    *short_field_1;
+       gchar    *long_field_1;
+       gchar    *short_field_2;
+       gchar    *long_field_2;
+       gboolean field_2_in_use;
+} _CoordFormatSetup;
+
 typedef enum
 {
     SPEED_LOCATION_BOTTOM_LEFT,
 typedef enum
 {
     SPEED_LOCATION_BOTTOM_LEFT,
@@ -457,4 +502,460 @@ struct _BrowseInfo {
     GtkWidget *txt;
 };
 
     GtkWidget *txt;
 };
 
+
+#ifdef INCLUDE_APRS
+
+// --------------------------------------------------------------------------------------
+// Start of APRS Types - Code taken from Xastir code 25 March 2008 by Rob Williams
+// Modification made to fit in with Maemo mapper
+// --------------------------------------------------------------------------------------
+
+typedef struct _TWriteBuffer TWriteBuffer;
+struct _TWriteBuffer
+{
+    int    write_in_pos;                          /* current write buffer input pos          */
+    int    write_out_pos;                         /* current write buffer output pos         */
+    GMutex* write_lock;                      /* Lock for writing the port data          */
+    char   device_write_buffer[MAX_DEVICE_BUFFER];/* write buffer for this port              */
+    int    errors;
+};
+
+
+typedef enum
+{
+       TNC_CONNECTION_BT,
+       TNC_CONNECTION_FILE
+} TTncConnection;
+
+typedef enum
+{
+       APRS_PORT_INET,
+       APRS_PORT_TTY,
+       APRS_PORT_COUNT
+} TAprsPort;
+
+// We should probably be using APRS_DF in extract_bearing_NRQ()
+// and extract_omnidf() functions.  We aren't currently.
+/* Define APRS Types */
+enum APRS_Types {
+    APRS_NULL,
+    APRS_MSGCAP,
+    APRS_FIXED,
+    APRS_DOWN,      // Not used anymore
+    APRS_MOBILE,
+    APRS_DF,
+    APRS_OBJECT,
+    APRS_ITEM,
+    APRS_STATUS,
+    APRS_WX1,
+    APRS_WX2,
+    APRS_WX3,
+    APRS_WX4,
+    APRS_WX5,
+    APRS_WX6,
+    QM_WX,
+    PEET_COMPLETE,
+    RSWX200,
+    GPS_RMC,
+    GPS_GGA,
+    GPS_GLL,
+    STATION_CALL_DATA,
+    OTHER_DATA,
+    APRS_MICE,
+    APRS_GRID,
+    DALLAS_ONE_WIRE,
+    DAVISMETEO
+};
+
+
+/* Define Record Types */
+#define NORMAL_APRS     'N'
+#define MOBILE_APRS     'M'
+#define DF_APRS         'D'
+#define DOWN_APRS       'Q'
+#define NORMAL_GPS_RMC  'C'
+#define NORMAL_GPS_GGA  'A'
+#define NORMAL_GPS_GLL  'L'
+
+/* define RECORD ACTIVES */
+#define RECORD_ACTIVE    'A'
+#define RECORD_NOTACTIVE 'N'
+#define RECORD_CLOSED     'C'
+
+/* define data from info type */
+#define DATA_VIA_LOCAL 'L'
+#define DATA_VIA_TNC   'T'
+#define DATA_VIA_NET   'I'
+#define DATA_VIA_FILE  'F'
+
+
+/* define Heard info type */
+#define VIA_TNC         'Y'
+#define NOT_VIA_TNC     'N'
+
+/* define Message types */
+#define MESSAGE_MESSAGE  'M'
+#define MESSAGE_BULLETIN 'B'
+#define MESSAGE_NWS      'W'
+
+
+// trail flag definitions
+#define MAX_CALLSIGN 9
+#define MAX_TIME             20
+#define MAX_LONG             12
+#define MAX_LAT              11
+#define MAX_ALTITUDE         10         //-32808.4 to 300000.0? feet
+#define MAX_SPEED             9         /* ?? 3 in knots */
+#define MAX_COURSE            7         /* ?? */
+#define MAX_POWERGAIN         7
+#define MAX_STATION_TIME     10         /* 6+1 */
+#define MAX_SAT               4
+#define MAX_DISTANCE         10
+#define MAX_WXSTATION        50
+#define MAX_TEMP            100
+#define MAX_MULTIPOINTS 35
+
+#define MAX_DEVICE_BUFFER 4096
+
+/* define max size of info field */
+#define MAX_INFO_FIELD_SIZE 256
+
+// Number of times to send killed objects/items before ceasing to
+// transmit them.
+#define MAX_KILLED_OBJECT_RETRANSMIT 20
+
+// Check entire station list at this rate for objects/items that
+// might need to be transmitted via the decaying algorithm.  This is
+// the start rate, which gets doubled on each transmit.
+#define OBJECT_CHECK_RATE 20
+
+
+#define MAX_MESSAGE_LENGTH  100
+#define MAX_MESSAGE_ORDER    10
+
+
+#define CHECKMALLOC(m)  if (!m) { fprintf(stderr, "***** Malloc Failed *****\n"); exit(0); }
+
+
+#define STATION_REMOVE_CYCLE 300    /* check station remove in seconds (every 5 minutes) */
+#define MESSAGE_REMOVE_CYCLE 600    /* check message remove in seconds (every 10 minutes) */
+#define IN_VIEW_MIN         600l    /* margin for off-screen stations, with possible trails on screen, in minutes */
+#define TRAIL_POINT_MARGIN   30l    /* margin for off-screen trails points, for segment to be drawn, in minutes */
+#define TRAIL_MAX_SPEED      900    /* max. acceptible speed for drawing trails, in mph */
+#define MY_TRAIL_COLOR      0x16    /* trail color index reserved for my station */
+#define TRAIL_ECHO_TIME       30    /* check for delayed echos during last 30 minutes */
+/* MY_TRAIL_DIFF_COLOR changed to user configurable my_trail_diff_color  */
+
+
+typedef struct { 
+    int digis;
+    int wxs;
+    int other_mobiles;
+    int mobiles_in_motion;
+    int homes;
+    int total;
+} aloha_stats;
+
+
+// station flag definitions.  We have 16 bits available here as
+// "flag" in "DataRow" is defined as a short.
+//
+#define ST_OBJECT       0x01    // station is an object
+#define ST_ITEM         0x02    // station is an item
+#define ST_ACTIVE       0x04    // station is active (deleted objects are
+                                // inactive)
+#define ST_MOVING       0x08    // station is moving
+#define ST_DIRECT       0x10    // heard direct (not via digis)
+#define ST_VIATNC       0x20    // station heard via TNC
+#define ST_3RD_PT       0x40    // third party traffic (not used yet)
+#define ST_MSGCAP       0x80    // message capable (not used yet)
+#define ST_STATUS       0x100   // got real status message
+#define ST_INVIEW       0x200   // station is in current screen view
+#define ST_MYSTATION    0x400   // station is owned by my call-SSID
+#define ST_MYOBJITEM    0x800   // object/item owned by me
+
+
+#define TR_LOCAL        0x01    // heard direct (not via digis)
+#define TR_NEWTRK       0x02    // start new track
+
+
+enum AprsAreaObjectTypes {
+    AREA_OPEN_CIRCLE     = 0x0,
+    AREA_LINE_LEFT       = 0x1,
+    AREA_OPEN_ELLIPSE    = 0x2,
+    AREA_OPEN_TRIANGLE   = 0x3,
+    AREA_OPEN_BOX        = 0x4,
+    AREA_FILLED_CIRCLE   = 0x5,
+    AREA_LINE_RIGHT      = 0x6,
+    AREA_FILLED_ELLIPSE  = 0x7,
+    AREA_FILLED_TRIANGLE = 0x8,
+    AREA_FILLED_BOX      = 0x9,
+    AREA_MAX             = 0x9,
+    AREA_NONE            = 0xF
+};
+
+
+
+enum AprsAreaObjectColors {
+    AREA_BLACK_HI  = 0x0,
+    AREA_BLUE_HI   = 0x1,
+    AREA_GREEN_HI  = 0x2,
+    AREA_CYAN_HI   = 0x3,
+    AREA_RED_HI    = 0x4,
+    AREA_VIOLET_HI = 0x5,
+    AREA_YELLOW_HI = 0x6,
+    AREA_GRAY_HI   = 0x7,
+    AREA_BLACK_LO  = 0x8,
+    AREA_BLUE_LO   = 0x9,
+    AREA_GREEN_LO  = 0xA,
+    AREA_CYAN_LO   = 0xB,
+    AREA_RED_LO    = 0xC,
+    AREA_VIOLET_LO = 0xD,
+    AREA_YELLOW_LO = 0xE,
+    AREA_GRAY_LO   = 0xF
+};
+
+
+typedef struct {
+    unsigned type : 4;
+    unsigned color : 4;
+    unsigned sqrt_lat_off : 8;
+    unsigned sqrt_lon_off : 8;
+    unsigned corridor_width : 16;
+} AprsAreaObject;
+
+typedef struct {
+    char aprs_type;
+    char aprs_symbol;
+    char special_overlay;
+    AprsAreaObject area_object;
+} APRS_Symbol;
+
+// Struct for holding track data.  Keeps a dynamically allocated
+// doubly-linked list of track points.  The first record should have its
+// "prev" pointer set to NULL and the last record should have its "next"
+// pointer set to NULL.  If no track storage exists then the pointers to
+// these structs in the DataRow struct should be NULL.
+typedef struct _AprsTrackRow{
+    long    trail_long_pos;     // coordinate of trail point
+    long    trail_lat_pos;      // coordinate of trail point
+    time_t  sec;                // date/time of position
+    long    speed;              // in 0.1 km/h   undefined: -1
+    int     course;             // in degrees    undefined: -1
+    long    altitude;           // in 0.1 m      undefined: -99999
+    char    flag;               // several flags, see below
+    struct  _AprsTrackRow *prev;    // pointer to previous record in list
+    struct  _AprsTrackRow *next;    // pointer to next record in list
+} AprsTrackRow;
+
+
+
+// Struct for holding current weather data.
+// This struct is pointed to by the DataRow structure.
+// An empty string indicates undefined data.
+typedef struct {                //                      strlen
+    time_t  wx_sec_time;
+    int     wx_storm;           // Set to one if severe storm
+    char    wx_time[MAX_TIME];
+    char    wx_course[4];       // in Â°                     3
+    char    wx_speed[4];        // in mph                   3
+    time_t  wx_speed_sec_time;
+    char    wx_gust[4];         // in mph                   3
+    char    wx_hurricane_radius[4];  //nautical miles       3
+    char    wx_trop_storm_radius[4]; //nautical miles       3
+    char    wx_whole_gale_radius[4]; // nautical miles      3
+    char    wx_temp[5];         // in Â°F                    3
+    char    wx_rain[10];        // in hundredths inch/h     3
+    char    wx_rain_total[10];  // in hundredths inch
+    char    wx_snow[6];         // in inches/24h            3
+    char    wx_prec_24[10];     // in hundredths inch/day   3
+    char    wx_prec_00[10];     // in hundredths inch       3
+    char    wx_hum[5];          // in %                     3
+    char    wx_baro[10];        // in hPa                   6
+    char    wx_fuel_temp[5];    // in Â°F                    3
+    char    wx_fuel_moisture[5];// in %                     2
+    char    wx_type;
+    char    wx_station[MAX_WXSTATION];
+} AprsWeatherRow;
+
+
+// Struct for holding comment/status data.  Will keep a dynamically
+// allocated list of text.  Every different comment field will be
+// stored in a separate line.
+typedef struct _AprsCommentRow{
+    char   *text_ptr;           // Ptr to the comment text
+    time_t sec_heard;           // Latest timestamp for this comment/status
+    struct _AprsCommentRow *next;   // Ptr to next record or NULL
+} AprsCommentRow;
+
+
+
+
+// Struct for holding multipoint data.
+typedef struct _AprsMultipointRow{
+    long multipoints[MAX_MULTIPOINTS][2];
+} AprsMultipointRow;
+
+typedef struct _AprsDisplayData
+{
+    gchar *call_sign; // call sign or name index or object/item
+                                    // name
+    gdouble coord_lon;
+    gdouble coord_lat;
+
+    gchar   *coord_lat_lon;
+    gchar   *path;
+    
+    gchar   *comment;
+    gchar   *status;
+} AprsDisplayData;
+
+typedef struct _AprsDataRow {
+
+    struct _AprsDataRow *n_next;    // pointer to next element in name ordered list
+    struct _AprsDataRow *n_prev;    // pointer to previous element in name ordered
+                                // list
+    struct _AprsDataRow *t_newer;   // pointer to next element in time ordered
+                                // list (newer)
+    struct _AprsDataRow *t_older;   // pointer to previous element in time ordered
+                                // list (older)
+
+    
+    char call_sign[MAX_CALLSIGN+1]; // call sign or name index or object/item
+                                    // name
+    char *tactical_call_sign;   // Tactical callsign.  NULL if not assigned
+    APRS_Symbol aprs_symbol;
+    long coord_lon;             // Xastir coordinates 1/100 sec, 0 = 180°W
+    long coord_lat;             // Xastir coordinates 1/100 sec, 0 =  90°N
+
+    int  time_sn;               // serial number for making time index unique
+    time_t sec_heard;           // time last heard, used also for time index
+    time_t heard_via_tnc_last_time;
+    time_t direct_heard;        // KC2ELS - time last heard direct
+
+// Change into time_t structs?  It'd save us a bunch of space.
+    char packet_time[MAX_TIME];
+    char pos_time[MAX_TIME];
+
+    short flag;                 // several flags, see below
+    char pos_amb;               // Position ambiguity, 0 = none,
+                                // 1 = 0.1 minute...
+
+    unsigned int error_ellipse_radius; // Degrades precision for this
+                                // station, from 0 to 65535 cm or
+                                // 655.35 meters.  Assigned when we
+                                // decode each type of packet.
+                                // Default is 6.0 meters (600 cm)
+                                // unless we know the GPS position
+                                // is augmented, or is degraded by
+                                // less precision in the packet.
+
+    unsigned int lat_precision;        // In 100ths of a second latitude
+    unsigned int lon_precision;        // In 100ths of a second longitude
+
+    int trail_color;            // trail color (when assigned)
+    char record_type;
+    //char data_via;              // L local, T TNC, I internet, F file
+
+// Change to char's to save space?
+    int  heard_via_tnc_port;    // Current this will always be 0, but keep for future
+    TAprsPort  last_port_heard;       // Current this will always be 0, but keep for future
+    unsigned int  num_packets;
+    char *node_path_ptr;        // Pointer to path string
+    char altitude[MAX_ALTITUDE]; // in meters (feet gives better resolution ??)
+    char speed[MAX_SPEED+1];    // in knots (same as nautical miles/hour)
+    char course[MAX_COURSE+1];
+    char bearing[MAX_COURSE+1];
+    char NRQ[MAX_COURSE+1];
+    char power_gain[MAX_POWERGAIN+1];   // Holds the phgd values
+    char signal_gain[MAX_POWERGAIN+1];  // Holds the shgd values (for DF'ing)
+
+    AprsWeatherRow *weather_data;   // Pointer to weather data or NULL
+    AprsCommentRow *status_data;    // Ptr to status records or NULL
+    AprsCommentRow *comment_data;   // Ptr to comment records or NULL
+
+    // Below two pointers are NULL if only one position has been received
+    AprsTrackRow *oldest_trackpoint; // Pointer to oldest track point in
+                                 // doubly-linked list
+    AprsTrackRow *newest_trackpoint; // Pointer to newest track point in
+                                 // doubly-linked list
+
+    // When the station is an object, it can include coordinates
+    // of related points. Currently these are being used to draw
+    // outlines of NWS severe weather watches and warnings, and
+    // storm regions. The coordinates are stored here in Xastir
+    // coordinate form. Element [x][0] is the latitude, and 
+    // element [x][1] is the longitude.  --KG4NBB
+    //
+    // Is there anything preventing a multipoint string from being
+    // in other types of packets, in the comment field?  --WE7U
+    //
+    int num_multipoints;
+    char type;      // from '0' to '9'
+    char style;     // from 'a' to 'z'
+    AprsMultipointRow *multipoint_data;
+
+
+///////////////////////////////////////////////////////////////////////
+// Optional stuff for Objects/Items only (I think, needs to be
+// checked).  These could be moved into an ObjectRow structure, with
+// only a NULL pointer here if not an object/item.
+///////////////////////////////////////////////////////////////////////
+    char origin[MAX_CALLSIGN+1]; // call sign originating an object
+    short object_retransmit;     // Number of times to retransmit object.
+                                 // -1 = forever
+                                 // Used currently to stop sending killed
+                                 // objects.
+    time_t last_transmit_time;   // Time we last transmitted an object/item.
+                                 // Used to implement decaying transmit time
+                                 // algorithm
+    short transmit_time_increment; // Seconds to add to transmit next time
+                                   // around.  Used to implement decaying
+                                   // transmit time algorithm
+//    time_t last_modified_time;   // Seconds since the object/item
+                                 // was last modified.  We'll
+                                 // eventually use this for
+                                 // dead-reckoning.
+    char signpost[5+1];          // Holds signpost data
+    int  df_color;
+    char sats_visible[MAX_SAT];
+    char probability_min[10+1];  // Holds prob_min (miles)
+    char probability_max[10+1];  // Holds prob_max (miles)
+
+} AprsDataRow;
+
+typedef enum
+{
+    APRSPOI_SELECTED,
+    APRSPOI_CALLSIGN,
+//    APRSPOI_LAT,
+//    APRSPOI_LON,
+//    APRSPOI_LATLON,
+//    APRSPOI_BEARING,
+//    APRSPOI_DISTANCE,
+//    APRSPOI_PATH,
+//    APRSPOI_COMMENT,
+//    APRSPOI_STATUS,
+    APRSPOI_NUM_COLUMNS
+} APRSPOIList;
+
+
+
+typedef struct _AprsStationList{
+    struct  _AprsStationList *next;    // pointer to next record in list
+    AprsDataRow *station;
+} AprsStationList;
+
+
+
+
+// --------------------------------------------------------------------------------------
+// End of APRS Types - Code taken from Xastir code 25 March 2008 by Rob Williams M1BGT
+// Modification made to fit in with Maemo mapper
+// --------------------------------------------------------------------------------------
+#endif // INCLUDE_APRS
+
+
 #endif /* ifndef MAEMO_MAPPER_TYPES_H */
 #endif /* ifndef MAEMO_MAPPER_TYPES_H */
index e94791496728b3394683b661b43eca8f6bcd21a4..16f04be602860a61e6ac4536b95eefea7140a726 100644 (file)
@@ -71,9 +71,16 @@ deg_format(gdouble coor, gchar *scoor, gchar neg_char, gchar pos_char)
 
     switch(_degformat)
     {
 
     switch(_degformat)
     {
+           case IARU_LOC:
+       case UK_OSGB:
+       case UK_NGR:
+               // These formats should not be formatted in the same way
+               // - they need to be converted first, therefore if we reach 
+               // this bit of code use the first available format - drop through.
         case DDPDDDDD:
             sprintf(scoor, "%.5f°", coor);
             break;
         case DDPDDDDD:
             sprintf(scoor, "%.5f°", coor);
             break;
+
         case DDPDDDDD_NSEW:
             sprintf(scoor, "%.5f° %c", acoor,
                     coor < 0.0 ? neg_char : pos_char);
         case DDPDDDDD_NSEW:
             sprintf(scoor, "%.5f° %c", acoor,
                     coor < 0.0 ? neg_char : pos_char);
@@ -485,6 +492,476 @@ strdmstod(const gchar *nptr, gchar **endptr)
     return s * d;
 }
 
     return s * d;
 }
 
+double marc(double bf0, double n, double phi0, double phi)
+{
+       return bf0 * (((1 + n + ((5 / 4) * (n * n)) + ((5 / 4) * (n * n * n))) * (phi - phi0))
+           - (((3 * n) + (3 * (n * n)) + ((21 / 8) * (n * n * n))) * (sin(phi - phi0)) * (cos(phi + phi0)))
+           + ((((15 / 8) * (n * n)) + ((15 / 8) * (n * n * n))) * (sin(2 * (phi - phi0))) * (cos(2 * (phi + phi0))))
+           - (((35 / 24) * (n * n * n)) * (sin(3 * (phi - phi0))) * (cos(3 * (phi + phi0)))));
+}
+
+gboolean os_grid_check_lat_lon(double lat, double lon)
+{
+       // TODO - Check exact OS Grid range
+       if(lat < 50.0 || lat > 62 || lon < -7.5 || lon > 2.2 )
+       {
+               return FALSE;
+       }
+       else
+       {
+               return TRUE;
+       }
+}
+
+gboolean coord_system_check_lat_lon (gdouble lat, gdouble lon, gint *fallback_deg_format)
+{
+       // Is the current coordinate system applicable to the provided lat and lon?
+       gboolean valid = FALSE;
+       
+       switch(_degformat)
+       {
+       case UK_OSGB:
+       case UK_NGR:
+               valid = os_grid_check_lat_lon(lat, lon);
+               if(fallback_deg_format != NULL) *fallback_deg_format = DDPDDDDD; 
+               break;
+       default:
+               valid = TRUE;
+               break;
+       }
+       
+       return valid;
+}
+
+gboolean convert_lat_long_to_os_grid(double lat, double lon, int *easting, int *northing)
+{
+       if(!os_grid_check_lat_lon(lat, lon))
+       {
+               return FALSE;
+       }
+       
+       const double deg2rad = (2 * PI / 360);
+       
+       const double phi = lat * deg2rad;          // convert latitude to radians
+       const double lam = lon * deg2rad;          // convert longitude to radians
+       const double a = 6377563.396;              // OSGB semi-major axis
+       const double b = 6356256.91;               // OSGB semi-minor axis
+       const double e0 = 400000;                  // easting of false origin
+       const double n0 = -100000;                 // northing of false origin
+       const double f0 = 0.9996012717;            // OSGB scale factor on central meridian
+       const double e2 = 0.0066705397616;         // OSGB eccentricity squared
+       const double lam0 = -0.034906585039886591; // OSGB false east
+       const double phi0 = 0.85521133347722145;   // OSGB false north
+       const double af0 = a * f0;
+       const double bf0 = b * f0;
+
+       // easting
+       double slat2 = sin(phi) * sin(phi);
+       double nu = af0 / (sqrt(1 - (e2 * (slat2))));
+       double rho = (nu * (1 - e2)) / (1 - (e2 * slat2));
+       double eta2 = (nu / rho) - 1;
+       double p = lam - lam0;
+       double IV = nu * cos(phi);
+       double clat3 = pow(cos(phi), 3);
+       double tlat2 = tan(phi) * tan(phi);
+       double V = (nu / 6) * clat3 * ((nu / rho) - tlat2);
+       double clat5 = pow(cos(phi), 5);
+       double tlat4 = pow(tan(phi), 4);
+       double VI = (nu / 120) * clat5 * ((5 - (18 * tlat2)) + tlat4 + (14 * eta2) - (58 * tlat2 * eta2));
+       double east = e0 + (p * IV) + (pow(p, 3) * V) + (pow(p, 5) * VI);
+               
+       // northing
+       double n = (af0 - bf0) / (af0 + bf0);
+       double M = marc(bf0, n, phi0, phi);
+       double I = M + (n0);
+       double II = (nu / 2) * sin(phi) * cos(phi);
+       double III = ((nu / 24) * sin(phi) * pow(cos(phi), 3)) * (5 - pow(tan(phi), 2) + (9 * eta2));
+       double IIIA = ((nu / 720) * sin(phi) * clat5) * (61 - (58 * tlat2) + tlat4);
+       double north = I + ((p * p) * II) + (pow(p, 4) * III) + (pow(p, 6) * IIIA);
+
+        // make whole number values
+        *easting = round(east);   // round to whole number
+        *northing = round(north); // round to whole number
+
+        return TRUE;
+}
+
+gboolean convert_os_grid_to_bng(gint easting, gint northing, gchar* bng)
+{
+       gdouble eX = (gdouble)easting / 500000.0;
+       gdouble nX = (gdouble)northing / 500000.0;
+       gdouble tmp = floor(eX) - 5.0 * floor(nX) + 17.0;
+       gchar eing[12];
+       gchar ning[12];
+       
+       nX = 5.0 * (nX - floor(nX));
+       eX = 20.0 - 5.0 * floor(nX) + floor(5.0 * (eX - floor(eX)));
+       if (eX > 7.5) eX = eX + 1;    // I is not used
+       if (tmp > 7.5) tmp = tmp + 1; // I is not used
+       
+       snprintf(eing, 12, "%u", easting);
+       snprintf(ning, 12, "%u", northing);
+       
+       gint lnth = strlen(eing);
+       // Note - we only want 4 numbers
+       snprintf(eing, 5, "%s", eing+(lnth-5));
+       
+       lnth = strlen(ning);
+       snprintf(ning, 5, "%s", ning+(lnth-5));
+       
+       sprintf(bng, "%c%c%s%s", 
+                       (char)(tmp + 65), 
+                       (char)(eX + 65),
+                       eing, ning
+               );
+               
+       return TRUE;
+}
+
+gboolean convert_os_xy_to_latlon(const gchar *easting, const gchar *northing, gdouble *d_lat, gdouble *d_lon)
+{
+    gint64 i64_n = g_ascii_strtoll (northing, NULL, 10);
+    gint64 i64_e = g_ascii_strtoll (easting, NULL, 10);
+    
+    const double deg2rad = (2 * PI / 360);
+    
+       const gdouble N =  (gdouble)i64_n;
+       const gdouble E = (gdouble)i64_e;
+
+       const gdouble a = 6377563.396, b = 6356256.910;              // Airy 1830 major & minor semi-axes
+       const gdouble F0 = 0.9996012717;                             // NatGrid scale factor on central meridian
+       const gdouble lat0 = 49*PI/180, lon0 = -2*PI/180;  // NatGrid true origin
+       const gdouble N0 = -100000, E0 = 400000;                     // northing & easting of true origin, metres
+       const gdouble e2 = 1 - (b*b)/(a*a);                          // eccentricity squared
+       const gdouble n = (a-b)/(a+b), n2 = n*n, n3 = n*n*n;
+
+       gdouble lat=lat0, M=0;
+       do {
+               lat = (N-N0-M)/(a*F0) + lat;
+
+               const gdouble Ma = (1 + n + (5/4)*n2 + (5/4)*n3) * (lat-lat0);
+               const gdouble Mb = (3*n + 3*n*n + (21/8)*n3) * sin(lat-lat0) * cos(lat+lat0);
+               const gdouble Mc = ((15/8)*n2 + (15/8)*n3) * sin(2*(lat-lat0)) * cos(2*(lat+lat0));
+               const gdouble Md = (35/24)*n3 * sin(3*(lat-lat0)) * cos(3*(lat+lat0));
+           M = b * F0 * (Ma - Mb + Mc - Md);                // meridional arc
+
+       } while (N-N0-M >= 0.00001);  // ie until < 0.01mm
+
+       const gdouble cosLat = cos(lat), sinLat = sin(lat);
+       const gdouble nu = a*F0/sqrt(1-e2*sinLat*sinLat);              // transverse radius of curvature
+       const gdouble rho = a*F0*(1-e2)/pow(1-e2*sinLat*sinLat, 1.5);  // meridional radius of curvature
+       const gdouble eta2 = nu/rho-1;
+
+       const gdouble tanLat = tan(lat);
+       const gdouble tan2lat = tanLat*tanLat, tan4lat = tan2lat*tan2lat, tan6lat = tan4lat*tan2lat;
+       const gdouble secLat = 1/cosLat;
+       const gdouble nu3 = nu*nu*nu, nu5 = nu3*nu*nu, nu7 = nu5*nu*nu;
+       const gdouble VII = tanLat/(2*rho*nu);
+       const gdouble VIII = tanLat/(24*rho*nu3)*(5+3*tan2lat+eta2-9*tan2lat*eta2);
+       const gdouble IX = tanLat/(720*rho*nu5)*(61+90*tan2lat+45*tan4lat);
+       const gdouble X = secLat/nu;
+       const gdouble XI = secLat/(6*nu3)*(nu/rho+2*tan2lat);
+       const gdouble XII = secLat/(120*nu5)*(5+28*tan2lat+24*tan4lat);
+       const gdouble XIIA = secLat/(5040*nu7)*(61+662*tan2lat+1320*tan4lat+720*tan6lat);
+
+       const gdouble dE = (E-E0), dE2 = dE*dE, dE3 = dE2*dE, dE4 = dE2*dE2, dE5 = dE3*dE2;
+       const gdouble dE6 = dE4*dE2, dE7 = dE5*dE2;
+       lat = lat - VII*dE2 + VIII*dE4 - IX*dE6;
+       const gdouble lon = lon0 + X*dE - XI*dE3 + XII*dE5 - XIIA*dE7;
+       
+       *d_lon = lon / deg2rad;
+       *d_lat = lat / deg2rad;
+       
+       return TRUE;
+}
+
+gboolean convert_os_ngr_to_latlon(const gchar *text, gdouble *d_lat, gdouble *d_lon)
+{
+       // get numeric values of letter references, mapping A->0, B->1, C->2, etc:
+       gint l1;
+       gint l2;
+
+       gchar s_e[6], s_n[6];
+       gchar easting[7], northing[7];
+       gint64 i64_e = 0;
+       gint64 i64_n = 0;
+       
+       
+       if( ((gchar)text[0])>='a' && ((gchar)text[0]) <= 'z' ) 
+               l1 = text[0] - (gint)'a'; // lower case
+       else if( ((gchar)text[0])>='A' && ((gchar)text[0]) <= 'Z' )
+               l1 = text[0] - (gint)'A'; // upper case
+       else
+               return FALSE; // Not a letter - invalid grid ref
+       
+       if( ((gchar)text[1])>='a' && ((gchar)text[1]) <= 'z' ) 
+               l2 = text[1] - (gint)'a'; // lower case
+       else if( ((gchar)text[1])>='A' && ((gchar)text[1]) <= 'Z' )
+               l2 = text[1] - (gint)'A'; // upper case
+       else
+               return FALSE; // Not a letter - invalid grid ref
+
+       
+       // shuffle down letters after 'I' since 'I' is not used in grid:
+       if (l1 > 7) l1--;
+       if (l2 > 7) l2--;
+
+       // convert grid letters into 100km-square indexes from false origin (grid square SV):
+       gdouble e = ((l1-2)%5)*5 + (l2%5);
+       gdouble n = (19-floor(l1/5)*5) - floor(l2/5);
+
+       // skip grid letters to get numeric part of ref, stripping any spaces:
+       gchar *gridref = (gchar*)(text+2); 
+       
+       // user may have entered a space, so remove any spaces
+       while(gridref[0] == ' ')
+       {
+               gridref = (gchar*)(text+1);
+       }
+
+       
+       // floor the length incase a space has been added
+       const gint len = (gint)floor((gdouble)strlen(gridref)/2.00); // normally this will be 4, often 3
+       if(len>5) return FALSE; 
+       
+       if(len >0)
+       {
+               snprintf(s_e, len+1, "%s", gridref);
+               
+               while( (gchar)((gint)gridref+len) == ' ' ) 
+                       gridref = (gchar*)(gridref+1); // Allow for a space
+               
+               snprintf(s_n, len+1, "%s", gridref+len);
+               
+               i64_e = g_ascii_strtoll (s_e, NULL, 10);
+               i64_n = g_ascii_strtoll (s_n, NULL, 10);
+               
+               // Move to most significate values
+               i64_e *= pow(10, 5-len);
+               i64_n *= pow(10, 5-len);
+       }
+       
+       // append numeric part of references to grid index:
+       e = (e*100000) + (gdouble)i64_e;
+       n = (n*100000) + (gdouble)i64_n;
+
+       snprintf(easting, 7, "%06u", (gint)e);
+       snprintf(northing, 7, "%06u", (gint)n);
+       
+       convert_os_xy_to_latlon(easting, northing, d_lat, d_lon);
+       
+       return TRUE;
+}
+
+
+// Attempt to convert any user entered grid reference to a double lat/lon
+// return TRUE on valid
+gboolean parse_coords(const gchar* txt_lat, const gchar* txt_lon, gdouble* lat, gdouble* lon)
+{
+       gboolean valid = FALSE;
+       
+       // UK_NGR starts with two letters, and then all numbers - it may contain spaces - no lon will be entered
+       if( _degformat == UK_NGR)
+       {
+               valid = convert_os_ngr_to_latlon(txt_lat, lat, lon);
+
+        if(!valid || *lat < -90. || *lat > 90.) { valid = FALSE; }
+        else   if(*lon < -180. || *lon > 180.)  { valid = FALSE; }
+       }
+       // UK_OSGB contains two 6 digit integers
+       else if( _degformat == UK_OSGB)
+       {
+               valid = convert_os_xy_to_latlon(txt_lat, txt_lon, lat, lon);
+               
+        if(!valid || *lat < -90. || *lat > 90.) { valid = FALSE; }
+        else   if(*lon < -180. || *lon > 180.)  { valid = FALSE; }
+
+       }
+       else if( _degformat == IARU_LOC)
+       {
+               valid = convert_iaru_loc_to_lat_lon(txt_lat, lat, lon);
+
+        if(!valid || *lat < -90. || *lat > 90.) { valid = FALSE; }
+        else   if(*lon < -180. || *lon > 180.)  { valid = FALSE; }
+       }
+       // It must either be invalid, or a lat/lon format
+       else
+       {
+               gchar* error_check;
+               *lat = strdmstod(txt_lat, &error_check);
+               
+        if(txt_lat == error_check || *lat < -90. || *lat > 90.) { valid = FALSE; }
+        else { valid = TRUE; }
+
+        if(valid == TRUE)
+        {
+               *lon = strdmstod(txt_lon, &error_check);
+               
+            if(txt_lon == error_check || *lon < -180. || *lon > 180.)  { valid = FALSE; }
+        }
+       }
+       
+       
+       
+       return valid;
+}
+
+gboolean convert_iaru_loc_to_lat_lon(const gchar* txt_lon, gdouble* lat, gdouble* lon)
+{
+       gint u_first = 0;
+       gint u_second = 0;
+       gint u_third = 0;
+       gint u_fourth = 0;
+       gint u_fifth = 0;
+       gint u_sixth = 0;
+       gint u_seventh = 0;
+       gint u_eighth = 0;
+       
+       if(strlen(txt_lon) >= 1)
+       {
+               if( ((gchar)txt_lon[0])>='a' && ((gchar)txt_lon[0]) <= 'z' ) 
+                       u_first = txt_lon[0] - (gint)'a'; // lower case
+               else if( ((gchar)txt_lon[0])>='A' && ((gchar)txt_lon[0]) <= 'Z' )
+                       u_first = txt_lon[0] - (gint)'A'; // upper case
+       }
+       
+       if(strlen(txt_lon) >= 2)
+       {
+               if( ((gchar)txt_lon[1])>='a' && ((gchar)txt_lon[1]) <= 'z' ) 
+                       u_second = txt_lon[1] - (gint)'a'; // lower case
+               else if( ((gchar)txt_lon[1])>='A' && ((gchar)txt_lon[1]) <= 'Z' )
+                       u_second = txt_lon[1] - (gint)'A'; // upper case
+       }
+       
+       if(strlen(txt_lon) >= 3)
+               u_third = txt_lon[2] - (gint)'0';
+       
+       if(strlen(txt_lon) >= 4)
+               u_fourth = txt_lon[3] - (gint)'0';
+
+       if(strlen(txt_lon) >= 5)
+       {
+               if( ((gchar)txt_lon[4])>='a' && ((gchar)txt_lon[4]) <= 'z' ) 
+                       u_fifth = txt_lon[4] - (gint)'a'; // lower case
+               else if( ((gchar)txt_lon[4])>='A' && ((gchar)txt_lon[4]) <= 'Z' )
+                       u_fifth = txt_lon[4] - (gint)'A'; // upper case
+       }
+
+       if(strlen(txt_lon) >= 6)
+       {
+               if( ((gchar)txt_lon[5])>='a' && ((gchar)txt_lon[5]) <= 'z' ) 
+                       u_sixth = txt_lon[5] - (gint)'a'; // lower case
+               else if( ((gchar)txt_lon[5])>='A' && ((gchar)txt_lon[5]) <= 'Z' )
+                       u_sixth = txt_lon[5] - (gint)'A'; // upper case
+       }
+       
+
+       if(strlen(txt_lon) >= 7)
+               u_seventh= txt_lon[6] - (gint)'0';
+
+       if(strlen(txt_lon) >= 8)
+               u_eighth = txt_lon[7] - (gint)'0';
+       
+       *lat = ((gdouble)u_first * 20.0) + ((gdouble)u_third * 2.0) + ((gdouble)u_fifth * (2.0/24.0)) + ((gdouble)u_seventh * (2.0/240.0)) - 90.0;
+       *lon = ((gdouble)u_second * 10.0) + ((gdouble)u_fourth) + ((gdouble)u_sixth * (1.0/24.0)) + ((gdouble)u_eighth * (1.0/240.0)) - 180.0;
+       
+       return TRUE;
+}
+
+void convert_lat_lon_to_iaru_loc(gdouble d_lat, gdouble d_lon, gchar *loc)
+{
+       const gdouble d_a_lat = (d_lat+90.0);
+       const gdouble d_a_lon = (d_lon+180.0);
+               
+       const gint i_first  = (gint)floor(d_a_lon/20.0);
+       const gint i_second = (gint)floor(d_a_lat/10.0);
+       const gint i_third  = (gint)floor((d_a_lon - (20.0*i_first))/2.0);
+       const gint i_fourth = (gint)floor((d_a_lat - (10.0*i_second)));
+       const gint i_fifth  = (gint)floor((d_a_lon - (20.0*i_first) - (2.0*i_third))/(5.0/60.0));
+       const gint i_sixth  = (gint)floor((d_a_lat - (10.0*i_second) - (i_fourth))/(2.5/60.0));
+       
+       const gint i_seventh  = (gint)floor((d_a_lon - (20.0*i_first) - (2.0*i_third) 
+                       - ((2.0/24.0)*i_fifth))/(2.0/240.0)  );
+       const gint i_eighth = (gint)floor((d_a_lat - (10.0*i_second) - (i_fourth) 
+                       - ((1.0/24.0)*i_sixth))/(1.0/240.0));
+       
+       
+       sprintf(loc, "%c%c%u%u%c%c%u%u",
+                       'A'+i_first,
+                       'A'+i_second,
+                       i_third,
+                       i_fourth,
+                       'a' + i_fifth,
+                       'a' + i_sixth,
+                       i_seventh,
+                       i_eighth);
+}
+
+gboolean convert_lat_lon_to_bng(gdouble lat, gdouble lon, gchar* bng)
+{
+       gint easting, northing;
+
+       if( convert_lat_long_to_os_grid(lat, lon, &easting, &northing) )
+       {
+               if( convert_os_grid_to_bng(easting, northing, bng) )
+               {
+                       return TRUE;
+               }
+       }
+       
+       return FALSE;
+}
+
+
+void format_lat_lon(gdouble d_lat, gdouble d_lon, gchar* lat, gchar* lon)
+{
+       gint east = 0;
+       gint north = 0;
+
+       switch (_degformat)
+       {
+       case UK_OSGB:
+               
+               if(convert_lat_long_to_os_grid(d_lat, d_lon, &east, &north))
+               {
+                       sprintf(lat, "%06d", east);
+                       sprintf(lon, "%06d", north);
+               }
+               else
+               {
+                       // Failed (possibly out of range), so use defaults
+                       lat_format(d_lat, lat);
+                       lon_format(d_lon, lon);                         
+               }
+               break;
+       case UK_NGR:
+               
+               if(convert_lat_lon_to_bng(d_lat, d_lon, lat))
+               {
+                       lon[0] = 0;
+               }
+               else
+               {
+                       // Failed (possibly out of range), so use defaults
+                       lat_format(d_lat, lat);
+                       lat_format(d_lon, lon);                         
+               }
+               break;
+               
+       case IARU_LOC:
+               convert_lat_lon_to_iaru_loc(d_lat, d_lon, lat);
+
+               break;
+               
+       default:
+               lat_format(d_lat, lat);
+               lon_format(d_lon, lon);
+
+               break;
+       }
+}
+
 #if 0
 struct t_case {
     gchar *fmt;
 #if 0
 struct t_case {
     gchar *fmt;
index c4c2c7c1a68e5712f4ea00415778b2c0fec0d46a..18d06252a84c4da0e2d462582724073478ceaa22 100644 (file)
@@ -51,4 +51,9 @@ void deg_format(gdouble coor, gchar *scoor, gchar neg_char, gchar pos_char);
 
 gdouble strdmstod(const gchar *nptr, gchar **endptr);
 
 
 gdouble strdmstod(const gchar *nptr, gchar **endptr);
 
+gboolean parse_coords(const gchar* txt_lat, const gchar* txt_lon, gdouble* lat, gdouble* lon);
+void format_lat_lon(gdouble d_lat, gdouble d_lon, gchar* lat, gchar* lon);
+
+gboolean coord_system_check_lat_lon (gdouble lat, gdouble lon, gint *fallback_deg_format);
+
 #endif /* ifndef MAEMO_MAPPER_UTIL_H */
 #endif /* ifndef MAEMO_MAPPER_UTIL_H */