diff --git a/adafruit_gps.py b/adafruit_gps.py index 36a17d2..2845846 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(".") + degrees = int(raw[0]) // 100 * 1000000 # the ddd + minutes = int(raw[0]) % 100 # the mm. + minutes += int(f"{raw[1][:4]:0<4}") / 10000 + minutes = int(minutes / 60 * 1000000) + return degrees + minutes # return parsed string in the format dddmmmmmm 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_degrees(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_degrees(data, 0, "s") # Longitude self.longitude = _read_degrees(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]) @@ -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_degrees(data, 2, "s") # Longitude self.longitude = _read_degrees(data, 4, "w") + self.longitude_degrees, self.longitude_minutes = _read_int_degrees(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_degrees(data, 1, "s") # Longitude self.longitude = _read_degrees(data, 3, "w") + self.longitude_degrees, self.longitude_minutes = _read_int_degrees(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..eb8de9b 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): @@ -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):