From bcb06247860e0b3650dceeb4840bf328e4e22045 Mon Sep 17 00:00:00 2001 From: W1CDN 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 @@ + + + + + + + + + +
+ + + + \ No newline at end of file From 5fb589507e8446e277faf7e205f2ef4e1baf2989 Mon Sep 17 00:00:00 2001 From: W1CDN 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 @@
\ No newline at end of file From 260e946ab65a370fb82787da00c14dc64fca6bb1 Mon Sep 17 00:00:00 2001 From: W1CDN 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 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 @@ {{station_call}} Status + + + + + -

{{station_call}} Status

-Station location: {{station_lat}}, {{station_lon}} +
+
-

About

-This is a work in progress. See https://amiok.net/gitea/W1CDN/aprs_tool for usage. +

{{station_call}} Status

+ Station location: {{station_lat}}, {{station_lon}} + +

About

+ This is a work in progress. See https://amiok.net/gitea/W1CDN/aprs_tool for usage. +
+
+
+ +
+

Recent RF Packets

@@ -40,6 +80,8 @@ This is a work in progress. See OpenStreetMap 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)); 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 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; + } @@ -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 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