#!/usr/bin/env python # # Project Horus - Browser-Based Chase Mapper # Listeners # # Copyright (C) 2018 Mark Jessop # Released under GNU GPL v3 or later # from math import radians, degrees, sin, cos, atan2, sqrt, pi def position_info(listener, balloon): """ Calculate and return information from 2 (lat, lon, alt) tuples Copyright 2012 (C) Daniel Richman; GNU GPL 3 Returns a dict with: - angle at centre - great circle distance - distance in a straight line - bearing (azimuth or initial course) - elevation (altitude) Input and output latitudes, longitudes, angles, bearings and elevations are in degrees, and input altitudes and output distances are in meters. """ # Earth: # radius = 6371000.0 radius = 6364963.0 # Optimized for Australia :-) (lat1, lon1, alt1) = listener (lat2, lon2, alt2) = balloon lat1 = radians(lat1) lat2 = radians(lat2) lon1 = radians(lon1) lon2 = radians(lon2) # Calculate the bearing, the angle at the centre, and the great circle # distance using Vincenty's_formulae with f = 0 (a sphere). See # http://en.wikipedia.org/wiki/Great_circle_distance#Formulas and # http://en.wikipedia.org/wiki/Great-circle_navigation and # http://en.wikipedia.org/wiki/Vincenty%27s_formulae d_lon = lon2 - lon1 sa = cos(lat2) * sin(d_lon) sb = (cos(lat1) * sin(lat2)) - (sin(lat1) * cos(lat2) * cos(d_lon)) bearing = atan2(sa, sb) aa = sqrt((sa ** 2) + (sb ** 2)) ab = (sin(lat1) * sin(lat2)) + (cos(lat1) * cos(lat2) * cos(d_lon)) angle_at_centre = atan2(aa, ab) great_circle_distance = angle_at_centre * radius # Armed with the angle at the centre, calculating the remaining items # is a simple 2D triangley circley problem: # Use the triangle with sides (r + alt1), (r + alt2), distance in a # straight line. The angle between (r + alt1) and (r + alt2) is the # angle at the centre. The angle between distance in a straight line and # (r + alt1) is the elevation plus pi/2. # Use sum of angle in a triangle to express the third angle in terms # of the other two. Use sine rule on sides (r + alt1) and (r + alt2), # expand with compound angle formulae and solve for tan elevation by # dividing both sides by cos elevation ta = radius + alt1 tb = radius + alt2 ea = (cos(angle_at_centre) * tb) - ta eb = sin(angle_at_centre) * tb elevation = atan2(ea, eb) # Use cosine rule to find unknown side. distance = sqrt((ta ** 2) + (tb ** 2) - 2 * tb * ta * cos(angle_at_centre)) # Give a bearing in range 0 <= b < 2pi if bearing < 0: bearing += 2 * pi return { "listener": listener, "balloon": balloon, "listener_radians": (lat1, lon1, alt1), "balloon_radians": (lat2, lon2, alt2), "angle_at_centre": degrees(angle_at_centre), "angle_at_centre_radians": angle_at_centre, "bearing": degrees(bearing), "bearing_radians": bearing, "great_circle_distance": great_circle_distance, "straight_distance": distance, "elevation": degrees(elevation), "elevation_radians": elevation, } def bearing_to_cardinal(bearing): """ Convert a bearing in degrees to a 16-point cardinal direction """ bearing = bearing % 360.0 if bearing < 11.25: cardinal = "N" elif 11.25 <= bearing < 33.75: cardinal = "NNE" elif 33.75 <= bearing < 56.25: cardinal = "NE" elif 56.25 <= bearing < 78.75: cardinal = "ENE" elif 78.75 <= bearing < 101.25: cardinal = "E" elif 101.25 <= bearing < 123.75: cardinal = "ESE" elif 123.75 <= bearing < 146.25: cardinal = "SE" elif 146.25 <= bearing < 168.75: cardinal = "SSE" elif 168.75 <= bearing < 191.25: cardinal = "S" elif 191.25 <= bearing < 213.75: cardinal = "SSW" elif 213.75 <= bearing < 236.25: cardinal = "SW" elif 236.25 <= bearing < 258.75: cardinal = "WSW" elif 258.75 <= bearing < 281.25: cardinal = "W" elif 281.25 <= bearing < 303.75: cardinal = "WNW" elif 303.75 <= bearing < 326.25: cardinal = "NW" elif 326.25 <= bearing < 348.75: cardinal = "NNW" elif bearing >= 348.75: cardinal = "N" else: cardinal = "?" return cardinal