File: nmea_utils.py

package info (click to toggle)
python-nmea2 1.19.0-3
  • links: PTS
  • area: main
  • in suites: sid, trixie
  • size: 420 kB
  • sloc: python: 2,948; makefile: 3
file content (173 lines) | stat: -rw-r--r-- 4,474 bytes parent folder | download
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
#pylint: disable=invalid-name
import datetime
import re


# python 2.7 backport
if not hasattr(datetime, 'timezone'):
    class UTC(datetime.tzinfo):
        def utcoffset(self, dt):
            return datetime.timedelta(0)
    class timezone(object):
        utc = UTC()
    datetime.timezone = timezone


def valid(s):
    return s == 'A'


def timestamp(s):
    '''
    Converts a timestamp given in "hhmmss[.ss]" ASCII text format to a
    datetime.time object
    '''
    ms_s = s[6:]
    ms = ms_s and int(float(ms_s) * 1000000) or 0

    t = datetime.time(
        hour=int(s[0:2]),
        minute=int(s[2:4]),
        second=int(s[4:6]),
        microsecond=ms,
        tzinfo=datetime.timezone.utc)
    return t


def datestamp(s):
    '''
    Converts a datestamp given in "DDMMYY" ASCII text format to a
    datetime.datetime object
    '''
    return datetime.datetime.strptime(s, '%d%m%y').date()


def dm_to_sd(dm):
    '''
    Converts a geographic co-ordinate given in "degrees/minutes" dddmm.mmmm
    format (eg, "12319.943281" = 123 degrees, 19.943281 minutes) to a signed
    decimal (python float) format
    '''
    # '12319.943281'
    if not dm or dm == '0':
        return 0.
    r = re.match(r'^(\d+)(\d\d\.\d+)$', dm)
    if not r:
        raise ValueError("Geographic coordinate value '{}' is not valid DDDMM.MMM".format(dm))
    d, m = r.groups()
    return float(d) + float(m) / 60


class LatLonFix(object):
    '''Mixin to add `latitude` and `longitude` properties as signed decimals
    to NMEA sentences which have co-ordinates given as degrees/minutes (lat, lon)
    and cardinal directions (lat_dir, lon_dir)'''
    #pylint: disable=no-member
    @property
    def latitude(self):
        '''Latitude in signed degrees (python float)'''
        sd = dm_to_sd(self.lat)
        if self.lat_dir == 'N':
            return +sd
        elif self.lat_dir == 'S':
            return -sd
        else:
            return 0.

    @property
    def longitude(self):
        '''Longitude in signed degrees (python float)'''
        sd = dm_to_sd(self.lon)
        if self.lon_dir == 'E':
            return +sd
        elif self.lon_dir == 'W':
            return -sd
        else:
            return 0.

    @staticmethod
    def _minutes(x):
        return abs(x * 60.) % 60.

    @staticmethod
    def _seconds(x):
        return abs(x * 3600.) % 60.

    @property
    def latitude_minutes(self):
        return self._minutes(self.latitude)

    @property
    def longitude_minutes(self):
        return self._minutes(self.longitude)

    @property
    def latitude_seconds(self):
        return self._seconds(self.latitude)

    @property
    def longitude_seconds(self):
        return self._seconds(self.longitude)


class DatetimeFix(object):
    #pylint: disable=no-member
    @property
    def datetime(self):
        return datetime.datetime.combine(self.datestamp, self.timestamp)


class ValidStatusFix(object):
    #pylint: disable=no-member
    @property
    def is_valid(self):
        return self.status == 'A'


class ValidRMCStatusFix(ValidStatusFix):
    #pylint: disable=no-member
    @property
    def is_valid(self):
        status = super(ValidRMCStatusFix, self).is_valid
        if self.name_to_idx["mode_indicator"] < len(self.data):
            status &= self.mode_indicator in tuple('ADEFMPRS')
        if self.name_to_idx["nav_status"] < len(self.data):
            status &= self.nav_status in tuple('SCU')
        return status


class ValidGSAFix(object):
    #pylint: disable=no-member
    @property
    def is_valid(self):
        return int(self.mode_fix_type) in [2, 3]


class ValidGGAFix(object):
    #pylint: disable=no-member
    @property
    def is_valid(self):
        return self.gps_qual in range(1,6)


class ValidVBWFix(object):
    #pylint: disable=no-member
    @property
    def is_valid(self):
        return self.data_validity_water_spd == self.data_validity_grnd_spd == 'A'


class TZInfo(datetime.tzinfo):
    def __init__(self, hh, mm):
        self.hh = hh
        self.mm = mm
        super(TZInfo, self).__init__()

    def tzname(self, dt):
        return ''

    def dst(self, dt):
        return datetime.timedelta(0)

    def utcoffset(self, dt):
        return datetime.timedelta(hours=self.hh, minutes=self.mm)