From bcb06247860e0b3650dceeb4840bf328e4e22045 Mon Sep 17 00:00:00 2001
From: W1CDN <matthew.burtonkelly@gmail.com>
Date: Mon, 15 Jan 2024 10:17:02 -0600
Subject: [PATCH 1/6] Stub out map.

---
 api_app.py               |  4 ++++
 aprs_tool.code-workspace |  7 +++++++
 templates/map.html       | 23 +++++++++++++++++++++++
 3 files changed, 34 insertions(+)
 create mode 100644 aprs_tool.code-workspace
 create mode 100644 templates/map.html

diff --git a/api_app.py b/api_app.py
index f9adff1..4769f8d 100644
--- a/api_app.py
+++ b/api_app.py
@@ -120,6 +120,10 @@ def index():
                             frames = frames,
                             stations = stations)
 
+@api_app.route('/map')
+def map():
+    return render_template('map.html')
+
 class Packets(Resource):
     def get(self):
         # Handle arguments that may or may not exist
diff --git a/aprs_tool.code-workspace b/aprs_tool.code-workspace
new file mode 100644
index 0000000..362d7c2
--- /dev/null
+++ b/aprs_tool.code-workspace
@@ -0,0 +1,7 @@
+{
+	"folders": [
+		{
+			"path": "."
+		}
+	]
+}
\ No newline at end of file
diff --git a/templates/map.html b/templates/map.html
new file mode 100644
index 0000000..824ddbd
--- /dev/null
+++ b/templates/map.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+    <!-- Leaflet's CSS -->
+    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
+     integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
+     crossorigin=""/>
+     <!-- Make sure you put this AFTER Leaflet's CSS -->
+    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
+    integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
+    crossorigin=""></script>
+    <style>
+        #map { height: 100%; }
+    </style>
+</head>
+<body>
+    <div id="map"></div>
+
+    <script>
+        var map = L.map('map').setView([51.505, -0.09], 13);
+        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {attribution: '&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors'}).addTo(map);
+    </script>
+</body>
+</html>
\ No newline at end of file

From 5fb589507e8446e277faf7e205f2ef4e1baf2989 Mon Sep 17 00:00:00 2001
From: W1CDN <matthew.burtonkelly@gmail.com>
Date: Mon, 15 Jan 2024 11:00:56 -0600
Subject: [PATCH 2/6] Add markers to map.

---
 api_app.py         | 26 +++++++++++++++++++++++++-
 templates/map.html |  5 ++++-
 2 files changed, 29 insertions(+), 2 deletions(-)

diff --git a/api_app.py b/api_app.py
index 4769f8d..a0ca74c 100644
--- a/api_app.py
+++ b/api_app.py
@@ -122,7 +122,31 @@ def index():
 
 @api_app.route('/map')
 def map():
-    return render_template('map.html')
+
+    # Get the default list of frames from the API
+    frames = json.loads(requests.get(config['Settings']['base_url']+"/packets").text)['data']
+
+    # Make markers for all the frames
+    id_counter = 0
+    markers = ''
+    for frame in frames:
+        if frame['latitude'] != None:
+            # Create unique ID for each marker
+            idd = 'frame' + str(id_counter)
+            id_counter += 1
+
+            # Create the marker and its pop-up for each shop
+            markers += "var {idd} = L.marker([{latitude}, {longitude}]);\
+                        {idd}.addTo(map).bindPopup('{from_ssid}');".format(idd=idd, latitude=frame['latitude'],\
+                                                                                    longitude=frame['longitude'],
+                                                                                    from_ssid=frame['from'],
+                                                                                    created=frame['created'])
+
+
+    return render_template('map.html',
+                           station_lat = config['Settings']['station_lat'],
+                           station_lon = config['Settings']['station_lon'],
+                           markers = markers)
 
 class Packets(Resource):
     def get(self):
diff --git a/templates/map.html b/templates/map.html
index 824ddbd..87fe6d2 100644
--- a/templates/map.html
+++ b/templates/map.html
@@ -16,8 +16,11 @@
     <div id="map"></div>
 
     <script>
-        var map = L.map('map').setView([51.505, -0.09], 13);
+        var map = L.map('map').setView([{{station_lat}}, {{station_lon}}], 10);
         L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {attribution: '&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors'}).addTo(map);
+
+        {{markers|safe}}
+
     </script>
 </body>
 </html>
\ No newline at end of file

From 260e946ab65a370fb82787da00c14dc64fca6bb1 Mon Sep 17 00:00:00 2001
From: W1CDN <matthew.burtonkelly@gmail.com>
Date: Mon, 15 Jan 2024 11:52:30 -0600
Subject: [PATCH 3/6] Fool around with marker groups.

---
 api_app.py | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/api_app.py b/api_app.py
index a0ca74c..ed483d5 100644
--- a/api_app.py
+++ b/api_app.py
@@ -129,24 +129,28 @@ def map():
     # Make markers for all the frames
     id_counter = 0
     markers = ''
+    marker_ids = []
     for frame in frames:
         if frame['latitude'] != None:
             # Create unique ID for each marker
             idd = 'frame' + str(id_counter)
             id_counter += 1
 
-            # Create the marker and its pop-up for each shop
+            # Create each marker
             markers += "var {idd} = L.marker([{latitude}, {longitude}]);\
-                        {idd}.addTo(map).bindPopup('{from_ssid}');".format(idd=idd, latitude=frame['latitude'],\
+                        {idd}.addTo(map).bindTooltip('{from_ssid}', permanent=true).openTooltip();".format(idd=idd, latitude=frame['latitude'],\
                                                                                     longitude=frame['longitude'],
                                                                                     from_ssid=frame['from'],
                                                                                     created=frame['created'])
+            # Try to make a list of markers for Leaflet, but not working
+            marker_ids.append(idd)
 
 
     return render_template('map.html',
                            station_lat = config['Settings']['station_lat'],
                            station_lon = config['Settings']['station_lon'],
-                           markers = markers)
+                           markers = markers,
+                           marker_ids = marker_ids)
 
 class Packets(Resource):
     def get(self):

From 3758ac21cb8d3a4de6428a3ee9f1735bd9a591aa Mon Sep 17 00:00:00 2001
From: W1CDN <matthew.burtonkelly@gmail.com>
Date: Mon, 15 Jan 2024 17:04:13 -0600
Subject: [PATCH 4/6] Plot recent packets on map.

---
 .gitignore           |  2 ++
 api_app.py           | 78 ++++++++++++++++++++++++++++++++------------
 templates/index.html | 50 +++++++++++++++++++++++++---
 templates/map.html   | 18 +++++++++-
 test_db.py           | 36 --------------------
 5 files changed, 123 insertions(+), 61 deletions(-)
 delete mode 100644 test_db.py

diff --git a/.gitignore b/.gitignore
index ab303c9..e121693 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,5 @@
 config.ini
 *.db
 *.log
+/temp/*
+*.pyc
diff --git a/api_app.py b/api_app.py
index ed483d5..fb35a98 100644
--- a/api_app.py
+++ b/api_app.py
@@ -113,12 +113,31 @@ def index():
             station['time_ago'] = timeago.format(station['last_heard_unix'], datetime.datetime.now())
 
 
+    # Map stuff
+    frames_locs = list(filter(lambda x: x['latitude'] != None, frames))
+    # Make a GeoJSON
+    geojs = json.dumps({
+     "type": "FeatureCollection",
+     "features":[
+           {
+                "type":"Feature",
+                "geometry": {
+                "type":"Point",
+                "coordinates":[frame['longitude'], frame['latitude']],
+            },
+                "properties":frame,
+        
+         } for frame in frames_locs
+    ]  
+    })
+
     return render_template('index.html',
                             station_call = config['Settings']['station_call'],
                             station_lat = config['Settings']['station_lat'],
                             station_lon = config['Settings']['station_lon'],
                             frames = frames,
-                            stations = stations)
+                            stations = stations,
+                            geojs = geojs)
 
 @api_app.route('/map')
 def map():
@@ -126,31 +145,50 @@ def map():
     # Get the default list of frames from the API
     frames = json.loads(requests.get(config['Settings']['base_url']+"/packets").text)['data']
 
-    # Make markers for all the frames
-    id_counter = 0
-    markers = ''
-    marker_ids = []
-    for frame in frames:
-        if frame['latitude'] != None:
-            # Create unique ID for each marker
-            idd = 'frame' + str(id_counter)
-            id_counter += 1
+    frames_locs = list(filter(lambda x: x['latitude'] != None, frames))
 
-            # Create each marker
-            markers += "var {idd} = L.marker([{latitude}, {longitude}]);\
-                        {idd}.addTo(map).bindTooltip('{from_ssid}', permanent=true).openTooltip();".format(idd=idd, latitude=frame['latitude'],\
-                                                                                    longitude=frame['longitude'],
-                                                                                    from_ssid=frame['from'],
-                                                                                    created=frame['created'])
-            # Try to make a list of markers for Leaflet, but not working
-            marker_ids.append(idd)
+    # Make a GeoJSON
+    geojs = json.dumps({
+     "type": "FeatureCollection",
+     "features":[
+           {
+                "type":"Feature",
+                "geometry": {
+                "type":"Point",
+                "coordinates":[frame['longitude'], frame['latitude']],
+            },
+                "properties":frame,
+        
+         } for frame in frames_locs
+    ]  
+    })
+
+    # Make markers for all the frames
+    # id_counter = 0
+    # markers = ''
+    # marker_ids = []
+    # for frame in frames:
+    #     if frame['latitude'] != None:
+    #         # Create unique ID for each marker
+    #         idd = 'frame' + str(id_counter)
+    #         id_counter += 1
+
+    #         # Create each marker
+    #         markers += "var {idd} = L.marker([{latitude}, {longitude}]);\
+    #                     {idd}.addTo(map).bindTooltip('{from_ssid}', permanent=true).openTooltip();".format(idd=idd, latitude=frame['latitude'],\
+    #                                                                                 longitude=frame['longitude'],
+    #                                                                                 from_ssid=frame['from'],
+    #                                                                                 created=frame['created'])
+    #         # Try to make a list of markers for Leaflet, but not working
+    #         marker_ids.append(idd)
 
 
     return render_template('map.html',
                            station_lat = config['Settings']['station_lat'],
                            station_lon = config['Settings']['station_lon'],
-                           markers = markers,
-                           marker_ids = marker_ids)
+                           station_call = config['Settings']['station_call'],
+                           #markers = markers,
+                           geojs = geojs)
 
 class Packets(Resource):
     def get(self):
diff --git a/templates/index.html b/templates/index.html
index 3b864fd..493a1e2 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -4,18 +4,58 @@
     <meta charset="UTF-8">
     <title>{{station_call}} Status</title>
 
+  <!-- Leaflet's CSS -->
+  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
+  integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
+  crossorigin=""/>
+  <!-- Make sure you put this AFTER Leaflet's CSS -->
+  <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
+  integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
+  crossorigin=""></script>
+
     <style>
       table, th, td {
         border: 1px solid black;
       }
+      #map { height: 250px; }
     </style>
 </head>
 <body>
-<h1>{{station_call}} Status</h1>
-Station location: {{station_lat}}, {{station_lon}}
+<div style="width: 100%; overflow: hidden;">
+    <div style="width: 50%; float: left;">
 
-<h2> About </h2>
-This is a work in progress. See <a href="https://amiok.net/gitea/W1CDN/aprs_tool">https://amiok.net/gitea/W1CDN/aprs_tool</a> for usage.
+      <h1>{{station_call}} Status</h1>
+      Station location: {{station_lat}}, {{station_lon}}
+
+      <h2> About </h2>
+      This is a work in progress. See <a href="https://amiok.net/gitea/W1CDN/aprs_tool">https://amiok.net/gitea/W1CDN/aprs_tool</a> for usage.
+    </div>
+    <div style="margin-left: 50%;">
+      <div id="map"></div>
+        <script>
+            var map = L.map('map').setView([{{station_lat}}, {{station_lon}}], 10);
+            L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {attribution: '&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors'}).addTo(map);
+
+            //{{markers|safe}}
+
+            // Show station location
+            var station = L.marker([{{station_lat}}, {{station_lon}}]).addTo(map).bindTooltip('{{station_call}}', permanent=true).openTooltip();
+            
+            // Show GeoJSON of markers
+            var group = L.geoJSON({{geojs|safe}},
+                    {
+                        style: function (feature) {
+                            return {color: feature.properties.color};
+                        }
+                    }).bindTooltip(function (layer) {
+                return 'Object '+layer.feature.properties.object_name+' from '+layer.feature.properties.from;
+                }, permanent=true).addTo(map);
+
+            // Zoom to show all
+            map.fitBounds(group.getBounds().pad(0.3));
+        </script>
+    </div>
+</div>
 
 <h2> Recent RF Packets </h2>
    <table>
@@ -40,6 +80,8 @@ This is a work in progress. See <a href="https://amiok.net/gitea/W1CDN/aprs_tool
       {% endfor %}
   </table>
 
+  
+
 <h2> Recent Stations </h2>
 <table>
   <tr>
diff --git a/templates/map.html b/templates/map.html
index 87fe6d2..614e608 100644
--- a/templates/map.html
+++ b/templates/map.html
@@ -19,7 +19,23 @@
         var map = L.map('map').setView([{{station_lat}}, {{station_lon}}], 10);
         L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {attribution: '&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors'}).addTo(map);
 
-        {{markers|safe}}
+        //{{markers|safe}}
+
+        // Show station location
+        var station = L.marker([{{station_lat}}, {{station_lon}}]).addTo(map).bindTooltip('{{station_call}}', permanent=true).openTooltip();
+        
+        // Show GeoJSON of markers
+        var group = L.geoJSON({{geojs|safe}},
+                {
+                    style: function (feature) {
+                        return {color: feature.properties.color};
+                    }
+                }).bindTooltip(function (layer) {
+            return 'Object '+layer.feature.properties.object_name+' from '+layer.feature.properties.from;
+            }, permanent=true).addTo(map);
+
+        // Zoom to show all
+        map.fitBounds(group.getBounds().pad(0.2));
 
     </script>
 </body>
diff --git a/test_db.py b/test_db.py
deleted file mode 100644
index e0959e5..0000000
--- a/test_db.py
+++ /dev/null
@@ -1,36 +0,0 @@
-
-# Learn how to update database
-
-import sqlite3
-
-def get_db_connection():
-    conn = sqlite3.connect('database.db')
-    conn.row_factory = sqlite3.Row
-    return conn
-
-conn = get_db_connection()
-
-# Grab a random row from frames table and pretend it is new
-cur = conn.cursor()
-cur.execute("SELECT [from], id, created_unix FROM frames ORDER BY RANDOM() LIMIT 1;")
-rows = cur.fetchall()
-results = dict(rows[0])
-values = ', '.join('"%s"' % w for w in results.values())
-
-
-# Build query
-# "from" is wrappedin [] because it is a reserved word and using '' doesn't work.
-query3 = "INSERT INTO stations ([from], frames_id, last_heard_unix, count) \
-VALUES("+values+", 1) \
-ON CONFLICT([from]) \
-DO UPDATE SET count = count + 1;"
-
-# example https://stackoverflow.com/a/50718957/2152245
-# query2 = "INSERT INTO stations ([from], frames_id, last_heard_unix, count) \
-# VALUES('KC9TZN-8', 4068, 1687623864, 1) \
-# ON CONFLICT([from]) \
-# DO UPDATE SET count = count + 1;"
-
-conn.execute(query3)
-conn.commit()
-conn.close()

From ab595ed3cbeea8703a503021515883a37d769d1b Mon Sep 17 00:00:00 2001
From: W1CDN <matthew.burtonkelly@gmail.com>
Date: Fri, 19 Jan 2024 11:08:12 -0600
Subject: [PATCH 5/6] Add better labels to main page map.

---
 templates/index.html | 29 +++++++++++++++++++++++++----
 1 file changed, 25 insertions(+), 4 deletions(-)

diff --git a/templates/index.html b/templates/index.html
index 493a1e2..9b3aad8 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -18,6 +18,11 @@
         border: 1px solid black;
       }
       #map { height: 250px; }
+      .leaflet-tooltip.my-labels {
+        background-color: transparent;
+        border: transparent;
+        box-shadow: none;
+        }
     </style>
 </head>
 <body>
@@ -39,7 +44,7 @@
             //{{markers|safe}}
 
             // Show station location
-            var station = L.marker([{{station_lat}}, {{station_lon}}]).addTo(map).bindTooltip('{{station_call}}', permanent=true).openTooltip();
+            var station = L.marker([{{station_lat}}, {{station_lon}}]).addTo(map).bindTooltip('{{station_call}}', {permanent: true}).openTooltip();
             
             // Show GeoJSON of markers
             var group = L.geoJSON({{geojs|safe}},
@@ -47,9 +52,25 @@
                         style: function (feature) {
                             return {color: feature.properties.color};
                         }
-                    }).bindTooltip(function (layer) {
-                return 'Object '+layer.feature.properties.object_name+' from '+layer.feature.properties.from;
-                }, permanent=true).addTo(map);
+                    });
+
+            // group.bindTooltip(function (layer) {
+            //                           return 'Object '+layer.feature.properties.object_name+' from '+layer.feature.properties.from;
+            //                           }, {permanent: false}).openTooltip().addTo(map);
+            // Hacked together from https://gis.stackexchange.com/a/246919
+            var pointLayer = L.geoJSON(null, {
+                                pointToLayer: function(feature,latlng){
+                                  //(true condition) ? "true" : "false"
+                                  label = (feature.properties.object_name === null) ? String(feature.properties.from) : String(feature.properties.object_name)
+                                  //label = String('Object '+feature.properties.object_name+' from '+feature.properties.from) // Must convert to string, .bindTooltip can't use straight 'feature.properties.attribute'
+                                  return new L.CircleMarker(latlng, {
+                                    radius: 1,
+                                  }).bindTooltip(label, {permanent: true, opacity: 0.7, className: "my-labels"}).openTooltip();
+                                  }
+                                });
+            pointLayer.addData({{geojs|safe}});
+            map.addLayer(pointLayer);
+            
 
             // Zoom to show all
             map.fitBounds(group.getBounds().pad(0.3));

From c8f8f28a4ac86dde5bd9d6c25dbc4760b17f6b08 Mon Sep 17 00:00:00 2001
From: W1CDN <matthew.burtonkelly@gmail.com>
Date: Wed, 20 Nov 2024 10:52:58 -0600
Subject: [PATCH 6/6] Only select 10 stations by default.

---
 api_app.py | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/api_app.py b/api_app.py
index fb35a98..de0a777 100644
--- a/api_app.py
+++ b/api_app.py
@@ -85,6 +85,19 @@ def select_frames(conn, n, url_params):
     cur.execute(sql)
     rows = cur.fetchall()
     return rows
+    
+def select_stations(conn, n):
+    """
+    Query rows in the stations table
+    :param conn: the Connection object
+    :return:
+    """
+    cur = conn.cursor()
+    sql = 'SELECT * FROM stations ORDER BY last_heard_unix DESC LIMIT {n}'.format(n=n)
+    print(sql)
+    cur.execute(sql)
+    rows = cur.fetchall()
+    return rows
 
 @api_app.route('/')
 def index():
@@ -215,7 +228,7 @@ class Stations(Resource):
 
         conn = get_db_connection()
         # Limit to number of records requested
-        data = select_all_stations(conn)
+        data = select_stations(conn, n = n)
         # Sort by created date, descending (https://stackoverflow.com/a/45266808)
         #data.sort(key=operator.itemgetter('created'), reverse=True)
         return {'data':data}, 200  # return data and 200 OK code