-
Notifications
You must be signed in to change notification settings - Fork 6
/
latlon.py
767 lines (656 loc) · 29.1 KB
/
latlon.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
from __future__ import absolute_import
import math
import re
import pyproj
import warnings
import abc
from six import PY3
if PY3:
def cmp(x, y):
"""
Replacement for built-in function cmp that was removed in Python 3
Compare the two objects x and y and return an integer according to
the outcome. The return value is negative if x < y, zero if x == y
and strictly positive if x > y.
"""
return (x > y) - (x < y)
'''
Methods for representing geographic coordinates (latitude and longitude)
Features:
Convert lat/lon strings from any format into a LatLon object
Automatically store decimal degrees, decimal minutes, and degree, minute, second
information in a LatLon object
Output lat/lon information into a formatted string
Project lat/lon coordinates into some other proj projection
Calculate distances between lat/lon pairs using either the FAI or WGS84 approximation
Written July 22, 2014
Author: Gen Del Raye
'''
# TODO: Write methods to convert -180 to 180 longitudes to 0 to 360 and vice versa
class GeoCoord:
__name__ = 'GeoCoord'
"""
Abstract class representing geographic coordinates (i.e. latitude or longitude)
Not meant to be used directly - access through Subclasses Latitude() and Longitude()
"""
__metaclass__ = abc.ABCMeta
def __init__(self, degree=0, minute=0, second=0):
"""
Initialize a GeoCoord object
Inputs:
degree (scalar) - integer or decimal degrees. If decimal degrees are given (e.g. 5.83),
the fractional values (0.83) will be added to the minute and second variables.
minute (scalar) - integer or decimal minutes. If decimal minutes are given (e.g. 49.17),
the fractional values (0.17) will be added to the second variable.
second (scalar) - decimal minutes.
"""
self.degree = float(degree)
self.minute = float(minute)
self.second = float(second)
self._update() # Clean up each variable and make them consistent
def set_minute(self, minute):
self.minute = float(minute)
def set_second(self, second):
self.second = float(second)
def set_degree(self, degree):
self.degree = float(degree)
@staticmethod
def _calc_decimaldegree(degree, minute, second):
"""
Calculate decimal degree form degree, minute, second
"""
return degree + minute / 60. + second / 3600.
@staticmethod
def _calc_degreeminutes(decimal_degree):
"""
Calculate degree, minute second from decimal degree
"""
sign = cmp(decimal_degree, 0) # Store whether the coordinate is negative or positive
decimal_degree = abs(decimal_degree)
degree = decimal_degree//1 # Truncate degree to be an integer
decimal_minute = (decimal_degree - degree)*60. # Calculate the decimal minutes
minute = decimal_minute//1 # Truncate minute to be an integer
second = (decimal_minute - minute)*60. # Calculate the decimal seconds
# Finally, re-impose the appropriate sign
degree = degree*sign
minute = minute*sign
second = second*sign
return degree, minute, decimal_minute, second
def _update(self):
"""
Given degree, minute, and second information, clean up the variables and make them
consistent (for example, if minutes > 60, add extra to degrees, or if degrees is
a decimal, add extra to minutes).
"""
self.decimal_degree = self._calc_decimaldegree(self.degree, self.minute, self.second)
self.degree, self.minute, self.decimal_minute, self.second = self._calc_degreeminutes(self.decimal_degree)
@abc.abstractmethod
def get_hemisphere(self):
"""
Dummy method, used in child classes such as Latitude and Longitude
"""
pass
@abc.abstractmethod
def set_hemisphere(self):
"""
Dummy method, used in child classes such as Latitude and Longitude
"""
pass
def to_string(self, format_str):
"""
Output lat, lon coordinates as string in chosen format
Inputs:
format (str) - A string of the form A%B%C where A, B and C are identifiers.
Unknown identifiers (e.g. ' ', ', ' or '_' will be inserted as separators
in a position corresponding to the position in format.
Examples:
>> palmyra = LatLon(5.8833, -162.0833)
>> palmyra.to_string('D') # Degree decimal output
('5.8833', '-162.0833')
>> palmyra.to_string('H% %D')
('N 5.8833', 'W 162.0833')
>> palmyra.to_string('d%_%M')
('5_52.998', '-162_4.998')
"""
format2value = {'H': self.get_hemisphere(),
'M': abs(self.decimal_minute),
'm': int(abs(self.minute)),
'd': int(self.degree),
'D': self.decimal_degree,
'S': abs(self.second)}
format_elements = format_str.split('%')
coord_list = [str(format2value.get(element, element)) for element in format_elements]
coord_str = ''.join(coord_list)
if 'H' in format_elements: # No negative values when hemispheres are indicated
coord_str = coord_str.replace('-', '')
return coord_str
def __cmp__(self, other):
return cmp(self.decimal_degree, other.decimal_degree)
def __neg__(self):
return GeoCoord(-self.decimal_degree)
def __pos__(self):
return GeoCoord(self.decimal_degree)
def __abs__(self):
self.__pos__()
return self
def __add__(self, other):
# other is a scalar
return GeoCoord(self.decimal_degree + other)
def __iadd__(self, other):
# other is a scalar
return self.__add__(other)
def __radd__(self, other):
# other is a scalar
return self.__add__(other)
def __sub__(self, other):
# other is a scalar
return self.__add__(-other)
def __isub__(self, other):
# other is a scalar
return self.__sub__(other)
def __rsub__(self, other):
# other is a scalar
return self.__sub__(other)
def __floor__(self):
return GeoCoord(math.floor(self.decimal_degree))
def __round__(self):
return GeoCoord(round(self.decimal_degree))
def __ceil__(self):
return GeoCoord(math.ceil(self.decimal_degree))
def __int__(self):
return self.degree
def __float__(self):
return self.decimal_degree
def __str__(self):
return str(self.decimal_degree)
def __repr__(self):
return str(self)
def type(self):
"""
Identifies the object type
"""
return 'GeoCoord'
class Latitude(GeoCoord):
"""
Coordinate object specific for latitude coordinates
"""
def get_hemisphere(self):
"""
Returns the hemisphere identifier for the current coordinate
"""
if self.decimal_degree < 0:
return 'S'
else:
return 'N'
def set_hemisphere(self, hemi_str):
"""
Given a hemisphere identifier, set the sign of the coordinate to match that hemisphere
"""
if hemi_str == 'S':
self.degree = abs(self.degree)*-1
self.minute = abs(self.minute)*-1
self.second = abs(self.second)*-1
self._update()
elif hemi_str == 'N':
self.degree = abs(self.degree)
self.minute = abs(self.minute)
self.second = abs(self.second)
self._update()
else:
raise ValueError('Hemisphere identifier for latitudes must be N or S')
def __repr__(self):
return 'Latitude %s' % (self.__str__())
class Longitude(GeoCoord):
"""
Coordinate object specific for longitude coordinates
Langitudes outside the range -180 to 180 (i.e. those reported in the range 0 to 360) will
automatically be converted to 0 to 360 to ensure that all operations such as hemisphere
assignment work as expected. To report in the range 0 to 360, use method range360()
"""
def __init__(self, degree=0, minute=0, second=0):
super(Longitude, self).__init__(degree, minute, second) # Initialize the GeoCoord
decimal_degree = self.range180() # Make sure that longitudes are reported in the range -180 to 180
self.degree, self.minute, self.decimal_minute, self.second = self._calc_degreeminutes(decimal_degree)
self._update()
def range180(self):
"""
Report longitudes using the range -180 to 180.
"""
return ((self.decimal_degree + 180) % 360) - 180
def range360(self):
"""
Report longitudes using the range 0 to 360
"""
return (self.decimal_degree + 360) % 360
def get_hemisphere(self):
"""
Returns the hemisphere identifier for the current coordinate
"""
if self.decimal_degree < 0:
return 'W'
else:
return 'E'
def set_hemisphere(self, hemi_str):
"""
Given a hemisphere identifier, set the sign of the coordinate to match that hemisphere
"""
if hemi_str == 'W':
self.degree = abs(self.degree)*-1
self.minute = abs(self.minute)*-1
self.second = abs(self.second)*-1
self._update()
elif hemi_str == 'E':
self.degree = abs(self.degree)
self.minute = abs(self.minute)
self.second = abs(self.second)
self._update()
else:
raise(ValueError, 'Hemisphere identifier for longitudes must be E or W')
def __repr__(self):
return 'Longitude %s' % (self.__str__())
def string2geocoord(coord_str, coord_class, format_str='D'):
"""
Create a GeoCoord object (e.g. Latitude or Longitude) from a string.
Inputs:
coord_str (str) - a string representation of a geographic coordinate (e.g. '5.083 N'). Each
section of the string must be separated by some kind of a separator character ('5.083N' is
invalid).
coord_class (class) - a class inheriting from GeoCoord that includes a set_hemisphere method.
Can be either Latitude or Longitude
format_str (str) - a string representation of the sections of coord_str. Possible letter values
correspond to the keys of the dictionary format2value, where
'H' is a hemisphere identifier (e.g. N, S, E or W)
'D' is a coordinate in decimal degrees notation
'd' is a coordinate in degrees notation
'M' is a coordinate in decimal minutes notation
'm' is a coordinate in minutes notation
'S' is a coordinate in seconds notation
Any other characters (e.g. ' ' or ', ') will be treated as a separator between the above components.
All components should be separated by the '%' character. For example, if the coord_str is
'5, 52, 59.88_N', the format_str would be 'd%, %m%, %S%_%H'
Returns:
GeoCoord object initialized with the coordinate information from coord_str
"""
new_coord = coord_class()
# Dictionary of functions for setting variables in the coordinate class:
format2value = {'H': new_coord.set_hemisphere,
'M': new_coord.set_minute,
'm': new_coord.set_minute,
'd': new_coord.set_degree,
'D': new_coord.set_degree,
'S': new_coord.set_second}
if format_str[0] == 'H':
'''Having the hemisphere identifier at the beginning is problematic for ensuring that
the final coordinate value will be negative. Instead, change the identifier and the
format string so that the hemisphere is identified at the end:'''
new_coord_start = re.search('\d', coord_str).start() # Find the beginning of the coordinate
new_format_start = re.search('[a-gi-zA-GI-Z]', format_str).start() # Find the first non-hemisphere identifier
# Move hemisphere identifier to the back
format_str = '% %'.join((format_str[new_format_start:], format_str[0]))
coord_str = ' '.join((coord_str[new_coord_start:], coord_str[0])) # Move hemisphere identifier to the back
format_elements = format_str.split('%')
separators = [sep for sep in format_elements if sep not in format2value.keys()] # E.g. ' ', '_' or ', ' characters
separators.append('%') # Dummy separator for the final part of the coord_str
formatters = [form for form in format_elements if form in format2value.keys()] # E.g. 'D', 'm', or 'S' characters
for form, sep in zip(formatters, separators):
coord_elements = coord_str.split(sep)
# Set the coordinate variable (e.g. 'self.degree' with the coordinate substring (e.g. '5')
format2value[form](coord_elements[0])
# Get rid of parts of the substring that have already been done
coord_str = sep.join(coord_elements[1:])
new_coord._update() # Change all of the variables in the coordinate class so they are consistent with each other
return new_coord
class LatLon:
__name__ = 'LatLon'
"""
Object representing lat/lon pairs
"""
def __init__(self, lat, lon, name=None):
"""
Input:
lat (class instance or scalar) - an instance of class Latitude or a scalar. A Latitude object
can be instantiated directly in the __init__ call for example by calling LatLon(Latitude(5.8),
Longitude(162.5)). If lat is specified as a scalar, the scalar will be assumed to be in
decimal degrees.
lon (class instance or scalar) - an instance of class Longitude or a scalar. If lat is
specified as a scalar, the scalar will be assumed to be in decimal degrees.
name (str) - an identifier
"""
try:
if lat.type() == 'GeoCoord':
self.lat = lat
else:
raise AttributeError
except AttributeError:
self.lat = Latitude(lat)
try:
if lon.type() == 'GeoCoord':
self.lon = lon
else:
raise AttributeError
except AttributeError:
self.lon = Longitude(lon)
self.name = name
def project(self, projection):
"""
Return coordinates transformed to a given projection
Projection should be a basemap or pyproj projection object or similar
"""
x, y = projection(self.lon.decimal_degree, self.lat.decimal_degree)
return x, y
def complex(self):
"""
Return lat/lon pairs as complex coordinates
"""
return self.lat.decimal_degree + 1j * self.lon.decimal_degree
def _pyproj_inv(self, other, ellipse='WGS84'):
"""
Perform Pyproj's inv operation on two LatLon objects
Returns the initial heading and reverse heading in degrees, and the distance
in km.
"""
lat1, lon1 = self.lat.decimal_degree, self.lon.decimal_degree
lat2, lon2 = other.lat.decimal_degree, other.lon.decimal_degree
g = pyproj.Geod(ellps=ellipse)
heading_initial, heading_reverse, distance = g.inv(lon1, lat1, lon2, lat2, radians=False)
distance = distance/1000.0
if heading_initial == 0.0: # Reverse heading not well handled for coordinates that are directly south
heading_reverse = 180.0
return {'heading_initial': heading_initial, 'heading_reverse': heading_reverse, 'distance': distance}
def heading_initial(self, other, **kwargs):
"""
Returns initial bearing between two LatLon objects in degrees using pyproj.
Assumes the WGS84 ellipsoid by default. Choose ellipse = 'sphere'
for the FAI ellipsoid.
"""
return self._pyproj_inv(other, **kwargs)['heading_initial']
def heading_reverse(self, other, **kwargs):
"""
Returns reverse bearing between two LatLon objects in degrees using pyproj.
Assumes the WGS84 ellipsoid by default. Choose ellipse = 'sphere'
for the FAI ellipsoid.
"""
return self._pyproj_inv(other, **kwargs)['heading_reverse']
def distance(self, other, **kwargs):
"""
Returns great circle distance between two LatLon objects in km using pyproj.
Assumes the WGS84 ellipsoid by default. Choose ellipse = 'sphere'
for the FAI ellipsoid.
"""
return self._pyproj_inv(other, **kwargs)['distance']
def distance_sphere(self, other, radius=6371.0):
"""
-- Deprecated in v0.70. Use distance(other, ellipse = 'sphere') instead --
Returns great circle distance between two lat/lon coordinates on a sphere
using the Haversine formula. The default radius corresponds to the FAI sphere
with units in km.
"""
warnings.warn("Deprecated in v0.70. Use distance(other, ellipse = 'sphere') instead",
DeprecationWarning)
lat1, lon1 = self.lat.decimal_degree, self.lon.decimal_degree
lat2, lon2 = other.lat.decimal_degree, other.lon.decimal_degree
pi = math.pi/180.
# phi is 90 - latitude
phi1 = (90. - lat1) * pi
phi2 = (90. - lat2) * pi
# theta is longitude
theta1 = lon1 * pi
theta2 = lon2 * pi
cos = (math.sin(phi1) * math.sin(phi2) * math.cos(theta1 - theta2) +
math.cos(phi1) * math.cos(phi2))
arc = math.acos(cos)
return arc*radius
def offset(self, heading_initial, distance, ellipse='WGS84'):
"""
Offset a LatLon object by a heading (in degrees) and distance (in km)
to return a new LatLon object
"""
lat1, lon1 = self.lat.decimal_degree, self.lon.decimal_degree
g = pyproj.Geod(ellps=ellipse)
distance = distance * 1000 # Convert km to meters
lon2, lat2, back_bearing = g.fwd(lon1, lat1, heading_initial, distance, radians=False)
return LatLon(Latitude(lat2), Longitude(lon2))
def to_string(self, formatter='D'):
"""
Return string representation of lat and lon as a 2-element tuple
using the format specified by formatter
"""
return self.lat.to_string(formatter), self.lon.to_string(formatter)
def _sub_vector(self, other):
"""
Called when subtracting a GeoVector object from self
"""
heading, distance = other()
heading = (heading + 180) % 360 # Flip heading
new_latlon = LatLon(self.lat, self.lon) # Copy current position
return new_latlon.offset(heading, distance) # Offset position by GeoVector
def _sub_latlon(self, other):
"""
Called when subtracting a LatLon object from self
"""
inv = self._pyproj_inv(other)
heading = inv['heading_reverse']
distance = inv['distance']
return GeoVector(initial_heading = heading, distance = distance)
def almost_equal(self, other, e = 0.000001):
"""
Sometimes required for comparing LatLon coordinates if float error has
occurred. Determine if self and other (another LatLon coordinate) are
equal to within e km of each other. The default (e = 0.000001) will return
True if self and other are less than 1 mm apart in distance.
"""
return (self - other).magnitude < e
def __eq__(self, other):
# other is a LatLon object
if self.lat == other.lat and self.lon == other.lon:
return True
else:
return False
def __ne__(self, other):
# other is a LatLon object
return not self.__eq__(other)
def __add__(self, other):
# other is a GeoVector object
heading, distance = other()
new_latlon = LatLon(self.lat, self.lon)
return new_latlon.offset(heading, distance)
def __iadd__(self, other):
# other is a GeoVector object
return self.__add__(other)
def __radd__(self, other):
# other is a GeoVector object
return self.__add__(other)
def __sub__(self, other):
# if other is a GeoVector, will return LatLon object
# if other is a LatLon, will return GeoVector object
object_operator = {'GeoVector': self._sub_vector,
'LatLon': self._sub_latlon}
return object_operator[other.type()](other)
def __isub__(self, other):
# other is a GeoVector object
return self.__sub__(other)
def __rsub__(self, other):
# other is a GeoVector object
return self.__sub__(other)
def __str__(self):
return '%s, %s' % (str(self.lat), str(self.lon))
def __repr__(self):
return '%s, %s' % (repr(self.lat), repr(self.lon))
def __complex__(self):
return self.complex()
def type(self):
"""
Identifies the object type
"""
return 'LatLon'
def string2latlon(lat_str, lon_str, format_str):
"""
Create a LatLon object from a pair of strings.
Inputs:
lat_str (str) - string representation of a latitude (e.g. '5 52 59.88 N')
lon_str (str) - string representation of a longitude (e.g. '162 4 59.88 W')
format_str (str) - format in which the coordinate strings are given (e.g.
for the above examples this would be 'd% %m% %S% %H'). See function
string2geocoord for a detailed explanation on how to specify formats.
Returns:
A LatLon object initialized with coordinate data from lat_str and lon_str
"""
lat = string2geocoord(lat_str, Latitude, format_str)
lon = string2geocoord(lon_str, Longitude, format_str)
new_latlon = LatLon(lat=lat, lon=lon)
return new_latlon
class GeoVector:
__name__ = 'GeoVector'
"""
Object representing the distance and heading between two lat/lon coordinates
Can be created by:
1. Passing dx and dy arguments
2. Passing initial_heading and distance keyword arguments
3. Subtracting two LatLon objects
"""
def __init__(self, dx=None, dy=None, initial_heading=None, distance=None):
"""
Create a GeoVector object
Inputs:
dx (scalar) - the zonal component of a vector in km
dy (scalar) - the meridional component of a vector in km
initial_heading (scalar) - the initial heading of the vector in degrees
distance (scalar) - the magnitude of the vector in km
"""
if dx is None and dy is None: # If only initial_heading and distance are given
self.heading = initial_heading
theta = self._angle_or_heading(self.heading) # Convert heading to angle
theta_rad = math.radians(theta)
self.magnitude = distance
self.dx = self.magnitude * math.cos(theta_rad)
self.dy = self.magnitude * math.sin(theta_rad)
elif initial_heading is None and distance is None: # If only dx and dy are given
self.dx = dx
self.dy = dy
self._update()
else:
raise NameError('Class GeoVector requires two arguments (dx and dy or initial_heading and distance)')
def __call__(self):
return self.heading, self.magnitude
def _angle_or_heading(self, angle_or_heading):
"""
Convert angle degrees (i.e. starting at coordinates (1, 0) or
due East and going clockwise to 360) into heading (i.e. starting
at coordinates (0, 1) or due North and going counterclockwise to
360) or vice versa.
"""
heading_or_angle = (90 - angle_or_heading) % 360
return heading_or_angle
def _update(self):
"""
Calculate heading and distance from dx and dy
"""
try:
theta_radians = math.atan(float(self.dy) / self.dx)
except ZeroDivisionError:
if self.dy > 0:
theta_radians = 0.5 * math.pi
elif self.dy < 0:
theta_radians = 1.5 * math.pi
self.magnitude = self.dy
else:
self.magnitude = 1. / (math.cos(theta_radians)) * self.dx
theta = math.degrees(theta_radians)
self.heading = self._angle_or_heading(theta) # Convert angle to heading
def almost_equals(self, other, e=0.000001):
"""
Sometimes required for comparing GeoVectors if float error has
occurred. Determine if self and other (another GeoVector) are
equal to within e km of each other in dx and dy. The default
(e = 0.000001) will return True if self and other are less than
1 mm apart in distance.
"""
return abs(self.dx - other.dx) < e and abs(self.dy - other.dy) < e
def __neg__(self):
return GeoVector(-self.dx, -self.dy)
def __add__(self, other):
# other is a GeoVector object
return GeoVector(self.dx + other.dx, self.dy + other.dy)
def __iadd__(self, other):
# other is a GeoVector object
return self.__add__(other)
def __radd__(self, other):
# other is a GeoVector object
return self.__add__(other)
def __sub__(self, other):
# other is a GeoVector object
return self.__add__(-other)
def __isub__(self, other):
# other is a GeoVector object
return self.__sub__(other)
def __rsub__(self, other):
# other is a GeoVector object
return self.__sub__(other)
def __cmp__(self, other):
return cmp(self.magnitude, other.magnitude)
def __pos__(self):
return GeoVector(self.dx, self.dy)
def __abs__(self):
return self.__pos__()
def __mul__(self, other):
# other is a scalar
return GeoVector(initial_heading=self.heading, distance=self.magnitude * other)
def __imul__(self, other):
# other is a scalar
return self.__mul__(other)
def __rmul__(self, other):
# other is a scalar
return self.__mul__(other)
def __div__(self, other):
# other is a scalar
return GeoVector(initial_heading=self.heading, distance=self.magnitude / other)
def __truediv__(self, other):
# other is a scalar
return GeoVector(initial_heading=self.heading, distance=self.magnitude / other)
def __idiv__(self, other):
# other is a scalar
return self.__div__(other)
def __rdiv__(self, other):
# other is a scalar
return self.__div__(other)
def __rfloordiv__(self, other):
# other is a scalar
return self.__truediv__(other)
def __str__(self):
return '%s, %s' % (self.heading, self.magnitude)
def __repr__(self):
return 'Heading %s, Distance %s' % (self.heading, self.magnitude)
def type(self):
"""
Identifies the object type
"""
return 'GeoVector'
def demonstration():
palmyra = LatLon(Latitude(5.8833), Longitude(-162.0833)) # Try instantiating Latitude and Longitude objects in call
print(repr(palmyra)) # Returns 'Latitude 5.8833, Longitude -162.0833'
palmyra = LatLon(5.8833, -162.0833) # Or even simpler - initialize from two scalars expressing decimal degrees
print(str(palmyra)) # Returns '5.8833, -162.0833'
# or more complicated!
palmyra = LatLon(Latitude(degree=5, minute=52, second=59.88), Longitude(degree=-162, minute=-4.998))
# Print coordinates to degree minute second (returns ('5 52 59.88 N', '162 4 59.88 W'))
print(palmyra.to_string('d% %m% %S% %H'))
palmyra = string2latlon('5 52 59.88 N', '162 4 59.88 W', 'd% %m% %S% %H') # Initialize from more complex string
# Print coordinates as degree minutes separated by underscore (returns ('5_52.998', '-162_4.998'))
print(palmyra.to_string('d%_%M'))
palmyra = string2latlon('N 5, 52.998', 'W 162, 4.998', 'H% %d%, %M') # An alternative complex string
print(palmyra.to_string('D')) # Print coordinate to decimal degrees (returns ('5.8833', '-162.0833'))
honolulu = LatLon(Latitude(21.3), Longitude(-157.8167))
print(palmyra.distance(honolulu, ellipse='sphere')) # FAI distance is 1774.77188181 km
distance = palmyra.distance(honolulu) # WGS84 distance is 1766.69130376 km
print(distance)
initial_heading = palmyra.heading_initial(honolulu) # Initial heading to Honolulu on WGS84 ellipsoid
print(initial_heading)
hnl = palmyra.offset(initial_heading, distance) # Reconstruct lat/lon for Honolulu based on offset from Palmyra
print(hnl.to_string('D')) # Coordinates of Honolulu are latitude 21.3, longitude -157.8167
# A GeoVector with heading equal to the vector between palmyra and honolulu, but 2x the magnitude
vector = (honolulu - palmyra) * 2
print(vector) # Print heading and magnitude
print(palmyra + (vector / 2.0)) # Recreate the coordinates of Honolulu by adding half of vector to palmyra
print('Finished running demonstration!')
if __name__ == '__main__':
demonstration()