Source code for boatdclient.point

import math
from math import sin as sin
from math import cos as cos

from .bearing import Bearing

EARTH_RADIUS = 6371009.0  # in meters

[docs]class Point(object): '''A point on the face of the earth''' def __init__(self, latitude, longitude): self._lat = latitude self._long = longitude @classmethod
[docs] def from_radians(cls, lat_radians, long_radians): ''' Return a new instance of Point from a pair of coordinates in radians. ''' return cls(math.degrees(lat_radians), math.degrees(long_radians))
def __getitem__(self, key): if key == 0: return self._lat elif key == 1: return self._long else: raise IndexError('Point objects can only have two coordinates') def __iter__(self): '''Return an iterator containing the lat and long''' return iter([, self.long]) def __str__(self): '''Return a string representation of the point''' return '{0:0.2f}N, {1:0.2f}W'.format(*list(self)) def __repr__(self): return '<{0}.{1} ({2}) object at {3}>'.format( self.__module__, type(self).__name__, str(self), hex(id(self))) @property def lat(self): '''Return the latitude in degrees''' return self._lat @property def long(self): '''Return the longitude in degrees''' return self._long @property def lat_radians(self): '''Return the latitude in radians''' return math.radians( @property def long_radians(self): '''Return the longitude in radians''' return math.radians(self.long)
[docs] def distance_to(self, point): ''' Return the distance between this point and another point in meters. :param point: Point to measure distance to :type point: Point :returns: The distance to the other point :rtype: float ''' angle = math.acos( sin(self.lat_radians) * sin(point.lat_radians) + cos(self.lat_radians) * cos(point.lat_radians) * cos(self.long_radians - point.long_radians) ) return angle * EARTH_RADIUS
[docs] def bearing_to(self, point): ''' Return the bearing to another point. :param point: Point to measure bearing to :type point: Point :returns: The bearing to the other point :rtype: Bearing ''' delta_long = point.long_radians - self.long_radians y = sin(delta_long) * cos(point.lat_radians) x = ( cos(self.lat_radians) * sin(point.lat_radians) - sin(self.lat_radians) * cos(point.lat_radians) * cos(delta_long) ) radians = math.atan2(y, x) return Bearing.from_radians(radians)
[docs] def cross_track_distance(self, start_point, end_point): ''' Return the cross track distance from this point to the line between two points:: * end_point / / / * this point / / * start_point :param start_point: First point on the line :type start_point: Point :param end_point: Second point on the line :type end_point: Point :returns: The perpendicular distance to the line between ``start_point`` and ``end_point``, where distance on the right of ``start_point`` is positive and distance on the left is negative :rtype: float ''' dist = start_point.distance_to(self) bearing_to_end = start_point.bearing_to(end_point).radians bearing_to_point = start_point.bearing_to(self).radians return math.asin(math.sin(dist / EARTH_RADIUS) * \ math.sin(bearing_to_point - bearing_to_end)) * \ EARTH_RADIUS
[docs] def relative_point(self, bearing_to_point, distance): ''' Return a waypoint at a location described relative to the current point :param bearing_to_point: Relative bearing from the current waypoint :type bearing_to_point: Bearing :param distance: Distance from the current waypoint :type distance: float :return: The point described by the parameters ''' bearing = math.radians(360 - bearing_to_point) rad_distance = (distance / EARTH_RADIUS) lat1 = (self.lat_radians) lon1 = (self.long_radians) lat3 = math.asin(math.sin(lat1) * math.cos(rad_distance) + math.cos(lat1) * math.sin(rad_distance) * math.cos(bearing)) lon3 = lon1 + math.atan2(math.sin(bearing) * math.sin(rad_distance) * math.cos(lat1) , math.cos(rad_distance) - math.sin(lat1) * math.sin(lat3)) return Point(math.degrees(lat3), math.degrees(lon3))
def __add__(self, other): return Point( +, self.long + other.long) def __sub__(self, other): return Point( -, self.long - other.long) def __div__(self, value): return Point( / value, self.long / value)
# do a couple of tests if __name__ == '__main__': castle = Point(52.41389, -4.09098) # aber castle print(castle) hill = Point(52.42459, -4.08339) # Constitution hill print(hill) # distance should be ~1.29844 km print('regular:', castle.distance_to(hill)) dismaland = Point(51.340911, -2.982787) print('regular:', castle.distance_to(dismaland)) print('cross track:', Point(52.413990, -4.089979).cross_track_distance(castle, hill)) print(castle.bearing_to(hill)) # should be ~90 degrees print(Point(52.41398, -4.4627).bearing_to(Point(52.41398, -4.09122)))