diff --git a/README.md b/README.md index a7db2ed..f29bb84 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,7 @@ git clone https://github.com/spudgunman/meshing-around | `readnews` | returns the contents of a file (news.txt, by default) via the chunker on air | ✅ | | `satpass` | returns the pass info from API for defined NORAD ID in config or Example: `satpass 25544,33591`| | | `wiki:` | Searches Wikipedia and returns the first few sentences of the first result if a match. Example: `wiki: lora radio` | +| `howfar` | returns the distance you have traveled since your last HowFar. `howfar reset` to start over | ✅ | ### CheckList | Command | Description | | diff --git a/mesh_bot.py b/mesh_bot.py index dfd14d9..6077d44 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -61,6 +61,7 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n "hangman": lambda: handleHangman(message, message_from_id, deviceID), "hfcond": hf_band_conditions, "history": lambda: handle_history(message, message_from_id, deviceID, isDM), + "howfar": lambda: handle_howfar(message, message_from_id, deviceID, isDM), "joke": lambda: tell_joke(message_from_id), "lemonstand": lambda: handleLemonade(message, message_from_id, deviceID), "lheard": lambda: handle_lheard(message, message_from_id, deviceID, isDM), @@ -311,6 +312,31 @@ def handle_wxalert(message_from_id, deviceID, message): weatherAlert = weatherAlert[0] return weatherAlert +def handle_howfar(message, message_from_id, deviceID, isDM): + msg = '' + location = get_node_location(message_from_id, deviceID) + lat = location[0] + lon = location[1] + # if ? in message + if "?" in message.lower(): + return "command returns the distance you have traveled since your last HowFar-command. Add 'reset' to reset your starting point." + + # if no GPS location return + if lat == latitudeValue and lon == longitudeValue: + logger.debug(f"System: HowFar: No GPS location for {message_from_id}") + return "No GPS location available" + + if "reset" in message.lower(): + msg = distance(lat,lon,message_from_id, reset=True) + else: + msg = distance(lat,lon,message_from_id) + + # if not a DM add the username to the beginning of msg + if not useDMForResponse and not isDM: + msg = "@" + get_name_from_number(message_from_id, 'short', deviceID) + " " + msg + + return msg + def handle_wiki(message, isDM): # location = get_node_location(message_from_id, deviceID) msg = "Wikipedia search function. \nUsage example:📲wiki: travelling gnome" diff --git a/modules/locationdata.py b/modules/locationdata.py index 0fd1535..dcbe7c0 100644 --- a/modules/locationdata.py +++ b/modules/locationdata.py @@ -8,8 +8,10 @@ import requests # pip install requests import bs4 as bs # pip install beautifulsoup4 import xml.dom.minidom from modules.log import * +import math -trap_list_location = ("whereami", "wx", "wxa", "wxalert", "rlist", "ea", "ealert", "riverflow", "valert", "earthquake") + +trap_list_location = ("whereami", "wx", "wxa", "wxalert", "rlist", "ea", "ealert", "riverflow", "valert", "earthquake", "howfar") def where_am_i(lat=0, lon=0, short=False, zip=False): whereIam = "" @@ -811,3 +813,121 @@ def checkUSGSEarthQuake(lat=0, lon=0): return NO_ALERTS else: return f"{quake_count} quakes in last {history} days within {radius}km of you largest was {largest_mag}. {description_text}" + +howfarDB = {} +def distance(lat=0,lon=0,nodeID=0, reset=False): + # part of the howfar function, calculates the distance between two lat/lon points + msg = "" + if lat == 0 and lon == 0: + return NO_DATA_NOGPS + if nodeID == 0: + return "No NodeID provided" + + if reset: + if nodeID in howfarDB: + del howfarDB[nodeID] + + if nodeID not in howfarDB: + #register first point NodeID, lat, lon, time, point + howfarDB[nodeID] = [{'lat': lat, 'lon': lon, 'time': datetime.now()}] + if reset: + return "Tracking reset, new starting point registered🗺️" + else: + return "Starting point registered🗺️" + else: + #de-dupe points if same as last point + if howfarDB[nodeID][-1]['lat'] == lat and howfarDB[nodeID][-1]['lon'] == lon: + return "📍No movement detected yet" + # calculate distance from last point in howfarDB + last_point = howfarDB[nodeID][-1] + lat1 = math.radians(last_point['lat']) + lon1 = math.radians(last_point['lon']) + lat2 = math.radians(lat) + lon2 = math.radians(lon) + dlon = lon2 - lon1 + dlat = lat2 - lat1 + a = math.sin(dlat / 2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2)**2 + c = 2 * math.asin(math.sqrt(a)) + r = 6371 # Radius of earth in kilometers + distance_km = c * r + if use_metric: + msg += f"{distance_km:.2f} km" + else: + distance_miles = distance_km * 0.621371 + msg += f"{distance_miles:.2f} miles" + + # calculate the speed if time difference is more than 1 minute + time_diff = datetime.now() - last_point['time'] + if time_diff.total_seconds() > 60: + hours = time_diff.total_seconds() / 3600 + if use_metric: + speed = distance_km / hours + speed_str = f"{speed:.2f} km/h" + else: + speed_mph = (distance_km * 0.621371) / hours + speed_str = f"{speed_mph:.2f} mph" + msg += f", travel time: {int(time_diff.total_seconds()//60)} min, Speed: {speed_str}" + + #calculate bearing + x = math.sin(dlon) * math.cos(lat2) + y = math.cos(lat1) * math.sin(lat2) - (math.sin(lat1) * math.cos(lat2) * math.cos(dlon)) + initial_bearing = math.atan2(x, y) + initial_bearing = math.degrees(initial_bearing) + compass_bearing = (initial_bearing + 360) % 360 + msg += f", Bearing from last point: {compass_bearing:.2f}°" + + # if points 3+ are within 30 meters of the first point add the area of the polygon + if len(howfarDB[nodeID]) >= 3: + points = [] + # loop the howfarDB to get all the points except the current nodeID + for key in howfarDB: + if key != nodeID: + points.append((howfarDB[key][-1]['lat'], howfarDB[key][-1]['lon'])) + # loop the howfarDB[nodeID] to get the points + for point in howfarDB[nodeID]: + points.append((point['lat'], point['lon'])) + # close the polygon by adding the first point to the end + points.append((howfarDB[nodeID][0]['lat'], howfarDB[nodeID][0]['lon'])) + # calculate the area of the polygon + area = 0.0 + for i in range(len(points)-1): + lat1 = math.radians(points[i][0]) + lon1 = math.radians(points[i][1]) + lat2 = math.radians(points[i+1][0]) + lon2 = math.radians(points[i+1][1]) + area += (lon2 - lon1) * (2 + math.sin(lat1) + math.sin(lat2)) + area = area * (6378137 ** 2) / 2.0 + area = abs(area) / 1e6 # convert to square kilometers + + if use_metric: + msg += f", Area Sq.Km: {area:.2f} sq.km (approx)" + else: + area_miles = area * 0.386102 + msg += f", Area Sq.Miles: {area_miles:.2f} sq.mi (approx)" + + #calculate the centroid of the polygon + x = 0.0 + y = 0.0 + z = 0.0 + for point in points[:-1]: + lat_rad = math.radians(point[0]) + lon_rad = math.radians(point[1]) + x += math.cos(lat_rad) * math.cos(lon_rad) + y += math.cos(lat_rad) * math.sin(lon_rad) + z += math.sin(lat_rad) + total_points = len(points) - 1 + x /= total_points + y /= total_points + z /= total_points + lon_centroid = math.atan2(y, x) + hyp = math.sqrt(x * x + y * y) + lat_centroid = math.atan2(z, hyp) + lat_centroid = math.degrees(lat_centroid) + lon_centroid = math.degrees(lon_centroid) + msg += f", Centroid: {lat_centroid:.5f}, {lon_centroid:.5f}" + + + # update the last point in howfarDB + howfarDB[nodeID].append({'lat': lat, 'lon': lon, 'time': datetime.now()}) + + return msg \ No newline at end of file diff --git a/modules/space.py b/modules/space.py index 2834ef6..10c7714 100644 --- a/modules/space.py +++ b/modules/space.py @@ -128,21 +128,21 @@ def get_moon(lat=0, lon=0): illum = moon.phase # 0 = new, 50 = first/last quarter, 100 = full if illum < 1.0: - moon_phase = 'New Moon🌑' + moon_phase = 'New Moon 🌑' elif illum < 49: - moon_phase = 'Waxing Crescent🌒' + moon_phase = 'Waxing Crescent 🌒' elif 49 <= illum < 51: - moon_phase = 'First Quarter🌓' + moon_phase = 'First Quarter 🌓' elif illum < 99: - moon_phase = 'Waxing Gibbous🌔' + moon_phase = 'Waxing Gibbous 🌔' elif illum >= 99: - moon_phase = 'Full Moon🌕' + moon_phase = 'Full Moon 🌕' elif illum > 51: - moon_phase = 'Waning Gibbous🌖' + moon_phase = 'Waning Gibbous 🌖' elif 51 >= illum > 49: - moon_phase = 'Last Quarter🌗' + moon_phase = 'Last Quarter 🌗' else: - moon_phase = 'Waning Crescent🌘' + moon_phase = 'Waning Crescent 🌘' moon_table['phase'] = moon_phase moon_table['illumination'] = moon.phase @@ -167,9 +167,9 @@ def get_moon(lat=0, lon=0): moon_table['next_full_moon'] = local_next_full_moon.strftime('%a %b %d %I:%M%p') moon_table['next_new_moon'] = local_next_new_moon.strftime('%a %b %d %I:%M%p') - moon_data = "MoonRise:" + moon_table['rise_time'] + "\nSet:" + moon_table['set_time'] + \ - "\nPhase:" + moon_table['phase'] + " @:" + str('{0:.2f}'.format(moon_table['illumination'])) + "%" \ - + "\nFullMoon:" + moon_table['next_full_moon'] + "\nNewMoon:" + moon_table['next_new_moon'] + moon_data = "MoonRise: " + moon_table['rise_time'] + "\nSet: " + moon_table['set_time'] + \ + "\nPhase: " + moon_table['phase'] + " @: " + str('{0:.2f}'.format(moon_table['illumination'])) + "%" \ + + "\nFullMoon: " + moon_table['next_full_moon'] + "\nNewMoon: " + moon_table['next_new_moon'] # if moon is in the sky, add azimuth and altitude if moon_table['altitude'] > 0: @@ -206,7 +206,7 @@ def getNextSatellitePass(satellite, lat=0, lon=0): pass_startAzCompass = pass_json['passes'][0]['startAzCompass'] pass_set_time = datetime.fromtimestamp(pass_time + pass_duration).strftime('%a %d %I:%M%p') pass__endAzCompass = pass_json['passes'][0]['endAzCompass'] - pass_data = f"{satname} @{pass_rise_time} Az:{pass_startAzCompass} for{getPrettyTime(pass_duration)}, MaxEl:{pass_maxEl}° Set@{pass_set_time} Az:{pass__endAzCompass}" + pass_data = f"{satname} @{pass_rise_time} Az: {pass_startAzCompass} for{getPrettyTime(pass_duration)}, MaxEl: {pass_maxEl}° Set @{pass_set_time} Az: {pass__endAzCompass}" elif pass_json['info']['passescount'] == 0: satname = pass_json['info']['satname'] pass_data = f"{satname} has no upcoming passes" @@ -215,5 +215,5 @@ def getNextSatellitePass(satellite, lat=0, lon=0): pass_data = ERROR_FETCHING_DATA except Exception as e: logger.warning(f"System: User supplied value {satellite} unknown or invalid") - pass_data = "Provide NORAD# example use:🛰️satpass 25544,33591" + pass_data = "Provide NORAD# example use: 🛰️ satpass 25544,33591" return pass_data diff --git a/modules/system.py b/modules/system.py index 201a34d..e0c4ebe 100644 --- a/modules/system.py +++ b/modules/system.py @@ -75,7 +75,7 @@ if enableCmdHistory: if location_enabled: from modules.locationdata import * # from the spudgunman/meshing-around repo trap_list = trap_list + trap_list_location - help_message = help_message + ", whereami, wx, rlist" + help_message = help_message + ", whereami, wx, rlist, howfar" if enableGBalerts and not enableDEalerts: from modules.globalalert import * # from the spudgunman/meshing-around repo logger.warning(f"System: GB Alerts not functional at this time need to find a source API")