def distance(start, end, options=None): """ Calculates the distance between two Points in degrees, radians, miles, or kilometers. This uses the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula) to account for global curvature. :param start: starting point [lng, lat] or Point feature :param end: ending point [lng, lat] or Point feature :param options: dictionary with units as an attribute. Can be degrees, radians, miles, or kilometers :return: distance between the 2 points """ kwargs = {} if isinstance(options, dict) and "units" in options: kwargs.update(options) coordinates1 = get_coords_from_features(start, ["Point"]) coordinates2 = get_coords_from_features(end, ["Point"]) d_lat = degrees_to_radians(coordinates2[1] - coordinates1[1]) d_lon = degrees_to_radians(coordinates2[0] - coordinates1[0]) lat1 = degrees_to_radians(coordinates1[1]) lat2 = degrees_to_radians(coordinates2[1]) distance_rad = calculate_radians_distance(d_lon, d_lat, lat1, lat2) return radians_to_length(distance_rad, **kwargs)
def __init__(self, lon: float, lat: float, decimals=6): self.lon = lon self.lat = lat self.x = degrees_to_radians(lon) self.y = degrees_to_radians(lat) self.decimals = decimals
def bearing(start, end, options=None): """ Takes two points and finds the geographic bearing between them, i.e. the angle measured in degrees from the north line (0 degrees) :param start: starting point [lng, lat] or Point feature :param end: ending point [lng, lat] or Point feature :param options: dictionary with options: [options["final"]] - calculates the final bearing if true :return: bearing in decimal degrees, between -180 and 180 (positive clockwise) """ if not options: options = {} if isinstance(options, dict) and "final" in options: return calculate_final_bearing(start, end) start = get_coords_from_features(start, ["Point"]) end = get_coords_from_features(end, ["Point"]) lon1 = degrees_to_radians(start[0]) lon2 = degrees_to_radians(end[0]) lat1 = degrees_to_radians(start[1]) lat2 = degrees_to_radians(end[1]) a = np.sin(lon2 - lon1) * np.cos(lat2) b = np.cos(lat1) * np.sin(lat2) - np.sin(lat1) * np.cos(lat2) * np.cos(lon2 - lon1) return radians_to_degrees(np.arctan2(a, b))
def calculate_rhumb_destination(origin: Sequence, distance_in_meters: float, bearing: float, radius: float = None) -> List: """ Calculates the destination point having travelled along a rhumb line from origin point the given distance on the given bearing. Adapted from Geodesy: http://www.movable-type.co.uk/scripts/latlong.html#rhumblines param origin: point coordinates in [lng, lat] form param distance: - Distance travelled, in same units as earth radius (default: metres). param bearing: - Bearing in degrees from north. param radius: - (Mean) radius of earth returns destination: point. """ if not radius: radius = earth_radius # angular distance in radians delta = distance_in_meters / radius # to radians, but without normalize to pi lambda_1 = origin[0] * pi / 180 phi_1 = degrees_to_radians(origin[1]) theta = degrees_to_radians(bearing) delta_phi = delta * cos(theta) phi_2 = phi_1 + delta_phi # check for some points going past the pole, normalise latitude if so if abs(phi_2) > (pi / 2) and (phi_2 > 0): phi_2 = pi - phi_2 if abs(phi_2) > (pi / 2) and (phi_2 < 0): phi_2 = pi - phi_2 delta_psi = log(tan(phi_2 / 2 + pi / 4) / tan(phi_1 / 2 + pi / 4)) # E-W course becomes ill-conditioned with 0/0 if abs(delta_psi) > 10e-12: q_1 = delta_phi / delta_psi else: q_1 = cos(phi_1) delta_lambda = delta * sin(theta) / q_1 lambda_2 = lambda_1 + delta_lambda # normalise to −180..+180° destination = [ fmod(((lambda_2 * 180 / pi) + 540), 360) - 180, (phi_2 * 180 / pi), ] return destination
def calculate_rhumb_distance(origin: List, destination: List, radius: float = None) -> float: """ Calculates the rhumb distance between two Points. # https://en.wikipedia.org/wiki/Rhumb_line :param origin: starting point [lng, lat] :param destination: ending point [lng, lat] :param radius: radius of the earth :return: distance between the 2 points in meters """ if not radius: radius = earth_radius phi_1 = degrees_to_radians(origin[1]) phi_2 = degrees_to_radians(destination[1]) delta_phi = phi_2 - phi_1 delta_lambda = degrees_to_radians(abs(destination[0] - origin[0])) # if dLon over 180° take shorter rhumb line across the anti-meridian: if delta_lambda > np.pi: delta_lambda -= 2 * np.pi # on Mercator projection, longitude distances shrink by latitude; q is the 'stretch factor' # q becomes ill-conditioned along E-W line (0/0); use empirical tolerance to avoid it delta_psi = np.log( np.tan(phi_2 / 2 + np.pi / 4) / np.tan(phi_1 / 2 + np.pi / 4)) if abs(delta_psi) > 10e-12: q_1 = delta_phi / delta_psi else: q_1 = np.cos(phi_1) # distance is pythagoras on 'stretched' Mercator projection delta = np.sqrt(delta_phi * delta_phi + q_1 * q_1 * delta_lambda * delta_lambda) # angular distance in radians distance = delta * radius return distance
def destination(origin, distance, bearing, options=None): """ Takes a Point and calculates the location of a destination point given a distance in degrees, radians, miles, or kilometers; and bearing in degrees. This uses the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula) to account for global curvature. :param origin: starting point :param distance: distance from the origin point :param bearing: bearing ranging from -180 to 180 :param options: optional parameters [options["units"]='kilometers'] miles, kilometers, degrees, or radians [options["properties"]={}] Translate properties to Point :return: destination GeoJSON Point feature """ if not options: options = {} kwargs = {} if "units" in options: kwargs["units"] = options.get("units") coords = get_coords_from_features(origin, ["Point"]) longitude1 = degrees_to_radians(coords[0]) latitude1 = degrees_to_radians(coords[1]) bearing_rads = degrees_to_radians(bearing) radians = length_to_radians(distance, **kwargs) latitude2 = asin( sin(latitude1) * cos(radians) + cos(latitude1) * sin(radians) * cos(bearing_rads)) longitude2 = longitude1 + atan2( sin(bearing_rads) * sin(radians) * cos(latitude1), cos(radians) - sin(latitude1) * sin(latitude2), ) lng = truncate(radians_to_degrees(longitude2), 6) lat = truncate(radians_to_degrees(latitude2), 6) return point([lng, lat], options.get("properties", None))
def calculate_rhumb_bearing(origin: Sequence, destination: Sequence) -> float: """ Calculates the bearing from origin to destination point along a rhumb line. http://www.edwilliams.org/avform.htm#Rhumb """ phi_1 = degrees_to_radians(origin[1]) phi_2 = degrees_to_radians(destination[1]) delta_lambda = degrees_to_radians(destination[0] - origin[0]) # if delta_lambda over 180° take shorter rhumb line across the anti-meridian: if abs(delta_lambda) > pi: if delta_lambda > 0: delta_lambda = -(2 * pi - delta_lambda) if delta_lambda < 0: delta_lambda = 2 * pi + delta_lambda delta_psi = log(tan(phi_2 / 2 + pi / 4) / tan(phi_1 / 2 + pi / 4)) theta = atan2(delta_lambda, delta_psi) return fmod(radians_to_degrees(theta) + 360, 360)
def test_degrees_to_radians(value, result): assert degrees_to_radians(value) == result