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
[docs] @classmethod
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.lat, 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(self.lat)
@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.lat + other.lat, self.long + other.long)
def __sub__(self, other):
return Point(self.lat - other.lat, self.long - other.long)
def __div__(self, value):
return Point(self.lat / 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)))