2
from itertools import islice
3
from geopy import util, units, format
7
A geodetic point with latitude, longitude, and altitude.
9
Latitude and longitude are floating point values in degrees.
10
Altitude is a floating point value in kilometers. The reference level
11
is never considered and is thus application dependent, so be consistent!
12
The default for all values is 0.
14
Points can be created in a number of ways...
16
With longitude, latitude, and altitude:
17
>>> p1 = Point(41.5, -81, 0)
18
>>> p2 = Point(latitude=41.5, longitude=-81)
20
With a sequence of 0 to 3 values (longitude, latitude, altitude):
21
>>> p1 = Point([41.5, -81, 0])
22
>>> p2 = Point((41.5, -81))
24
Copy another `Point` instance:
31
Give an object with a 'point' attribute, such as a `Location` instance:
32
>>> p = Point(location)
34
Give a string containing at least latitude and longitude:
35
>>> p1 = Point('41.5,-81.0')
36
>>> p2 = Point('41.5 N -81.0 W')
37
>>> p3 = Point('-41.5 S, 81.0 E, 2.5km')
38
>>> p4 = Point('23 26m 22s N 23 27m 30s E 21.0mi')
39
>>> p5 = Point('''3 26' 22" N 23 27' 30" E''')
41
Point values can be accessed by name or by index:
42
>>> p = Point(41.5, -81.0, 0)
43
>>> p.latitude == p[0]
45
>>> p.longitude == p[1]
47
>>> p.altitude == p[2]
50
When unpacking (or iterating), only latitude and longitude are included:
51
>>> latitude, longitude = p
55
FLOAT=r'\d+(?:\.\d+)?',
58
DOUBLE_PRIME=format.DOUBLE_PRIME,
61
POINT_PATTERN = re.compile(r"""
64
(?P<latitude_degrees>-?%(FLOAT)s)(?:[%(DEGREE)s ][ ]*
65
(?:(?P<latitude_arcminutes>%(FLOAT)s)[%(PRIME)s'm][ ]*)?
66
(?:(?P<latitude_arcseconds>%(FLOAT)s)[%(DOUBLE_PRIME)s"s][ ]*)?
67
)?(?P<latitude_direction>[NS])?)
70
(?P<longitude_degrees>-?%(FLOAT)s)(?:[%(DEGREE)s\s][ ]*
71
(?:(?P<longitude_arcminutes>%(FLOAT)s)[%(PRIME)s'm][ ]*)?
72
(?:(?P<longitude_arcseconds>%(FLOAT)s)[%(DOUBLE_PRIME)s"s][ ]*)?
73
)?(?P<longitude_direction>[EW])?)(?:
76
(?P<altitude_distance>-?%(FLOAT)s)[ ]*
77
(?P<altitude_units>km|m|mi|ft|nm|nmi)))?
79
""" % UTIL_PATTERNS, re.X)
81
def __new__(cls, latitude=None, longitude=None, altitude=None):
82
single_arg = longitude is None and altitude is None
83
if single_arg and not isinstance(latitude, util.NUMBER_TYPES):
87
elif isinstance(arg, Point):
88
return cls.from_point(arg)
89
elif isinstance(arg, basestring):
90
return cls.from_string(arg)
96
"Failed to create Point instance from %r." % (arg,)
99
return cls.from_sequence(seq)
101
latitude = float(latitude or 0)
102
if abs(latitude) > 90:
103
raise ValueError("Latitude out of range [-90, 90]: %r" % latitude)
105
longitude = float(longitude or 0)
106
if abs(longitude) > 180:
107
raise ValueError("Longitude out of range [-180, 180]: %r" % longitude)
109
altitude = float(altitude or 0)
111
self = super(Point, cls).__new__(cls)
112
self.latitude = latitude
113
self.longitude = longitude
114
self.altitude = altitude
117
def __getitem__(self, index):
118
return (self.latitude, self.longitude, self.altitude)[index]
120
def __setitem__(self, index, value):
121
point = [self.latitude, self.longitude, self.altitude]
123
self.latitude, self.longitude, self.altitude = point
126
return iter((self.latitude, self.longitude, self.altitude))
129
return "Point(%r, %r, %r)" % (
130
self.latitude, self.longitude, self.altitude
133
def format(self, altitude=None, deg_char='', min_char='m', sec_char='s'):
134
latitude = "%s %s" % (
135
format.angle(abs(self.latitude), deg_char, min_char, sec_char),
136
self.latitude >= 0 and 'N' or 'S'
138
longitude = "%s %s" % (
139
format.angle(abs(self.longitude), deg_char, min_char, sec_char),
140
self.longitude >= 0 and 'E' or 'W'
142
coordinates = [latitude, longitude]
145
altitude = bool(self.altitude)
147
if not isinstance(altitude, basestring):
149
coordinates.append(self.format_altitude(altitude))
151
return ", ".join(coordinates)
153
def format_decimal(self, altitude=None):
154
latitude = "%s" % self.latitude
155
longitude = "%s" % self.longitude
156
coordinates = [latitude, longitude]
159
altitude = bool(self.altitude)
161
if not isinstance(altitude, basestring):
163
coordinates.append(self.format_altitude(altitude))
165
return ", ".join(coordinates)
167
def format_altitude(self, unit='km'):
168
return format.distance(self.altitude, unit)
173
def __unicode__(self):
175
None, format.DEGREE, format.PRIME, format.DOUBLE_PRIME
178
def __eq__(self, other):
179
return tuple(self) == tuple(other)
181
def __ne__(self, other):
182
return tuple(self) != tuple(other)
185
def parse_degrees(cls, degrees, arcminutes, arcseconds, direction=None):
186
negative = degrees < 0 or degrees.startswith('-')
187
degrees = float(degrees or 0)
188
arcminutes = float(arcminutes or 0)
189
arcseconds = float(arcseconds or 0)
191
if arcminutes or arcseconds:
192
more = units.degrees(arcminutes=arcminutes, arcseconds=arcseconds)
198
if direction in [None, 'N', 'E']:
200
elif direction in ['S', 'W']:
203
raise ValueError("Invalid direction! Should be one of [NSEW].")
206
def parse_altitude(cls, distance, unit):
207
if distance is not None:
208
distance = float(distance)
211
'm': lambda d: units.kilometers(meters=d),
212
'mi': lambda d: units.kilometers(miles=d),
213
'ft': lambda d: units.kilometers(feet=d),
214
'nm': lambda d: units.kilometers(nautical=d),
215
'nmi': lambda d: units.kilometers(nautical=d)
217
return CONVERTERS[unit](distance)
222
def from_string(cls, string):
224
Create and return a Point instance from a string containing latitude
225
and longitude, and optionally, altitude.
227
Latitude and longitude must be in degrees and may be in decimal form
228
or indicate arcminutes and arcseconds (labeled with Unicode prime and
229
double prime, ASCII quote and double quote or 'm' and 's'). The degree
230
symbol is optional and may be included after the decimal places (in
231
decimal form) and before the arcminutes and arcseconds otherwise.
232
Coordinates given from south and west (indicated by S and W suffixes)
233
will be converted to north and east by switching their signs. If no
234
(or partial) cardinal directions are given, north and east are the
235
assumed directions. Latitude and longitude must be separated by at
236
least whitespace, a comma, or a semicolon (each with optional
237
surrounding whitespace).
239
Altitude, if supplied, must be a decimal number with given units.
240
The following unit abbrevations (case-insensitive) are supported:
246
nm, nmi (nautical miles)
248
Some example strings the will work include:
255
23 26m 22s N 23 27m 30s E
256
23 26' 22" N 23 27' 30" E
259
match = re.match(cls.POINT_PATTERN, string)
261
latitude = cls.parse_degrees(
262
match.group('latitude_degrees'),
263
match.group('latitude_arcminutes'),
264
match.group('latitude_arcseconds'),
265
match.group('latitude_direction')
267
longitude = cls.parse_degrees(
268
match.group('longitude_degrees'),
269
match.group('longitude_arcminutes'),
270
match.group('longitude_arcseconds'),
271
match.group('longitude_direction'),
273
altitude = cls.parse_altitude(
274
match.group('altitude_distance'),
275
match.group('altitude_units')
277
return cls(latitude, longitude, altitude)
280
"Failed to create Point instance from string: unknown format."
284
def from_sequence(cls, seq):
286
Create and return a new Point instance from any iterable with 0 to
287
3 elements. The elements, if present, must be latitude, longitude,
288
and altitude, respectively.
291
args = tuple(islice(seq, 4))
295
def from_point(cls, point):
297
Create and return a new Point instance from another Point instance.
300
return cls(point.latitude, point.longitude, point.altitude)