1#!/usr/local/bin/python3.8
2#
3# Test gps/clienthelpers.py
4#
5# This code runs compatibly under Python 2 and 3.x for x >= 2.
6# Preserve this property!
7"""Partial test suite for gps.clienthelpers module."""
8
9from __future__ import absolute_import, print_function, division
10
11import math               # for math.fabs()
12import os                 # for os.environ()
13import sys                # for stderr, etc.
14
15import gps.clienthelpers
16import gps.misc
17
18debug = 0
19
20test1 = [(0, 0, "  0.00000000"),          # deg_dd
21         (0, 89.999, " 89.99900000"),
22         (0, 90.1, " 90.10000000"),
23         (0, 180.21, "180.21000000"),
24         (0, 359.321, "359.32100000"),
25         (0, 360.0, "  0.00000000"),
26         (1, 0, "  0 00.000000'"),        # deg_ddmm
27         (1, 89.999, " 89 59.940000'"),
28         (1, 90.1, " 90 06.000000'"),
29         (1, 180.21, "180 12.600000'"),
30         (1, 359.321, "359 19.260000'"),
31         (1, 360.0, "  0 00.000000'"),
32         (2, 0, "  0 00' 00.00000\""),    # deg_ddmmss
33         (2, 89.999, " 89 59' 56.40000\""),
34         (2, 90.1, " 90 06' 00.00000\""),
35         (2, 180.21, "180 12' 36.00000\""),
36         (2, 359.321, "359 19' 15.60000\""),
37         (2, 360.0, "  0 00' 00.00000\""),
38         ]
39
40# maidenhead
41# keep in sync with tests/test_gpsdclient.c
42test2 = [(48.86471, 2.37305, "JN18eu", "Paris"),
43         (41.93498, 12.43652, "JN61fw", "Rome"),
44         (39.9771, -75.1685, "FM29jx", "Philadelphia"),
45         (-23.4028, -50.9766, "GG46mo", "Sao Paulo"),
46         (90, 180, "RR99xx", "North Pole"),
47         (-90, -180, "AA00aa", "South Pole"),
48         ]
49
50test3 = [
51    #  wgs84 separation, cm precision
52    #  online calculator:
53    #  https://geographiclib.sourceforge.io/cgi-bin/GeoidEval
54    #
55    # magnetic variation, hundredths of a degree precision.
56    #
57    # same tests as tests/test_geoid.c.  Keep them in sync.
58    #
59
60    # Easter Island: EGM2008 -3.8178, EGM96 -4.9979, EGM84 -5.1408
61    # wmm2015 2.90
62    (27.1127, -109.3497, -31.29, 8.45, "Easter Island"),
63    # Kalahari: EGM2008, 20.6560, EGM96, 20.8419, EGM84 23.4496
64    # wmm2015 6.73
65    (25.5920, 21.0937, 21.80, 3.35,  "Kalahari Desert"),
66    # Greenland: EGM2008 40.3981, EGM96 40.5912, EGM84 41.7056
67    # wmm2015 28.26  AWEFUL!
68    (71.7069, -42.6043, 40.11, -28.25, "Greenland"),
69    # Kuk Swamp: EGM2008 62.8837, EGM96, 62.8002, EGM84, 64.5655
70    # wmm2015 9.11
71    # seems to be over a gravitational anomaly
72    (5.7837, 144.3317, 62.34, 2.42, "Kuk Swamp PG"),
73    # KBDN: EGM2008 -20.3509, EGM96 -19.8008, EGM84 -18.5562
74    # wmm2015 14.61
75    (44.094556, -121.200222, -21.95, 14.40, "Bend Airport, OR (KBDN)"),
76    # PANC: EGM2008 7.6508, EGM96 7.8563, EGM84 8.0838
77    # wmm2015 15.78
78    (61.171333, -149.991164, 13.54, 15.52, "Anchorage Airport, AK (PANC)"),
79    # KDEN: EGM2008 -18.1941, EGM96 -18.4209, EGM84 -15.9555
80    # wmm2015 7.95
81    (39.861666, -104.673166, -18.15, 7.84, "Denver Airport, CO (KDEN)"),
82    # LHR: EGM2008 46.4499, EGM96 46.3061, EGM84 47.7620
83    # wmm2015 0.17
84    (51.46970, -0.45943, 46.26, -0.28, "London Heathrow Airport, UK (LHR)"),
85    # SCPE: EGM2008 37.9592, EGM96 39.3400, EGM84 46.6604
86    # wmm2015 -6.17
87    (-22.92170, -68.158401, 35.46, -6.11,
88     "San Pedro de Atacama Airport, CL (SCPE)"),
89    # SIN: EGM2008 8.6453, EGM96 8.3503, EGM84 8.2509
90    # wmm2015 0.22
91    (1.350190, 103.994003, 7.51, 0.17, "Singapore Changi Airport, SG (SIN)"),
92    # UURB: EGM2008 13.6322, EGM96 13.6448, EGM84 13.1280
93    # wmm2015 11.41
94    (55.617199, 38.06000, 13.22, 11.42, "Moscow Bykovo Airport, RU (UUBB)"),
95    # SYD: EGM2008 13.0311, EGM96 13.3736, EGM84 13.3147
96    # wmm2015 -4.28
97    (33.946098, 151.177002, 13.59, -4.26, "Sydney Airport, AU (SYD)"),
98    # Doyle: EGM2008 -23.3366, EGM96 -23.3278, EGM84 -21.1672
99    # wmm2015 13.35
100    (40, -120, -23.34, 13.35, "Near Doyle, CA"),
101
102    # test calc at delta lat == 0
103    # North Poll: EGM2008 14.8980, EGM96 13.6050, EGM84 13.0980
104    # wmm2015 1.75
105    (90, 0, 14.90, 1.75, "North Poll 0"),
106    # wmm2015 3.75
107    (90, 2, 14.90, 3.75, "North Poll 2"),
108    # wmm2015 4.25
109    (90, 2.5, 14.90, 4.25, "North Poll 2.5"),
110    # wmm2015 1.75
111    (90, 3, 14.90, 4.75, "North Poll 3"),
112    # wmm2015 1.75
113    (90, 5, 14.90, 6.75, "North Poll 5"),
114    # wmm2015 -178.25
115    (90, 180, 14.90, -178.25, "North Poll"),
116
117    # Equator 0, EGM2008 17.2260, EGM96 17.1630, EGM84 18.3296
118    # wmm2015 -4.84
119    (0, 0, 17.23, -4.84, "Equator 0W"),
120
121    # South Poll: EGM2008 -30.1500, EGM96 -29.5350, EGM84 -29.7120
122    # wmm2015 -30.80
123    (-90, 0, -30.15, -30.80, "South Poll"),
124
125    # test calc at delta lon == 0
126    # 2 0: EGM2008 17.1724, EGM96 16.8962, EGM84 17.3676
127    # wmm2015 -4.17
128    (2, 0, 18.42, -4.23, "2N 0W"),
129    # 2.5 0: EGM2008 16.5384, EGM96 16.5991, EGM84 17.0643
130    # wmm2015 -4.02
131    (2.5, 0, 18.71, -4.08, "2.5N 0W"),
132    # 3 0: EGM2008 16.7998, EGM96 16.6161, EGM84 16.7857
133    # wmm2015 -3.87
134    (3, 0, 19.01, -3.92, "3N 0W"),
135    # 3.5 0: EGM2008 17.0646, EGM96 17.0821, EGM84 16.7220
136    # wmm2015 -3.72
137    (3.5, 0, 19.31, -3.77, "3.5N 0W"),
138    # 5 0: EGM2008 20.1991, EGM96 20.4536, EGM84 20.3181
139    # wmm2015 -3.31
140    (5, 0, 20.20, -3.31, "5N 0W"),
141
142    # test calc on diagonal
143    # Equator 0, EGM2008 17.2260, EGM96 17.1630, EGM84 18.3296
144    # wmm2015 -4.84
145    # 2 2: EGM2008 16.2839, EGM96 16.1579, EGM84 17.5354
146    # wmm2015 -3.53
147    (2, 2, 18.39, -3.60, "2N 2E"),
148    # 2.5 2.5: EGM2008 15.7918, EGM96 15.5314, EGM84 16.3230
149    # wmm2015 -3.24
150    (2.5, 2.5, 18.78, -3.30, "2.5N 2.5E"),
151    # 3 3: EGM2008 15.2097, EGM96 15.0751, EGM84 14.6542
152    # wmm2015 -2.95
153    (3, 3, 19.20, -3.01, "3N 3E"),
154    # 3.5 3.5: EGM2008 14.8706, EGM96 14.6668, EGM84 13.9592
155    # wmm2015 -3.72
156    (3.5, 3.5, 19.66, -2.73, "3.5N 3.5E"),
157
158    # some 5x5 points, s/b exact EGM2008, +/- rounding
159    # 5, 5:  EGM2008 21.2609, EGM96 20.8917, EGM84 20.3509
160    # wmm2015 -1.91
161    (5, 5, 21.26, -1.91, "5, 5"),
162    # -5, -5: EGM2008 17.1068, EGM96 16.8362, EGM84 17.5916
163    # wmm2015 -9.03
164    (-5, -5, 17.11, -9.03, "-5, -5"),
165    # -5, 5: EGM2008 9.3988, EGM96 9.2399, EGM84 9.7948
166    # wmm2015 -4.80
167    (-5, 5, 9.40, -4.80, "-5, 5"),
168    # 5, -5: EGM2008 25.7668, EGM96 25.6144, EGM84 25.1224
169    # wmm2015 -4.90
170    (5, -5, 25.77, -4.90, "5, 5"),
171
172    # test data for some former corners in the code
173    # 0, -78.452222: EGM2008 26.8978, EGM96 25.3457, EGM84 26.1507
174    # wmm2015 -3.87
175    (0, -78.452222, 15.98, -3.89, "Equatorial Sign Bolivia"),
176    # 51.4778067, 0: EGM2008 45.8961, EGM96 45.7976, EGM84 47.2468
177    # wmm2015 -0.10
178    (51.4778067, 0, 45.46, -0.11, "Lawn Greenwich Observatory UK"),
179    # 0, 180: EGM2008 21.2813, EGM96 21.1534, EGM84 21.7089
180    # wmm2015 9.75
181    (0, 180, 21.28, 9.75, "Far away from Google default"),
182    (0, -180, 21.28, 9.75, "Away far from Google default"),
183]
184
185# gpsd gpsd_units
186test4 = [('GPSD_UNITS', 'imperial', gps.clienthelpers.imperial),
187         ('GPSD_UNITS', 'nautical', gps.clienthelpers.nautical),
188         ('GPSD_UNITS', 'metric', gps.clienthelpers.metric),
189         ('LC_MEASUREMENT', 'en_US', gps.clienthelpers.imperial),
190         ('LC_MEASUREMENT', 'C', gps.clienthelpers.imperial),
191         ('LC_MEASUREMENT', 'POSIX', gps.clienthelpers.imperial),
192         ('LC_MEASUREMENT', 'ru_RU', gps.clienthelpers.metric),
193         ('LANG', 'en_US', gps.clienthelpers.imperial),
194         ('LANG', 'C', gps.clienthelpers.imperial),
195         ('LANG', 'POSIX', gps.clienthelpers.imperial),
196         ('LANG', 'ru_RU', gps.clienthelpers.metric),
197         ]
198
199errors = 0
200
201for test in test1:
202    (deg_type, deg, expected) = test
203    result = gps.clienthelpers.deg_to_str(deg_type, deg)
204    if result != expected:
205        print("fail: deg_to_str(%d, %.3f) got %s expected %s" %
206              (deg_type, deg, result, expected))
207        errors += 1
208
209for (lat, lon, maidenhead, location) in test2:
210    converted = gps.clienthelpers.maidenhead(lat, lon)
211    if converted != maidenhead:
212        sys.stderr.write(
213            "fail: maidenhead test%s, %s (%s)) expected %s got %s\n" %
214            (lat, lon, maidenhead, location, converted))
215        errors += 1
216
217# check wgs84_separation()
218for (lat, lon, wgs84, var, desc) in test3:
219    separation = gps.clienthelpers.wgs84_separation(lat, lon)
220    # check to 1 millimeter
221    diff = separation - wgs84
222    if debug:
223        print("diff %f sep %f wgs84 %f" % (diff, separation, wgs84))
224    if 0.009 < math.fabs(diff):
225        sys.stderr.write(
226            "fail: wgs84_separation(%s, %s) (%s) expected %.2f got %.2f\n" %
227            (lat, lon, desc, wgs84, separation))
228        errors += 1
229
230# check mag_var()
231for (lat, lon, wgs84, var, desc) in test3:
232    magvar = gps.clienthelpers.mag_var(lat, lon)
233    # check to 0.1 degree
234    diff = magvar - var
235    if debug:
236        print("diff %f magvar %f s/b %f" % (diff, magvar, var))
237    if 0.09 < math.fabs(diff):
238        sys.stderr.write(
239            "fail: mag_var(%s, %s) (%s) expected %.2f got %.2f\n" %
240            (lat, lon, desc, var, magvar))
241        errors += 1
242
243
244savedenv = os.environ
245# from the python doc:
246# calls to unsetenv() don't update os.environ, so it is actually
247# preferable to delete items of os.environ.
248for key in ['GPSD_UNITS', 'LC_MEASUREMENT', 'LANG']:
249    if key in os.environ:
250        del os.environ[key]
251
252for (key, val, expected) in test4:
253    os.environ[key] = val
254
255    result = gps.clienthelpers.gpsd_units()
256    del os.environ[key]
257
258    if result != expected:
259        print("fail: gpsd_units() %s=%s got %s expected %d" %
260              (key, val, str(result), expected))
261        errors += 1
262
263# restore environment
264os.environ = savedenv
265
266if errors:
267    print("test_clienthelpers.py: %d tests failed" % errors)
268    sys.exit(1)
269else:
270    print("test_clienthelpers.py: OK")
271    sys.exit(0)
272