From 28b2d2543f82cc39cdcfd86de85db1b5aa7d3f96 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Mon, 13 Jun 2022 16:36:16 -0500 Subject: [PATCH 1/5] Add handling with extra precision in lat/long --- adafruit_gps.py | 34 +++++++++++++++++++++++++++++----- examples/gps_simpletest.py | 10 ++++++++++ tests/adafruit_gps_test.py | 6 +++--- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/adafruit_gps.py b/adafruit_gps.py index 36a17d2..92105e7 100644 --- a/adafruit_gps.py +++ b/adafruit_gps.py @@ -80,10 +80,15 @@ def _parse_degrees(nmea_data): # Where ddd is the degrees, mm.mmmm is the minutes. if nmea_data is None or len(nmea_data) < 3: return None - raw = float(nmea_data) - deg = raw // 100 - minutes = raw % 100 - return deg + minutes / 60 + # To avoid losing precision handle degrees and minutes separately + # Return the final value as an integer. Further functions can parse + # this into a float or separate parts to retain the precision + raw = nmea_data.split(".") + deg = int(raw[0]) // 100 * 1000000 # the ddd + minutes = int(raw[0]) % 100 * 10000 # the mm. + minutes += int(raw[1]) # the .mmmm + minutes = int(minutes / 60 * 100) + return deg + minutes def _parse_int(nmea_data): @@ -105,12 +110,21 @@ def _parse_str(nmea_data): def _read_degrees(data, index, neg): - x = data[index] + # This function loses precision with float32 + x = data[index] / 1000000 if data[index + 1].lower() == neg: x *= -1.0 return x +def _read_int_degress(data, index, neg): + deg = data[index] // 1000000 + minutes = data[index] % 1000000 / 10000 + if data[index + 1].lower() == neg: + deg *= -1 + return (deg, minutes) + + def _parse_talker(data_type): # Split the data_type into talker and sentence_type if data_type[:1] == b"P": # Proprietary codes @@ -208,7 +222,11 @@ def __init__(self, uart, debug=False): # Initialize null starting values for GPS attributes. self.timestamp_utc = None self.latitude = None + self.latitude_degrees = None + self.latitude_minutes = None # Use for full precision minutes self.longitude = None + self.longitude_degrees = None + self.longitude_minutes = None # Use for full precision minutes self.fix_quality = 0 self.fix_quality_3d = 0 self.satellites = None @@ -424,9 +442,11 @@ def _parse_gll(self, data): # Latitude self.latitude = _read_degrees(data, 0, "s") + self.latitude_degrees, self.latitude_minutes = _read_int_degress(data, 0, "s") # Longitude self.longitude = _read_degrees(data, 2, "w") + self.longitude_degrees, self.longitude_minutes = _read_int_degress(data, 2, "w") # UTC time of position self._update_timestamp_utc(data[4]) @@ -462,9 +482,11 @@ def _parse_rmc(self, data): # Latitude self.latitude = _read_degrees(data, 2, "s") + self.latitude_degrees, self.latitude_minutes = _read_int_degress(data, 2, "s") # Longitude self.longitude = _read_degrees(data, 4, "w") + self.longitude_degrees, self.longitude_minutes = _read_int_degress(data, 4, "w") # Speed over ground, knots self.speed_knots = data[6] @@ -498,9 +520,11 @@ def _parse_gga(self, data): # Latitude self.latitude = _read_degrees(data, 1, "s") + self.latitude_degrees, self.latitude_minutes = _read_int_degress(data, 1, "s") # Longitude self.longitude = _read_degrees(data, 3, "w") + self.longitude_degrees, self.longitude_minutes = _read_int_degress(data, 3, "w") # GPS quality indicator # 0 - fix not available, diff --git a/examples/gps_simpletest.py b/examples/gps_simpletest.py index 4e41a8d..34af8f8 100644 --- a/examples/gps_simpletest.py +++ b/examples/gps_simpletest.py @@ -82,6 +82,16 @@ ) print("Latitude: {0:.6f} degrees".format(gps.latitude)) print("Longitude: {0:.6f} degrees".format(gps.longitude)) + print( + "Precise Latitude: {:2.}{:2.4f} degrees".format( + gps.latitude_degrees, gps.latitude_minutes + ) + ) + print( + "Precise Longitude: {:2.}{:2.4f} degrees".format( + gps.longitude_degrees, gps.longitude_minutes + ) + ) print("Fix quality: {}".format(gps.fix_quality)) # Some attributes beyond latitude, longitude and timestamp are optional # and might not be present. Check if they're None before trying to use! diff --git a/tests/adafruit_gps_test.py b/tests/adafruit_gps_test.py index 8a956f3..926e4af 100644 --- a/tests/adafruit_gps_test.py +++ b/tests/adafruit_gps_test.py @@ -21,9 +21,9 @@ @pytest.mark.parametrize( ("val", "exp"), ( - pytest.param("0023.456", 0.390933, id="leading zero"), - pytest.param("6413.9369", 64.23228, id="regular value"), - pytest.param("2747.416122087989", 27.79027, id="long value"), + pytest.param("0023.456", 390933, id="leading zero"), + pytest.param("6413.9369", 64232281, id="regular value"), + pytest.param("2747.416122087989", 27790268, id="long value"), ), ) def test_parse_degrees(val, exp): From d22cc67f5c365d6c08ee961df0b65957bcab263a Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Mon, 13 Jun 2022 22:30:35 -0500 Subject: [PATCH 2/5] Fix to handle other nmea data string lengths --- adafruit_gps.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/adafruit_gps.py b/adafruit_gps.py index 92105e7..114fa8f 100644 --- a/adafruit_gps.py +++ b/adafruit_gps.py @@ -85,9 +85,9 @@ def _parse_degrees(nmea_data): # this into a float or separate parts to retain the precision raw = nmea_data.split(".") deg = int(raw[0]) // 100 * 1000000 # the ddd - minutes = int(raw[0]) % 100 * 10000 # the mm. - minutes += int(raw[1]) # the .mmmm - minutes = int(minutes / 60 * 100) + minutes = int(raw[0]) % 100 # the mm. + minutes += int(f"{raw[1][:4]:0<4}") / 10000 # the .mmmm + minutes = int(minutes / 60 * 1000000) return deg + minutes From 4ccc6b963034dfff4c0b71ba28d49a843589bc74 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Mon, 13 Jun 2022 22:31:18 -0500 Subject: [PATCH 3/5] Changed tests as internal representation of degrees changed --- tests/adafruit_gps_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/adafruit_gps_test.py b/tests/adafruit_gps_test.py index 926e4af..eb8de9b 100644 --- a/tests/adafruit_gps_test.py +++ b/tests/adafruit_gps_test.py @@ -61,10 +61,10 @@ def test_parse_float_invalid(val): @pytest.mark.parametrize( ("data", "neg", "exp"), ( - pytest.param([27.79027, "S"], "s", -27.79027, id="south negative"), - pytest.param([64.23228, "N"], "s", 64.23228, id="north not negative"), - pytest.param([123.4567, "W"], "w", -123.4567, id="west negative"), - pytest.param([10.7891, "E"], "w", 10.7891, id="east not negative"), + pytest.param([27790270, "S"], "s", -27.79027, id="south negative"), + pytest.param([64232280, "N"], "s", 64.23228, id="north not negative"), + pytest.param([123456700, "W"], "w", -123.4567, id="west negative"), + pytest.param([10789100, "E"], "w", 10.7891, id="east not negative"), ), ) def test_read_degrees(data, neg, exp): From 9f90c48e1747ce52355ebba65a90e339eb638018 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Mon, 13 Jun 2022 22:47:12 -0500 Subject: [PATCH 4/5] Refactor for mpy-cross --- adafruit_gps.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/adafruit_gps.py b/adafruit_gps.py index 114fa8f..4c51fe7 100644 --- a/adafruit_gps.py +++ b/adafruit_gps.py @@ -86,7 +86,8 @@ def _parse_degrees(nmea_data): raw = nmea_data.split(".") deg = int(raw[0]) // 100 * 1000000 # the ddd minutes = int(raw[0]) % 100 # the mm. - minutes += int(f"{raw[1][:4]:0<4}") / 10000 # the .mmmm + tmp = raw[1][:4] # mpy-cross wont compile if this is directly in the next line + minutes += int(f"{tmp:0<4}") / 10000 minutes = int(minutes / 60 * 1000000) return deg + minutes From 872ce6e4d5f6556b252aca4b2fdb28d7a8933161 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Thu, 16 Jun 2022 18:19:09 -0500 Subject: [PATCH 5/5] Spelling mistake, comment --- adafruit_gps.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/adafruit_gps.py b/adafruit_gps.py index 4c51fe7..2845846 100644 --- a/adafruit_gps.py +++ b/adafruit_gps.py @@ -84,12 +84,11 @@ def _parse_degrees(nmea_data): # Return the final value as an integer. Further functions can parse # this into a float or separate parts to retain the precision raw = nmea_data.split(".") - deg = int(raw[0]) // 100 * 1000000 # the ddd + degrees = int(raw[0]) // 100 * 1000000 # the ddd minutes = int(raw[0]) % 100 # the mm. - tmp = raw[1][:4] # mpy-cross wont compile if this is directly in the next line - minutes += int(f"{tmp:0<4}") / 10000 + minutes += int(f"{raw[1][:4]:0<4}") / 10000 minutes = int(minutes / 60 * 1000000) - return deg + minutes + return degrees + minutes # return parsed string in the format dddmmmmmm def _parse_int(nmea_data): @@ -118,7 +117,7 @@ def _read_degrees(data, index, neg): return x -def _read_int_degress(data, index, neg): +def _read_int_degrees(data, index, neg): deg = data[index] // 1000000 minutes = data[index] % 1000000 / 10000 if data[index + 1].lower() == neg: @@ -443,11 +442,11 @@ def _parse_gll(self, data): # Latitude self.latitude = _read_degrees(data, 0, "s") - self.latitude_degrees, self.latitude_minutes = _read_int_degress(data, 0, "s") + self.latitude_degrees, self.latitude_minutes = _read_int_degrees(data, 0, "s") # Longitude self.longitude = _read_degrees(data, 2, "w") - self.longitude_degrees, self.longitude_minutes = _read_int_degress(data, 2, "w") + self.longitude_degrees, self.longitude_minutes = _read_int_degrees(data, 2, "w") # UTC time of position self._update_timestamp_utc(data[4]) @@ -483,11 +482,11 @@ def _parse_rmc(self, data): # Latitude self.latitude = _read_degrees(data, 2, "s") - self.latitude_degrees, self.latitude_minutes = _read_int_degress(data, 2, "s") + self.latitude_degrees, self.latitude_minutes = _read_int_degrees(data, 2, "s") # Longitude self.longitude = _read_degrees(data, 4, "w") - self.longitude_degrees, self.longitude_minutes = _read_int_degress(data, 4, "w") + self.longitude_degrees, self.longitude_minutes = _read_int_degrees(data, 4, "w") # Speed over ground, knots self.speed_knots = data[6] @@ -521,11 +520,11 @@ def _parse_gga(self, data): # Latitude self.latitude = _read_degrees(data, 1, "s") - self.latitude_degrees, self.latitude_minutes = _read_int_degress(data, 1, "s") + self.latitude_degrees, self.latitude_minutes = _read_int_degrees(data, 1, "s") # Longitude self.longitude = _read_degrees(data, 3, "w") - self.longitude_degrees, self.longitude_minutes = _read_int_degress(data, 3, "w") + self.longitude_degrees, self.longitude_minutes = _read_int_degrees(data, 3, "w") # GPS quality indicator # 0 - fix not available,